diff options
author | John W. Parent <45471568+johnwparent@users.noreply.github.com> | 2024-09-06 08:26:46 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-06 14:26:46 +0200 |
commit | 4042afaa99426d8ed35643e531b69a8012fbd3f2 (patch) | |
tree | 5ff5555897e77f6538a192ee623595b54bbccc90 /lib | |
parent | 7fdf1029b747555f1d3040075bfeaf322ee6837e (diff) | |
download | spack-4042afaa99426d8ed35643e531b69a8012fbd3f2.tar.gz spack-4042afaa99426d8ed35643e531b69a8012fbd3f2.tar.bz2 spack-4042afaa99426d8ed35643e531b69a8012fbd3f2.tar.xz spack-4042afaa99426d8ed35643e531b69a8012fbd3f2.zip |
Bootstrap GnuPG and `file` on Windows (#41810)
Spack can now bootstrap two new dependencies on Windows: GnuPG, and file.
These dependencies are modeled as a separate package, and they install a cross-compiled binary.
Details on how they binaries are built are in https://github.com/spack/windows-bootstrap-resources
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/llnl/util/filesystem.py | 74 | ||||
-rw-r--r-- | lib/spack/spack/binary_distribution.py | 3 | ||||
-rw-r--r-- | lib/spack/spack/bootstrap/__init__.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/bootstrap/core.py | 23 | ||||
-rw-r--r-- | lib/spack/spack/bootstrap/status.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/package.py | 1 | ||||
-rw-r--r-- | lib/spack/spack/relocate.py | 6 | ||||
-rw-r--r-- | lib/spack/spack/util/filesystem.py | 102 |
8 files changed, 130 insertions, 83 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index 308c6154e1..54ace7d42f 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -27,8 +27,6 @@ from llnl.util import tty from llnl.util.lang import dedupe, memoized from llnl.util.symlink import islink, readlink, resolve_link_target_relative_to_the_link, symlink -from spack.util.executable import Executable, which - from ..path import path_to_os_path, system_path_filter if sys.platform != "win32": @@ -53,7 +51,6 @@ __all__ = [ "find_all_headers", "find_libraries", "find_system_libraries", - "fix_darwin_install_name", "force_remove", "force_symlink", "getuid", @@ -248,42 +245,6 @@ def path_contains_subdirectory(path, root): return norm_path.startswith(norm_root) -@memoized -def file_command(*args): - """Creates entry point to `file` system command with provided arguments""" - file_cmd = which("file", required=True) - for arg in args: - file_cmd.add_default_arg(arg) - return file_cmd - - -@memoized -def _get_mime_type(): - """Generate method to call `file` system command to aquire mime type - for a specified path - """ - if sys.platform == "win32": - # -h option (no-dereference) does not exist in Windows - return file_command("-b", "--mime-type") - else: - return file_command("-b", "-h", "--mime-type") - - -def mime_type(filename): - """Returns the mime type and subtype of a file. - - Args: - filename: file to be analyzed - - Returns: - Tuple containing the MIME type and subtype - """ - output = _get_mime_type()(filename, output=str, error=str).strip() - tty.debug("==> " + output) - type, _, subtype = output.partition("/") - return type, subtype - - #: This generates the library filenames that may appear on any OS. library_extensions = ["a", "la", "so", "tbd", "dylib"] @@ -1679,41 +1640,6 @@ def safe_remove(*files_or_dirs): raise -@system_path_filter -def fix_darwin_install_name(path): - """Fix install name of dynamic libraries on Darwin to have full path. - - There are two parts of this task: - - 1. Use ``install_name('-id', ...)`` to change install name of a single lib - 2. Use ``install_name('-change', ...)`` to change the cross linking between - libs. The function assumes that all libraries are in one folder and - currently won't follow subfolders. - - Parameters: - path (str): directory in which .dylib files are located - """ - libs = glob.glob(join_path(path, "*.dylib")) - for lib in libs: - # fix install name first: - install_name_tool = Executable("install_name_tool") - install_name_tool("-id", lib, lib) - otool = Executable("otool") - long_deps = otool("-L", lib, output=str).split("\n") - deps = [dep.partition(" ")[0][1::] for dep in long_deps[2:-1]] - # fix all dependencies: - for dep in deps: - for loc in libs: - # We really want to check for either - # dep == os.path.basename(loc) or - # dep == join_path(builddir, os.path.basename(loc)), - # but we don't know builddir (nor how symbolic links look - # in builddir). We thus only compare the basenames. - if os.path.basename(dep) == os.path.basename(loc): - install_name_tool("-change", dep, loc, lib) - break - - def find_first(root: str, files: Union[Iterable[str], str], bfs_depth: int = 2) -> Optional[str]: """Find the first file matching a pattern. diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py index 8d3c3cfb7a..15189f9d43 100644 --- a/lib/spack/spack/binary_distribution.py +++ b/lib/spack/spack/binary_distribution.py @@ -54,6 +54,7 @@ import spack.user_environment import spack.util.archive import spack.util.crypto import spack.util.file_cache as file_cache +import spack.util.filesystem as ssys import spack.util.gpg import spack.util.parallel import spack.util.path @@ -687,7 +688,7 @@ def get_buildfile_manifest(spec): # Non-symlinks. for rel_path in visitor.files: abs_path = os.path.join(root, rel_path) - m_type, m_subtype = fsys.mime_type(abs_path) + m_type, m_subtype = ssys.mime_type(abs_path) if relocate.needs_binary_relocation(m_type, m_subtype): # Why is this branch not part of needs_binary_relocation? :( diff --git a/lib/spack/spack/bootstrap/__init__.py b/lib/spack/spack/bootstrap/__init__.py index 85935cd0e0..d710caee68 100644 --- a/lib/spack/spack/bootstrap/__init__.py +++ b/lib/spack/spack/bootstrap/__init__.py @@ -9,6 +9,7 @@ from .core import ( all_core_root_specs, ensure_clingo_importable_or_raise, ensure_core_dependencies, + ensure_file_in_path_or_raise, ensure_gpg_in_path_or_raise, ensure_patchelf_in_path_or_raise, ) @@ -19,6 +20,7 @@ __all__ = [ "is_bootstrapping", "ensure_bootstrap_configuration", "ensure_core_dependencies", + "ensure_file_in_path_or_raise", "ensure_gpg_in_path_or_raise", "ensure_clingo_importable_or_raise", "ensure_patchelf_in_path_or_raise", diff --git a/lib/spack/spack/bootstrap/core.py b/lib/spack/spack/bootstrap/core.py index 62b6b86570..02909cbdf7 100644 --- a/lib/spack/spack/bootstrap/core.py +++ b/lib/spack/spack/bootstrap/core.py @@ -472,7 +472,8 @@ def ensure_clingo_importable_or_raise() -> None: def gnupg_root_spec() -> str: """Return the root spec used to bootstrap GnuPG""" - return _root_spec("gnupg@2.3:") + root_spec_name = "win-gpg" if IS_WINDOWS else "gnupg" + return _root_spec(f"{root_spec_name}@2.3:") def ensure_gpg_in_path_or_raise() -> None: @@ -482,6 +483,19 @@ def ensure_gpg_in_path_or_raise() -> None: ) +def file_root_spec() -> str: + """Return the root spec used to bootstrap file""" + root_spec_name = "win-file" if IS_WINDOWS else "file" + return _root_spec(root_spec_name) + + +def ensure_file_in_path_or_raise() -> None: + """Ensure file is in the PATH or raise""" + return ensure_executables_in_path_or_raise( + executables=["file"], abstract_spec=file_root_spec() + ) + + def patchelf_root_spec() -> str: """Return the root spec used to bootstrap patchelf""" # 0.13.1 is the last version not to require C++17. @@ -565,14 +579,15 @@ def ensure_core_dependencies() -> None: """Ensure the presence of all the core dependencies.""" if sys.platform.lower() == "linux": ensure_patchelf_in_path_or_raise() - if not IS_WINDOWS: - ensure_gpg_in_path_or_raise() + elif sys.platform == "win32": + ensure_file_in_path_or_raise() + ensure_gpg_in_path_or_raise() ensure_clingo_importable_or_raise() def all_core_root_specs() -> List[str]: """Return a list of all the core root specs that may be used to bootstrap Spack""" - return [clingo_root_spec(), gnupg_root_spec(), patchelf_root_spec()] + return [clingo_root_spec(), gnupg_root_spec(), patchelf_root_spec(), file_root_spec()] def bootstrapping_sources(scope: Optional[str] = None): diff --git a/lib/spack/spack/bootstrap/status.py b/lib/spack/spack/bootstrap/status.py index 582927af6e..6d3270b42c 100644 --- a/lib/spack/spack/bootstrap/status.py +++ b/lib/spack/spack/bootstrap/status.py @@ -88,7 +88,7 @@ def _core_requirements() -> List[RequiredResponseType]: def _buildcache_requirements() -> List[RequiredResponseType]: _buildcache_exes = { - "file": _missing("file", "required to analyze files for buildcaches"), + "file": _missing("file", "required to analyze files for buildcaches", system_only=False), ("gpg2", "gpg"): _missing("gpg2", "required to sign/verify buildcaches", False), } if platform.system().lower() == "darwin": diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index d0b7beda1d..99135b4834 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -104,6 +104,7 @@ from spack.package_base import ( from spack.spec import InvalidSpecDetected, Spec from spack.util.cpus import determine_number_of_jobs from spack.util.executable import * +from spack.util.filesystem import file_command, fix_darwin_install_name, mime_type from spack.variant import ( any_combination_of, auto_or_any_combination_of, diff --git a/lib/spack/spack/relocate.py b/lib/spack/spack/relocate.py index 357dd92f84..364e72f7c3 100644 --- a/lib/spack/spack/relocate.py +++ b/lib/spack/spack/relocate.py @@ -12,7 +12,6 @@ from typing import List, Optional import macholib.mach_o import macholib.MachO -import llnl.util.filesystem as fs import llnl.util.lang import llnl.util.tty as tty from llnl.util.lang import memoized @@ -25,6 +24,7 @@ import spack.spec import spack.store import spack.util.elf as elf import spack.util.executable as executable +import spack.util.filesystem as ssys import spack.util.path from .relocate_text import BinaryFilePrefixReplacer, TextFilePrefixReplacer @@ -664,7 +664,7 @@ def is_binary(filename): Returns: True or False """ - m_type, _ = fs.mime_type(filename) + m_type, _ = ssys.mime_type(filename) msg = "[{0}] -> ".format(filename) if m_type == "application": @@ -692,7 +692,7 @@ def fixup_macos_rpath(root, filename): True if fixups were applied, else False """ abspath = os.path.join(root, filename) - if fs.mime_type(abspath) != ("application", "x-mach-binary"): + if ssys.mime_type(abspath) != ("application", "x-mach-binary"): return False # Get Mach-O header commands diff --git a/lib/spack/spack/util/filesystem.py b/lib/spack/spack/util/filesystem.py new file mode 100644 index 0000000000..b296438fe8 --- /dev/null +++ b/lib/spack/spack/util/filesystem.py @@ -0,0 +1,102 @@ +# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +""" +Utilities for interacting with files, +like those in llnl.util.filesystem, but which require logic from spack.util +""" + +import glob +import os +import sys + +from llnl.util import tty +from llnl.util.filesystem import join_path +from llnl.util.lang import memoized + +from spack.util.executable import Executable, which + + +def _ensure_file_on_win(): + """Ensures the file command is available on Windows + If not, it is bootstrapped. + No-op on all other platforms""" + if sys.platform != "win32": + return + import spack.bootstrap + + with spack.bootstrap.ensure_bootstrap_configuration(): + spack.bootstrap.ensure_file_in_path_or_raise() + + +@memoized +def file_command(*args): + """Creates entry point to `file` system command with provided arguments""" + _ensure_file_on_win() + file_cmd = which("file", required=True) + for arg in args: + file_cmd.add_default_arg(arg) + return file_cmd + + +@memoized +def _get_mime_type(): + """Generate method to call `file` system command to aquire mime type + for a specified path + """ + if sys.platform == "win32": + # -h option (no-dereference) does not exist in Windows + return file_command("-b", "--mime-type") + else: + return file_command("-b", "-h", "--mime-type") + + +def mime_type(filename): + """Returns the mime type and subtype of a file. + + Args: + filename: file to be analyzed + + Returns: + Tuple containing the MIME type and subtype + """ + output = _get_mime_type()(filename, output=str, error=str).strip() + tty.debug("==> " + output) + type, _, subtype = output.partition("/") + return type, subtype + + +def fix_darwin_install_name(path): + """Fix install name of dynamic libraries on Darwin to have full path. + + There are two parts of this task: + + 1. Use ``install_name('-id', ...)`` to change install name of a single lib + 2. Use ``install_name('-change', ...)`` to change the cross linking between + libs. The function assumes that all libraries are in one folder and + currently won't follow subfolders. + + Parameters: + path (str): directory in which .dylib files are located + """ + libs = glob.glob(join_path(path, "*.dylib")) + for lib in libs: + # fix install name first: + install_name_tool = Executable("install_name_tool") + install_name_tool("-id", lib, lib) + otool = Executable("otool") + long_deps = otool("-L", lib, output=str).split("\n") + deps = [dep.partition(" ")[0][1::] for dep in long_deps[2:-1]] + # fix all dependencies: + for dep in deps: + for loc in libs: + # We really want to check for either + # dep == os.path.basename(loc) or + # dep == join_path(builddir, os.path.basename(loc)), + # but we don't know builddir (nor how symbolic links look + # in builddir). We thus only compare the basenames. + if os.path.basename(dep) == os.path.basename(loc): + install_name_tool("-change", dep, loc, lib) + break |