Tcl package: support build on Windows (#41939)
This commit is contained in:
		| @@ -4,14 +4,32 @@ | |||||||
| # SPDX-License-Identifier: (Apache-2.0 OR MIT) | # SPDX-License-Identifier: (Apache-2.0 OR MIT) | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
|  | import sys | ||||||
| 
 | 
 | ||||||
| from llnl.util.filesystem import find_first | from llnl.util.filesystem import find_first | ||||||
| 
 | 
 | ||||||
| from spack.package import * | from spack.package import * | ||||||
| from spack.util.environment import is_system_path | from spack.util.environment import is_system_path | ||||||
| 
 | 
 | ||||||
|  | is_windows = sys.platform == "win32" | ||||||
| 
 | 
 | ||||||
| class Tcl(AutotoolsPackage, SourceforgePackage): | 
 | ||||||
|  | class TclHelper: | ||||||
|  |     @staticmethod | ||||||
|  |     def find_script_dir(spec): | ||||||
|  |         # Put more-specific prefixes first | ||||||
|  |         check_prefixes = [ | ||||||
|  |             join_path(spec.prefix, "share", "tcl{0}".format(spec.package.version.up_to(2))), | ||||||
|  |             spec.prefix, | ||||||
|  |         ] | ||||||
|  |         for prefix in check_prefixes: | ||||||
|  |             result = find_first(prefix, "init.tcl") | ||||||
|  |             if result: | ||||||
|  |                 return os.path.dirname(result) | ||||||
|  |         raise RuntimeError("Cannot locate init.tcl") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Tcl(AutotoolsPackage, NMakePackage, SourceforgePackage, TclHelper): | ||||||
|     """Tcl (Tool Command Language) is a very powerful but easy to learn dynamic |     """Tcl (Tool Command Language) is a very powerful but easy to learn dynamic | ||||||
|     programming language, suitable for a very wide range of uses, including web and |     programming language, suitable for a very wide range of uses, including web and | ||||||
|     desktop applications, networking, administration, testing and many more. Open source |     desktop applications, networking, administration, testing and many more. Open source | ||||||
| @@ -20,6 +38,7 @@ class Tcl(AutotoolsPackage, SourceforgePackage): | |||||||
| 
 | 
 | ||||||
|     homepage = "https://www.tcl.tk/" |     homepage = "https://www.tcl.tk/" | ||||||
|     sourceforge_mirror_path = "tcl/tcl8.6.11-src.tar.gz" |     sourceforge_mirror_path = "tcl/tcl8.6.11-src.tar.gz" | ||||||
|  |     tags = ["windows"] | ||||||
| 
 | 
 | ||||||
|     license("TCL") |     license("TCL") | ||||||
| 
 | 
 | ||||||
| @@ -37,53 +56,30 @@ class Tcl(AutotoolsPackage, SourceforgePackage): | |||||||
| 
 | 
 | ||||||
|     depends_on("zlib-api") |     depends_on("zlib-api") | ||||||
| 
 | 
 | ||||||
|     configure_directory = "unix" |     # No compiler wrappers on Windows | ||||||
| 
 |     for plat in ["linux", "darwin", "cray", "freebsd"]: | ||||||
|     filter_compiler_wrappers("tclConfig.sh", relative_root="lib") |         filter_compiler_wrappers("tclConfig.sh", relative_root="lib", when=f"platform={plat}") | ||||||
| 
 |  | ||||||
|     def install(self, spec, prefix): |  | ||||||
|         with working_dir(self.build_directory): |  | ||||||
|             make("install") |  | ||||||
| 
 |  | ||||||
|             # https://wiki.tcl-lang.org/page/kitgen |  | ||||||
|             if self.spec.satisfies("@8.6:"): |  | ||||||
|                 make("install-headers") |  | ||||||
| 
 |  | ||||||
|             # Some applications like Expect require private Tcl headers. |  | ||||||
|             make("install-private-headers") |  | ||||||
| 
 |  | ||||||
|             # Copy source to install tree |  | ||||||
|             # A user-provided install option might re-do this |  | ||||||
|             # https://github.com/spack/spack/pull/4102/files |  | ||||||
|             installed_src = join_path(self.spec.prefix, "share", self.name, "src") |  | ||||||
|             stage_src = os.path.realpath(self.stage.source_path) |  | ||||||
|             install_tree(stage_src, installed_src) |  | ||||||
| 
 |  | ||||||
|             # Replace stage dir -> installed src dir in tclConfig |  | ||||||
|             filter_file( |  | ||||||
|                 stage_src, |  | ||||||
|                 installed_src, |  | ||||||
|                 join_path(self.spec["tcl"].libs.directories[0], "tclConfig.sh"), |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         # Don't install binaries in src/ tree |  | ||||||
|         with working_dir(join_path(installed_src, self.configure_directory)): |  | ||||||
|             make("clean") |  | ||||||
| 
 |  | ||||||
|     @run_after("install") |  | ||||||
|     def symlink_tclsh(self): |  | ||||||
|         with working_dir(self.prefix.bin): |  | ||||||
|             symlink("tclsh{0}".format(self.version.up_to(2)), "tclsh") |  | ||||||
| 
 | 
 | ||||||
|  |     build_system("autotools", "nmake") | ||||||
|  |     patch("tcl-quote-cc-path.patch", when="platform=windows") | ||||||
|     # ======================================================================== |     # ======================================================================== | ||||||
|     # Set up environment to make install easy for tcl extensions. |     # Set up environment to make install easy for tcl extensions. | ||||||
|     # ======================================================================== |     # ======================================================================== | ||||||
| 
 | 
 | ||||||
|  |     @property | ||||||
|  |     def _tcl_name(self): | ||||||
|  |         ver_suffix = self.version.up_to(2) | ||||||
|  |         win_suffix = "" | ||||||
|  |         if is_windows: | ||||||
|  |             if self.spec.satisfies("@:8.7"): | ||||||
|  |                 win_suffix = "t" | ||||||
|  |             ver_suffix = ver_suffix.joined | ||||||
|  |         return f"{ver_suffix}{win_suffix}" | ||||||
|  | 
 | ||||||
|     @property |     @property | ||||||
|     def libs(self): |     def libs(self): | ||||||
|         return find_libraries( |         lib = "lib" if not is_windows else "" | ||||||
|             ["libtcl{0}".format(self.version.up_to(2))], root=self.prefix, recursive=True |         return find_libraries([f"{lib}tcl{self._tcl_name}"], root=self.prefix, recursive=True) | ||||||
|         ) |  | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def command(self): |     def command(self): | ||||||
| @@ -95,21 +91,8 @@ def command(self): | |||||||
|         # Although we symlink tclshX.Y to tclsh, we also need to support external |         # Although we symlink tclshX.Y to tclsh, we also need to support external | ||||||
|         # installations that may not have this symlink, or may have multiple versions |         # installations that may not have this symlink, or may have multiple versions | ||||||
|         # of Tcl installed in the same directory. |         # of Tcl installed in the same directory. | ||||||
|         return Executable( |         exe = ".exe" if is_windows else "" | ||||||
|             os.path.realpath(self.prefix.bin.join("tclsh{0}".format(self.version.up_to(2)))) |         return Executable(os.path.realpath(self.prefix.bin.join(f"tclsh{self._tcl_name}{exe}"))) | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def _find_script_dir(self): |  | ||||||
|         # Put more-specific prefixes first |  | ||||||
|         check_prefixes = [ |  | ||||||
|             join_path(self.prefix, "share", "tcl{0}".format(self.version.up_to(2))), |  | ||||||
|             self.prefix, |  | ||||||
|         ] |  | ||||||
|         for prefix in check_prefixes: |  | ||||||
|             result = find_first(prefix, "init.tcl") |  | ||||||
|             if result: |  | ||||||
|                 return os.path.dirname(result) |  | ||||||
|         raise RuntimeError("Cannot locate init.tcl") |  | ||||||
| 
 | 
 | ||||||
|     def setup_run_environment(self, env): |     def setup_run_environment(self, env): | ||||||
|         """Set TCL_LIBRARY to the directory containing init.tcl. |         """Set TCL_LIBRARY to the directory containing init.tcl. | ||||||
| @@ -120,7 +103,41 @@ def setup_run_environment(self, env): | |||||||
|         """ |         """ | ||||||
|         # When using tkinter from within spack provided python+tkinter, |         # When using tkinter from within spack provided python+tkinter, | ||||||
|         # python will not be able to find Tcl unless TCL_LIBRARY is set. |         # python will not be able to find Tcl unless TCL_LIBRARY is set. | ||||||
|         env.set("TCL_LIBRARY", self._find_script_dir()) |         env.set("TCL_LIBRARY", TclHelper.find_script_dir(self.spec)) | ||||||
|  | 
 | ||||||
|  |     def setup_dependent_run_environment(self, env, dependent_spec): | ||||||
|  |         """Set TCLLIBPATH to include the tcl-shipped directory for | ||||||
|  |         extensions and any other tcl extension it depends on. | ||||||
|  | 
 | ||||||
|  |         For further info see: | ||||||
|  | 
 | ||||||
|  |         * https://wiki.tcl-lang.org/page/TCLLIBPATH | ||||||
|  |         """ | ||||||
|  |         if dependent_spec.package.extends(self.spec): | ||||||
|  |             # Tcl libraries may be installed in lib or lib64, see #19546 | ||||||
|  |             for lib in ["lib", "lib64"]: | ||||||
|  |                 tcllibpath = join_path(dependent_spec.prefix, lib) | ||||||
|  |                 if os.path.exists(tcllibpath): | ||||||
|  |                     env.prepend_path("TCLLIBPATH", tcllibpath, separator=" ") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseBuilder(TclHelper, metaclass=spack.builder.PhaseCallbacksMeta): | ||||||
|  |     @run_after("install") | ||||||
|  |     def symlink_tclsh(self): | ||||||
|  |         # There's some logic regarding this suffix in the build system | ||||||
|  |         # but the way Spack builds tcl, the Windows suffix is always 't' | ||||||
|  |         # unless the version is >= 8.7, in which case there is no suffix | ||||||
|  |         # if the build is ever switched to static, this will need to change | ||||||
|  |         # to be "s[t]" | ||||||
|  |         win_suffix = "" | ||||||
|  |         ver_suffix = self.pkg.version.up_to(2) | ||||||
|  |         if is_windows: | ||||||
|  |             win_suffix = "t" if self.spec.satisfies("@:8.7") else "" | ||||||
|  |             win_suffix += ".exe" | ||||||
|  |             ver_suffix = ver_suffix.joined | ||||||
|  | 
 | ||||||
|  |         with working_dir(self.prefix.bin): | ||||||
|  |             symlink(f"tclsh{ver_suffix}{win_suffix}", "tclsh") | ||||||
| 
 | 
 | ||||||
|     def setup_dependent_build_environment(self, env, dependent_spec): |     def setup_dependent_build_environment(self, env, dependent_spec): | ||||||
|         """Set TCL_LIBRARY to the directory containing init.tcl. |         """Set TCL_LIBRARY to the directory containing init.tcl. | ||||||
| @@ -132,15 +149,15 @@ def setup_dependent_build_environment(self, env, dependent_spec): | |||||||
|         * https://wiki.tcl-lang.org/page/TCL_LIBRARY |         * https://wiki.tcl-lang.org/page/TCL_LIBRARY | ||||||
|         * https://wiki.tcl-lang.org/page/TCLLIBPATH |         * https://wiki.tcl-lang.org/page/TCLLIBPATH | ||||||
|         """ |         """ | ||||||
|         env.set("TCL_LIBRARY", self._find_script_dir()) |         env.set("TCL_LIBRARY", TclHelper.find_script_dir(self.spec)) | ||||||
| 
 | 
 | ||||||
|         # If we set TCLLIBPATH, we must also ensure that the corresponding |         # If we set TCLLIBPATH, we must also ensure that the corresponding | ||||||
|         # tcl is found in the build environment. This to prevent cases |         # tcl is found in the build environment. This to prevent cases | ||||||
|         # where a system provided tcl is run against the standard libraries |         # where a system provided tcl is run against the standard libraries | ||||||
|         # of a Spack built tcl. See issue #7128 that relates to python but |         # of a Spack built tcl. See issue #7128 that relates to python but | ||||||
|         # it boils down to the same situation we have here. |         # it boils down to the same situation we have here. | ||||||
|         if not is_system_path(self.prefix.bin): |         if not is_system_path(self.spec.prefix.bin): | ||||||
|             env.prepend_path("PATH", self.prefix.bin) |             env.prepend_path("PATH", self.spec.prefix.bin) | ||||||
| 
 | 
 | ||||||
|         # WARNING: paths in $TCLLIBPATH must be *space* separated, |         # WARNING: paths in $TCLLIBPATH must be *space* separated, | ||||||
|         # its value is meant to be a Tcl list, *not* an env list |         # its value is meant to be a Tcl list, *not* an env list | ||||||
| @@ -162,17 +179,51 @@ def setup_dependent_build_environment(self, env, dependent_spec): | |||||||
|                 if os.path.exists(tcllibpath): |                 if os.path.exists(tcllibpath): | ||||||
|                     env.prepend_path("TCLLIBPATH", tcllibpath, separator=" ") |                     env.prepend_path("TCLLIBPATH", tcllibpath, separator=" ") | ||||||
| 
 | 
 | ||||||
|     def setup_dependent_run_environment(self, env, dependent_spec): |  | ||||||
|         """Set TCLLIBPATH to include the tcl-shipped directory for |  | ||||||
|         extensions and any other tcl extension it depends on. |  | ||||||
| 
 | 
 | ||||||
|         For further info see: | class AutotoolsBuilder(BaseBuilder, spack.build_systems.autotools.AutotoolsBuilder): | ||||||
|  |     configure_directory = "unix" | ||||||
| 
 | 
 | ||||||
|         * https://wiki.tcl-lang.org/page/TCLLIBPATH |     def install(self, pkg, spec, prefix): | ||||||
|         """ |         with working_dir(self.build_directory): | ||||||
|         if dependent_spec.package.extends(self.spec): |             make("install") | ||||||
|             # Tcl libraries may be installed in lib or lib64, see #19546 | 
 | ||||||
|             for lib in ["lib", "lib64"]: |             # https://wiki.tcl-lang.org/page/kitgen | ||||||
|                 tcllibpath = join_path(dependent_spec.prefix, lib) |             if self.spec.satisfies("@8.6:"): | ||||||
|                 if os.path.exists(tcllibpath): |                 make("install-headers") | ||||||
|                     env.prepend_path("TCLLIBPATH", tcllibpath, separator=" ") | 
 | ||||||
|  |             # Some applications like Expect require private Tcl headers. | ||||||
|  |             make("install-private-headers") | ||||||
|  | 
 | ||||||
|  |             # Copy source to install tree | ||||||
|  |             # A user-provided install option might re-do this | ||||||
|  |             # https://github.com/spack/spack/pull/4102/files | ||||||
|  |             installed_src = join_path(self.spec.prefix, "share", pkg.name, "src") | ||||||
|  |             stage_src = os.path.realpath(pkg.stage.source_path) | ||||||
|  |             install_tree(stage_src, installed_src) | ||||||
|  | 
 | ||||||
|  |             # Replace stage dir -> installed src dir in tclConfig | ||||||
|  |             filter_file( | ||||||
|  |                 stage_src, | ||||||
|  |                 installed_src, | ||||||
|  |                 join_path(self.spec["tcl"].libs.directories[0], "tclConfig.sh"), | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         # Don't install binaries in src/ tree | ||||||
|  |         with working_dir(join_path(installed_src, self.configure_directory)): | ||||||
|  |             make("clean") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NMakeBuilder(BaseBuilder, spack.build_systems.nmake.NMakeBuilder): | ||||||
|  |     build_targets = ["all"] | ||||||
|  |     install_targets = ["install"] | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def makefile_root(self): | ||||||
|  |         return f"{self.stage.source_path}\\win" | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def makefile_name(self): | ||||||
|  |         return "makefile.vc" | ||||||
|  | 
 | ||||||
|  |     def nmake_install_args(self): | ||||||
|  |         return [self.define("INSTALLDIR", self.spec.prefix)] | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								var/spack/repos/builtin/packages/tcl/tcl-quote-cc-path.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								var/spack/repos/builtin/packages/tcl/tcl-quote-cc-path.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | diff --git a/win/rules.vc b/win/rules.vc | ||||||
|  | index 8a91b58b46..9e888ce8cb 100644 | ||||||
|  | --- a/win/rules.vc | ||||||
|  | +++ b/win/rules.vc | ||||||
|  | @@ -416,7 +416,7 @@ _INSTALLDIR=$(_INSTALLDIR)\lib | ||||||
|  |  # MACHINE - same as $(ARCH) - legacy | ||||||
|  |  # _VC_MANIFEST_EMBED_{DLL,EXE} - commands for embedding a manifest if needed | ||||||
|  |  | ||||||
|  | -cc32		= $(CC)   # built-in default. | ||||||
|  | +cc32		= "$(CC)"   # built-in default. | ||||||
|  |  link32		= link | ||||||
|  |  lib32		= lib | ||||||
|  |  rc32		= $(RC)   # built-in default. | ||||||
		Reference in New Issue
	
	Block a user
	 John W. Parent
					John W. Parent