diff options
-rw-r--r-- | lib/spack/spack/caches.py | 19 | ||||
-rw-r--r-- | lib/spack/spack/cmd/develop.py | 8 | ||||
-rw-r--r-- | lib/spack/spack/fetch_strategy.py | 94 | ||||
-rw-r--r-- | lib/spack/spack/mirror.py | 65 | ||||
-rw-r--r-- | lib/spack/spack/package_base.py | 8 | ||||
-rw-r--r-- | lib/spack/spack/patch.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/stage.py | 105 | ||||
-rw-r--r-- | lib/spack/spack/test/mirror.py | 54 |
8 files changed, 173 insertions, 182 deletions
diff --git a/lib/spack/spack/caches.py b/lib/spack/spack/caches.py index 14d03a14b3..eda141ed15 100644 --- a/lib/spack/spack/caches.py +++ b/lib/spack/spack/caches.py @@ -9,11 +9,11 @@ from typing import Union import llnl.util.lang from llnl.util.filesystem import mkdirp -from llnl.util.symlink import symlink import spack.config import spack.error import spack.fetch_strategy +import spack.mirror import spack.paths import spack.util.file_cache import spack.util.path @@ -74,23 +74,6 @@ class MirrorCache: mkdirp(os.path.dirname(dst)) fetcher.archive(dst) - def symlink(self, mirror_ref): - """Symlink a human readible path in our mirror to the actual - storage location.""" - - cosmetic_path = os.path.join(self.root, mirror_ref.cosmetic_path) - storage_path = os.path.join(self.root, mirror_ref.storage_path) - relative_dst = os.path.relpath(storage_path, start=os.path.dirname(cosmetic_path)) - - if not os.path.exists(cosmetic_path): - if os.path.lexists(cosmetic_path): - # In this case the link itself exists but it is broken: remove - # it and recreate it (in order to fix any symlinks broken prior - # to https://github.com/spack/spack/pull/13908) - os.unlink(cosmetic_path) - mkdirp(os.path.dirname(cosmetic_path)) - symlink(relative_dst, cosmetic_path) - #: Spack's local cache for downloaded source archives FETCH_CACHE: Union[spack.fetch_strategy.FsCache, llnl.util.lang.Singleton] = ( diff --git a/lib/spack/spack/cmd/develop.py b/lib/spack/spack/cmd/develop.py index e226c16766..cc181d9a92 100644 --- a/lib/spack/spack/cmd/develop.py +++ b/lib/spack/spack/cmd/develop.py @@ -10,8 +10,10 @@ import llnl.util.tty as tty import spack.cmd import spack.config import spack.fetch_strategy +import spack.package_base import spack.repo import spack.spec +import spack.stage import spack.util.path import spack.version from spack.cmd.common import arguments @@ -62,7 +64,7 @@ def _update_config(spec, path): spack.config.change_or_add("develop", find_fn, change_fn) -def _retrieve_develop_source(spec, abspath): +def _retrieve_develop_source(spec: spack.spec.Spec, abspath: str) -> None: # "steal" the source code via staging API. We ask for a stage # to be created, then copy it afterwards somewhere else. It would be # better if we can create the `source_path` directly into its final @@ -71,13 +73,13 @@ def _retrieve_develop_source(spec, abspath): # We construct a package class ourselves, rather than asking for # Spec.package, since Spec only allows this when it is concrete package = pkg_cls(spec) - source_stage = package.stage[0] + source_stage: spack.stage.Stage = package.stage[0] if isinstance(source_stage.fetcher, spack.fetch_strategy.GitFetchStrategy): source_stage.fetcher.get_full_repo = True # If we retrieved this version before and cached it, we may have # done so without cloning the full git repo; likewise, any # mirror might store an instance with truncated history. - source_stage.disable_mirrors() + source_stage.default_fetcher_only = True source_stage.fetcher.set_package(package) package.stage.steal_source(abspath) diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py index 3b22cfb94c..f163f2cb34 100644 --- a/lib/spack/spack/fetch_strategy.py +++ b/lib/spack/spack/fetch_strategy.py @@ -574,18 +574,18 @@ class VCSFetchStrategy(FetchStrategy): # Set a URL based on the type of fetch strategy. self.url = kwargs.get(self.url_attr, None) if not self.url: - raise ValueError("%s requires %s argument." % (self.__class__, self.url_attr)) + raise ValueError(f"{self.__class__} requires {self.url_attr} argument.") for attr in self.optional_attrs: setattr(self, attr, kwargs.get(attr, None)) @_needs_stage def check(self): - tty.debug("No checksum needed when fetching with {0}".format(self.url_attr)) + tty.debug(f"No checksum needed when fetching with {self.url_attr}") @_needs_stage def expand(self): - tty.debug("Source fetched with %s is already expanded." % self.url_attr) + tty.debug(f"Source fetched with {self.url_attr} is already expanded.") @_needs_stage def archive(self, destination, *, exclude: Optional[str] = None): @@ -605,10 +605,10 @@ class VCSFetchStrategy(FetchStrategy): ) def __str__(self): - return "VCS: %s" % self.url + return f"VCS: {self.url}" def __repr__(self): - return "%s<%s>" % (self.__class__, self.url) + return f"{self.__class__}<{self.url}>" @fetcher @@ -717,6 +717,11 @@ class GitFetchStrategy(VCSFetchStrategy): git_version_re = r"git version (\S+)" def __init__(self, **kwargs): + + self.commit: Optional[str] = None + self.tag: Optional[str] = None + self.branch: Optional[str] = None + # Discards the keywords in kwargs that may conflict with the next call # to __init__ forwarded_args = copy.copy(kwargs) @@ -765,43 +770,42 @@ class GitFetchStrategy(VCSFetchStrategy): @property def cachable(self): - return self.cache_enabled and bool(self.commit or self.tag) + return self.cache_enabled and bool(self.commit) def source_id(self): - return self.commit or self.tag + # TODO: tree-hash would secure download cache and mirrors, commit only secures checkouts. + return self.commit def mirror_id(self): - repo_ref = self.commit or self.tag or self.branch - if repo_ref: + if self.commit: repo_path = urllib.parse.urlparse(self.url).path - result = os.path.sep.join(["git", repo_path, repo_ref]) + result = os.path.sep.join(["git", repo_path, self.commit]) return result def _repo_info(self): args = "" - if self.commit: - args = " at commit {0}".format(self.commit) + args = f" at commit {self.commit}" elif self.tag: - args = " at tag {0}".format(self.tag) + args = f" at tag {self.tag}" elif self.branch: - args = " on branch {0}".format(self.branch) + args = f" on branch {self.branch}" - return "{0}{1}".format(self.url, args) + return f"{self.url}{args}" @_needs_stage def fetch(self): if self.stage.expanded: - tty.debug("Already fetched {0}".format(self.stage.source_path)) + tty.debug(f"Already fetched {self.stage.source_path}") return if self.git_sparse_paths: - self._sparse_clone_src(commit=self.commit, branch=self.branch, tag=self.tag) + self._sparse_clone_src() else: - self._clone_src(commit=self.commit, branch=self.branch, tag=self.tag) + self._clone_src() self.submodule_operations() - def bare_clone(self, dest): + def bare_clone(self, dest: str) -> None: """ Execute a bare clone for metadata only @@ -809,7 +813,7 @@ class GitFetchStrategy(VCSFetchStrategy): and shouldn't be used for staging. """ # Default to spack source path - tty.debug("Cloning git repository: {0}".format(self._repo_info())) + tty.debug(f"Cloning git repository: {self._repo_info()}") git = self.git debug = spack.config.get("config:debug") @@ -821,24 +825,16 @@ class GitFetchStrategy(VCSFetchStrategy): clone_args.extend([self.url, dest]) git(*clone_args) - def _clone_src(self, commit=None, branch=None, tag=None): - """ - Clone a repository to a path using git. - - Arguments: - commit (str or None): A commit to fetch from the remote. Only one of - commit, branch, and tag may be non-None. - branch (str or None): A branch to fetch from the remote. - tag (str or None): A tag to fetch from the remote. - """ + def _clone_src(self) -> None: + """Clone a repository to a path using git.""" # Default to spack source path dest = self.stage.source_path - tty.debug("Cloning git repository: {0}".format(self._repo_info())) + tty.debug(f"Cloning git repository: {self._repo_info()}") git = self.git debug = spack.config.get("config:debug") - if commit: + if self.commit: # Need to do a regular clone and check out everything if # they asked for a particular commit. clone_args = ["clone", self.url] @@ -857,7 +853,7 @@ class GitFetchStrategy(VCSFetchStrategy): ) with working_dir(dest): - checkout_args = ["checkout", commit] + checkout_args = ["checkout", self.commit] if not debug: checkout_args.insert(1, "--quiet") git(*checkout_args) @@ -869,10 +865,10 @@ class GitFetchStrategy(VCSFetchStrategy): args.append("--quiet") # If we want a particular branch ask for it. - if branch: - args.extend(["--branch", branch]) - elif tag and self.git_version >= spack.version.Version("1.8.5.2"): - args.extend(["--branch", tag]) + if self.branch: + args.extend(["--branch", self.branch]) + elif self.tag and self.git_version >= spack.version.Version("1.8.5.2"): + args.extend(["--branch", self.tag]) # Try to be efficient if we're using a new enough git. # This checks out only one branch's history @@ -904,7 +900,7 @@ class GitFetchStrategy(VCSFetchStrategy): # For tags, be conservative and check them out AFTER # cloning. Later git versions can do this with clone # --branch, but older ones fail. - if tag and self.git_version < spack.version.Version("1.8.5.2"): + if self.tag and self.git_version < spack.version.Version("1.8.5.2"): # pull --tags returns a "special" error code of 1 in # older versions that we have to ignore. # see: https://github.com/git/git/commit/19d122b @@ -917,16 +913,8 @@ class GitFetchStrategy(VCSFetchStrategy): git(*pull_args, ignore_errors=1) git(*co_args) - def _sparse_clone_src(self, commit=None, branch=None, tag=None, **kwargs): - """ - Use git's sparse checkout feature to clone portions of a git repository - - Arguments: - commit (str or None): A commit to fetch from the remote. Only one of - commit, branch, and tag may be non-None. - branch (str or None): A branch to fetch from the remote. - tag (str or None): A tag to fetch from the remote. - """ + def _sparse_clone_src(self, **kwargs): + """Use git's sparse checkout feature to clone portions of a git repository""" dest = self.stage.source_path git = self.git @@ -945,12 +933,12 @@ class GitFetchStrategy(VCSFetchStrategy): "Cloning the full repository instead." ) ) - self._clone_src(commit, branch, tag) + self._clone_src() else: # default to depth=2 to allow for retention of some git properties depth = kwargs.get("depth", 2) - needs_fetch = branch or tag - git_ref = branch or tag or commit + needs_fetch = self.branch or self.tag + git_ref = self.branch or self.tag or self.commit assert git_ref @@ -1050,7 +1038,7 @@ class GitFetchStrategy(VCSFetchStrategy): return not (self.url.startswith("http://") or self.url.startswith("/")) def __str__(self): - return "[git] {0}".format(self._repo_info()) + return f"[git] {self._repo_info()}" @fetcher @@ -1668,7 +1656,7 @@ def for_package_version(pkg, version=None): raise InvalidArgsError(pkg, version, **args) -def from_url_scheme(url: str, **kwargs): +def from_url_scheme(url: str, **kwargs) -> FetchStrategy: """Finds a suitable FetchStrategy by matching its url_attr with the scheme in the given url.""" parsed_url = urllib.parse.urlparse(url, scheme="file") diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py index af2a179dd0..8caa272137 100644 --- a/lib/spack/spack/mirror.py +++ b/lib/spack/spack/mirror.py @@ -21,6 +21,7 @@ import urllib.parse from typing import List, Optional, Union import llnl.url +import llnl.util.symlink import llnl.util.tty as tty from llnl.util.filesystem import mkdirp @@ -30,6 +31,7 @@ import spack.error import spack.fetch_strategy import spack.mirror import spack.oci.image +import spack.repo import spack.spec import spack.util.path import spack.util.spack_json as sjson @@ -427,27 +429,58 @@ Spack not to expand it with the following syntax: class MirrorLayout: - """A ``MirrorLayout`` stores the relative locations of files in a mirror directory. The main - storage location is ``storage_path``. An additional, human-readable path may be obtained as the - second entry when iterating this object.""" + """A ``MirrorLayout`` object describes the relative path of a mirror entry.""" - def __init__(self, storage_path: str) -> None: - self.storage_path = storage_path + def __init__(self, path: str) -> None: + self.path = path def __iter__(self): - yield self.storage_path + """Yield all paths including aliases where the resource can be found.""" + yield self.path + + def make_alias(self, root: str) -> None: + """Make the entry ``root / self.path`` available under a human readable alias""" + pass class DefaultLayout(MirrorLayout): - def __init__(self, cosmetic_path: str, global_path: Optional[str] = None) -> None: - super().__init__(global_path or cosmetic_path) - self.global_path = global_path - self.cosmetic_path = cosmetic_path + def __init__(self, alias_path: str, digest_path: Optional[str] = None) -> None: + # When we have a digest, it is used as the primary storage location. If not, then we use + # the human-readable alias. In case of mirrors of a VCS checkout, we currently do not have + # a digest, that's why an alias is required and a digest optional. + super().__init__(path=digest_path or alias_path) + self.alias = alias_path + self.digest_path = digest_path + + def make_alias(self, root: str) -> None: + """Symlink a human readible path in our mirror to the actual storage location.""" + # We already use the human-readable path as the main storage location. + if not self.digest_path: + return + + alias, digest = os.path.join(root, self.alias), os.path.join(root, self.digest_path) + + alias_dir = os.path.dirname(alias) + relative_dst = os.path.relpath(digest, start=alias_dir) + + mkdirp(alias_dir) + tmp = f"{alias}.tmp" + llnl.util.symlink.symlink(relative_dst, tmp) + + try: + os.rename(tmp, alias) + except OSError: + # Clean up the temporary if possible + try: + os.unlink(tmp) + except OSError: + pass + raise def __iter__(self): - if self.global_path: - yield self.global_path - yield self.cosmetic_path + if self.digest_path: + yield self.digest_path + yield self.alias class OCILayout(MirrorLayout): @@ -458,7 +491,11 @@ class OCILayout(MirrorLayout): super().__init__(os.path.join("blobs", digest.algorithm, digest.digest)) -def mirror_archive_paths(fetcher, per_package_ref, spec=None): +def default_mirror_layout( + fetcher: "spack.fetch_strategy.FetchStrategy", + per_package_ref: str, + spec: Optional["spack.spec.Spec"] = None, +) -> MirrorLayout: """Returns a ``MirrorReference`` object which keeps track of the relative storage path of the resource associated with the specified ``fetcher``.""" ext = None diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index f4b7c2b4c1..3fa4a86143 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -741,7 +741,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass raise ValueError(msg.format(self)) # init internal variables - self._stage = None + self._stage: Optional[StageComposite] = None self._fetcher = None self._tester: Optional["PackageTest"] = None @@ -1099,7 +1099,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass root=root_stage, resource=resource, name=self._resource_stage(resource), - mirror_paths=spack.mirror.mirror_archive_paths( + mirror_paths=spack.mirror.default_mirror_layout( resource.fetcher, os.path.join(self.name, pretty_resource_name) ), mirrors=spack.mirror.MirrorCollection(source=True).values(), @@ -1114,7 +1114,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass # Construct a mirror path (TODO: get this out of package.py) format_string = "{name}-{version}" pretty_name = self.spec.format_path(format_string) - mirror_paths = spack.mirror.mirror_archive_paths( + mirror_paths = spack.mirror.default_mirror_layout( fetcher, os.path.join(self.name, pretty_name), self.spec ) # Construct a path where the stage should build.. @@ -1180,7 +1180,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass return self._stage @stage.setter - def stage(self, stage): + def stage(self, stage: StageComposite): """Allow a stage object to be set to override the default.""" self._stage = stage diff --git a/lib/spack/spack/patch.py b/lib/spack/spack/patch.py index b2630f7c00..68f3f47bb2 100644 --- a/lib/spack/spack/patch.py +++ b/lib/spack/spack/patch.py @@ -326,7 +326,7 @@ class UrlPatch(Patch): name = "{0}-{1}".format(os.path.basename(self.url), fetch_digest[:7]) per_package_ref = os.path.join(self.owner.split(".")[-1], name) - mirror_ref = spack.mirror.mirror_archive_paths(fetcher, per_package_ref) + mirror_ref = spack.mirror.default_mirror_layout(fetcher, per_package_ref) self._stage = spack.stage.Stage( fetcher, name=f"{spack.stage.stage_prefix}patch-{fetch_digest}", diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py index 8956a4e933..752ac42e4e 100644 --- a/lib/spack/spack/stage.py +++ b/lib/spack/spack/stage.py @@ -364,36 +364,30 @@ class Stage(LockableStagingDir): """Create a stage object. Parameters: url_or_fetch_strategy - URL of the archive to be downloaded into this stage, OR - a valid FetchStrategy. + URL of the archive to be downloaded into this stage, OR a valid FetchStrategy. name - If a name is provided, then this stage is a named stage - and will persist between runs (or if you construct another - stage object later). If name is not provided, then this + If a name is provided, then this stage is a named stage and will persist between runs + (or if you construct another stage object later). If name is not provided, then this stage will be given a unique name automatically. mirror_paths - If provided, Stage will search Spack's mirrors for - this archive at each of the provided relative mirror paths - before using the default fetch strategy. + If provided, Stage will search Spack's mirrors for this archive at each of the + provided relative mirror paths before using the default fetch strategy. keep - By default, when used as a context manager, the Stage - is deleted on exit when no exceptions are raised. - Pass True to keep the stage intact even if no - exceptions are raised. + By default, when used as a context manager, the Stage is deleted on exit when no + exceptions are raised. Pass True to keep the stage intact even if no exceptions are + raised. path If provided, the stage path to use for associated builds. lock - True if the stage directory file lock is to be used, False - otherwise. + True if the stage directory file lock is to be used, False otherwise. search_fn - The search function that provides the fetch strategy - instance. + The search function that provides the fetch strategy instance. """ super().__init__(name, path, keep, lock) @@ -419,26 +413,27 @@ class Stage(LockableStagingDir): self.srcdir = None - self.mirror_paths = mirror_paths + self.mirror_layout = mirror_paths self.mirrors = list(mirrors) if mirrors else [] + # Allow users the disable both mirrors and download cache + self.default_fetcher_only = False @property def expected_archive_files(self): """Possible archive file paths.""" - paths = [] fnames = [] expanded = True if isinstance(self.default_fetcher, fs.URLFetchStrategy): expanded = self.default_fetcher.expand_archive fnames.append(url_util.default_download_filename(self.default_fetcher.url)) - if self.mirror_paths: - fnames.extend(os.path.basename(x) for x in self.mirror_paths) + if self.mirror_layout: + fnames.append(os.path.basename(self.mirror_layout.path)) - paths.extend(os.path.join(self.path, f) for f in fnames) + paths = [os.path.join(self.path, f) for f in fnames] if not expanded: - # If the download file is not compressed, the "archive" is a - # single file placed in Stage.source_path + # If the download file is not compressed, the "archive" is a single file placed in + # Stage.source_path paths.extend(os.path.join(self.source_path, f) for f in fnames) return paths @@ -471,14 +466,8 @@ class Stage(LockableStagingDir): """Returns the well-known source directory path.""" return os.path.join(self.path, _source_path_subdir) - def disable_mirrors(self): - """The Stage will not attempt to look for the associated fetcher - target in any of Spack's mirrors (including the local download cache). - """ - self.mirror_paths = None - def _generate_fetchers(self, mirror_only=False) -> Generator[fs.FetchStrategy, None, None]: - fetchers = [] + fetchers: List[fs.FetchStrategy] = [] if not mirror_only: fetchers.append(self.default_fetcher) @@ -495,27 +484,26 @@ class Stage(LockableStagingDir): # TODO: move mirror logic out of here and clean it up! # TODO: Or @alalazo may have some ideas about how to use a # TODO: CompositeFetchStrategy here. - if self.mirror_paths and self.mirrors: + if not self.default_fetcher_only and self.mirror_layout and self.mirrors: # Add URL strategies for all the mirrors with the digest # Insert fetchers in the order that the URLs are provided. fetchers[:0] = ( fs.from_url_scheme( - url_util.join(mirror.fetch_url, rel_path), + url_util.join(mirror.fetch_url, self.mirror_layout.path), checksum=digest, expand=expand, extension=extension, ) for mirror in self.mirrors - if not mirror.fetch_url.startswith("oci://") - for rel_path in self.mirror_paths + if not mirror.fetch_url.startswith("oci://") # no support for mirrors yet ) - if self.mirror_paths and self.default_fetcher.cachable: - fetchers[:0] = ( + if not self.default_fetcher_only and self.mirror_layout and self.default_fetcher.cachable: + fetchers.insert( + 0, spack.caches.FETCH_CACHE.fetcher( - rel_path, digest, expand=expand, extension=extension - ) - for rel_path in self.mirror_paths + self.mirror_layout.path, digest, expand=expand, extension=extension + ), ) yield from fetchers @@ -611,41 +599,42 @@ class Stage(LockableStagingDir): self.fetcher.check() def cache_local(self): - spack.caches.FETCH_CACHE.store(self.fetcher, self.mirror_paths.storage_path) + spack.caches.FETCH_CACHE.store(self.fetcher, self.mirror_layout.path) - def cache_mirror(self, mirror, stats): + def cache_mirror( + self, mirror: spack.caches.MirrorCache, stats: spack.mirror.MirrorStats + ) -> None: """Perform a fetch if the resource is not already cached Arguments: - mirror (spack.caches.MirrorCache): the mirror to cache this Stage's - resource in - stats (spack.mirror.MirrorStats): this is updated depending on whether the - caching operation succeeded or failed + mirror: the mirror to cache this Stage's resource in + stats: this is updated depending on whether the caching operation succeeded or failed """ if isinstance(self.default_fetcher, fs.BundleFetchStrategy): - # BundleFetchStrategy has no source to fetch. The associated - # fetcher does nothing but the associated stage may still exist. - # There is currently no method available on the fetcher to - # distinguish this ('cachable' refers to whether the fetcher - # refers to a resource with a fixed ID, which is not the same - # concept as whether there is anything to fetch at all) so we - # must examine the type of the fetcher. + # BundleFetchStrategy has no source to fetch. The associated fetcher does nothing but + # the associated stage may still exist. There is currently no method available on the + # fetcher to distinguish this ('cachable' refers to whether the fetcher refers to a + # resource with a fixed ID, which is not the same concept as whether there is anything + # to fetch at all) so we must examine the type of the fetcher. + return + + elif mirror.skip_unstable_versions and not fs.stable_target(self.default_fetcher): return - if mirror.skip_unstable_versions and not fs.stable_target(self.default_fetcher): + elif not self.mirror_layout: return - absolute_storage_path = os.path.join(mirror.root, self.mirror_paths.storage_path) + absolute_storage_path = os.path.join(mirror.root, self.mirror_layout.path) if os.path.exists(absolute_storage_path): stats.already_existed(absolute_storage_path) else: self.fetch() self.check() - mirror.store(self.fetcher, self.mirror_paths.storage_path) + mirror.store(self.fetcher, self.mirror_layout.path) stats.added(absolute_storage_path) - mirror.symlink(self.mirror_paths) + self.mirror_layout.make_alias(mirror.root) def expand_archive(self): """Changes to the stage directory and attempt to expand the downloaded @@ -653,9 +642,9 @@ class Stage(LockableStagingDir): downloaded.""" if not self.expanded: self.fetcher.expand() - tty.debug("Created stage in {0}".format(self.path)) + tty.debug(f"Created stage in {self.path}") else: - tty.debug("Already staged {0} in {1}".format(self.name, self.path)) + tty.debug(f"Already staged {self.name} in {self.path}") def restage(self): """Removes the expanded archive path if it exists, then re-expands diff --git a/lib/spack/spack/test/mirror.py b/lib/spack/spack/test/mirror.py index 2c389538ef..5609595f46 100644 --- a/lib/spack/spack/test/mirror.py +++ b/lib/spack/spack/test/mirror.py @@ -11,15 +11,16 @@ import pytest from llnl.util.symlink import resolve_link_target_relative_to_the_link import spack.caches +import spack.config import spack.fetch_strategy import spack.mirror import spack.patch import spack.repo +import spack.stage import spack.util.executable import spack.util.spack_json as sjson import spack.util.url as url_util from spack.spec import Spec -from spack.stage import Stage from spack.util.executable import which from spack.util.spack_yaml import SpackYAMLError @@ -51,7 +52,7 @@ def set_up_package(name, repository, url_attr): def check_mirror(): - with Stage("spack-mirror-test") as stage: + with spack.stage.Stage("spack-mirror-test") as stage: mirror_root = os.path.join(stage.path, "test-mirror") # register mirror with spack config mirrors = {"spack-mirror-test": url_util.path_to_file_url(mirror_root)} @@ -66,8 +67,8 @@ def check_mirror(): for spec in specs: fetcher = spec.package.fetcher per_package_ref = os.path.join(spec.name, "-".join([spec.name, str(spec.version)])) - mirror_paths = spack.mirror.mirror_archive_paths(fetcher, per_package_ref) - expected_path = os.path.join(mirror_root, mirror_paths.storage_path) + mirror_layout = spack.mirror.default_mirror_layout(fetcher, per_package_ref) + expected_path = os.path.join(mirror_root, mirror_layout.path) assert os.path.exists(expected_path) # Now try to fetch each package. @@ -203,13 +204,11 @@ def test_invalid_json_mirror_collection(invalid_json, error_message): def test_mirror_archive_paths_no_version(mock_packages, mock_archive): spec = Spec("trivial-install-test-package@=nonexistingversion").concretized() fetcher = spack.fetch_strategy.URLFetchStrategy(url=mock_archive.url) - spack.mirror.mirror_archive_paths(fetcher, "per-package-ref", spec) + spack.mirror.default_mirror_layout(fetcher, "per-package-ref", spec) def test_mirror_with_url_patches(mock_packages, monkeypatch): - spec = Spec("patch-several-dependencies") - spec.concretize() - + spec = Spec("patch-several-dependencies").concretized() files_cached_in_mirror = set() def record_store(_class, fetcher, relative_dst, cosmetic_path=None): @@ -228,30 +227,25 @@ def test_mirror_with_url_patches(mock_packages, monkeypatch): def successful_apply(*args, **kwargs): pass - def successful_symlink(*args, **kwargs): + def successful_make_alias(*args, **kwargs): pass - with Stage("spack-mirror-test") as stage: + with spack.stage.Stage("spack-mirror-test") as stage: mirror_root = os.path.join(stage.path, "test-mirror") monkeypatch.setattr(spack.fetch_strategy.URLFetchStrategy, "fetch", successful_fetch) monkeypatch.setattr(spack.fetch_strategy.URLFetchStrategy, "expand", successful_expand) monkeypatch.setattr(spack.patch, "apply_patch", successful_apply) monkeypatch.setattr(spack.caches.MirrorCache, "store", record_store) - monkeypatch.setattr(spack.caches.MirrorCache, "symlink", successful_symlink) + monkeypatch.setattr(spack.mirror.DefaultLayout, "make_alias", successful_make_alias) with spack.config.override("config:checksum", False): spack.mirror.create(mirror_root, list(spec.traverse())) - assert not ( - set( - [ - "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234", - "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.gz", - ] - ) - - files_cached_in_mirror - ) + assert { + "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234", + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.gz", + }.issubset(files_cached_in_mirror) class MockFetcher: @@ -266,23 +260,21 @@ class MockFetcher: @pytest.mark.regression("14067") -def test_mirror_cache_symlinks(tmpdir): +def test_mirror_layout_make_alias(tmpdir): """Confirm that the cosmetic symlink created in the mirror cache (which may be relative) targets the storage path correctly. """ - cosmetic_path = os.path.join("zlib", "zlib-1.2.11.tar.gz") - global_path = os.path.join("_source-cache", "archive", "c3", "c3e5.tar.gz") - cache = spack.caches.MirrorCache(str(tmpdir), False) - reference = spack.mirror.DefaultLayout(cosmetic_path, global_path) + alias = os.path.join("zlib", "zlib-1.2.11.tar.gz") + path = os.path.join("_source-cache", "archive", "c3", "c3e5.tar.gz") + cache = spack.caches.MirrorCache(root=str(tmpdir), skip_unstable_versions=False) + layout = spack.mirror.DefaultLayout(alias, path) - cache.store(MockFetcher(), reference.storage_path) - cache.symlink(reference) + cache.store(MockFetcher(), layout.path) + layout.make_alias(cache.root) - link_target = resolve_link_target_relative_to_the_link( - os.path.join(cache.root, reference.cosmetic_path) - ) + link_target = resolve_link_target_relative_to_the_link(os.path.join(cache.root, layout.alias)) assert os.path.exists(link_target) - assert os.path.normpath(link_target) == os.path.join(cache.root, reference.storage_path) + assert os.path.normpath(link_target) == os.path.join(cache.root, layout.path) @pytest.mark.regression("31627") |