Fetching from URLs falls back to mirrors if they exist (#13881)
Users can now list mirrors of the main url in packages. - [x] Instead of just a single `url` attribute, users can provide a list (`urls`) in the package, and these will be tried by in order by the fetch strategy. - [x] To handle one of the most common mirror cases, define a `GNUMirrorPackage` mixin to handle all the standard GNU mirrors. GNU packages can set `gnu_mirror_path` to define the path within a mirror, and the mixin handles setting up all the requisite GNU mirror URLs. - [x] update all GNU packages in `builtin` to use the `GNUMirrorPackage` mixin.
This commit is contained in:

committed by
Todd Gamblin

parent
1b93320848
commit
497fddfcb9
@@ -553,6 +553,34 @@ version. This is useful for packages that have an easy to extrapolate URL, but
|
||||
keep changing their URL format every few releases. With this method, you only
|
||||
need to specify the ``url`` when the URL changes.
|
||||
|
||||
"""""""""""""""""""""""
|
||||
Mirrors of the main URL
|
||||
"""""""""""""""""""""""
|
||||
|
||||
Spack supports listing mirrors of the main URL in a package by defining
|
||||
the ``urls`` attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(Package):
|
||||
|
||||
urls = [
|
||||
'http://example.com/foo-1.0.tar.gz',
|
||||
'http://mirror.com/foo-1.0.tar.gz'
|
||||
]
|
||||
|
||||
instead of just a single ``url``. This attribute is a list of possible URLs that
|
||||
will be tried in order when fetching packages. Notice that either one of ``url``
|
||||
or ``urls`` can be present in a package, but not both at the same time.
|
||||
|
||||
A well-known case of packages that can be fetched from multiple mirrors is that
|
||||
of GNU. For that, Spack goes a step further and defines a mixin class that
|
||||
takes care of all of the plumbing and requires packagers to just define a proper
|
||||
``gnu_mirror_path`` attribute:
|
||||
|
||||
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/autoconf/package.py
|
||||
:lines: 9-18
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Skipping the expand step
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
37
lib/spack/spack/build_systems/gnu.py
Normal file
37
lib/spack/spack/build_systems/gnu.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright 2013-2019 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)
|
||||
|
||||
import os.path
|
||||
|
||||
import spack.package
|
||||
|
||||
|
||||
class GNUMirrorPackage(spack.package.PackageBase):
|
||||
"""Mixin that takes care of setting url and mirrors for GNU packages."""
|
||||
#: Path of the package in a GNU mirror
|
||||
gnu_mirror_path = None
|
||||
|
||||
#: List of GNU mirrors used by Spack
|
||||
base_mirrors = [
|
||||
'https://ftp.gnu.org/gnu',
|
||||
'https://ftpmirror.gnu.org/',
|
||||
# Fall back to http if https didn't work (for instance because
|
||||
# Spack is bootstrapping curl)
|
||||
'http://ftpmirror.gnu.org/'
|
||||
]
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
self._ensure_gnu_mirror_path_is_set_or_raise()
|
||||
return [
|
||||
os.path.join(m, self.gnu_mirror_path) for m in self.base_mirrors
|
||||
]
|
||||
|
||||
def _ensure_gnu_mirror_path_is_set_or_raise(self):
|
||||
if self.gnu_mirror_path is None:
|
||||
cls_name = type(self).__name__
|
||||
msg = ('{0} must define a `gnu_mirror_path` attribute'
|
||||
' [none defined]')
|
||||
raise AttributeError(msg.format(cls_name))
|
@@ -135,7 +135,7 @@ def url_list(args):
|
||||
|
||||
# Gather set of URLs from all packages
|
||||
for pkg in spack.repo.path.all_packages():
|
||||
url = getattr(pkg.__class__, 'url', None)
|
||||
url = getattr(pkg, 'url', None)
|
||||
urls = url_list_parsing(args, urls, url, pkg)
|
||||
|
||||
for params in pkg.versions.values():
|
||||
@@ -174,7 +174,7 @@ def url_summary(args):
|
||||
for pkg in spack.repo.path.all_packages():
|
||||
urls = set()
|
||||
|
||||
url = getattr(pkg.__class__, 'url', None)
|
||||
url = getattr(pkg, 'url', None)
|
||||
if url:
|
||||
urls.add(url)
|
||||
|
||||
|
@@ -22,33 +22,30 @@
|
||||
* archive()
|
||||
Archive a source directory, e.g. for creating a mirror.
|
||||
"""
|
||||
import copy
|
||||
import functools
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import re
|
||||
import shutil
|
||||
import copy
|
||||
import sys
|
||||
import xml.etree.ElementTree
|
||||
from functools import wraps
|
||||
from six import string_types, with_metaclass
|
||||
import six.moves.urllib.parse as urllib_parse
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import (
|
||||
working_dir, mkdirp, temp_rename, temp_cwd, get_single_file)
|
||||
|
||||
import six
|
||||
import six.moves.urllib.parse as urllib_parse
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.util.crypto as crypto
|
||||
import spack.util.pattern as pattern
|
||||
import spack.util.web as web_util
|
||||
import spack.util.url as url_util
|
||||
|
||||
import spack.util.web as web_util
|
||||
from llnl.util.filesystem import (
|
||||
working_dir, mkdirp, temp_rename, temp_cwd, get_single_file)
|
||||
from spack.util.compression import decompressor_for, extension
|
||||
from spack.util.executable import which
|
||||
from spack.util.string import comma_and, quote
|
||||
from spack.version import Version, ver
|
||||
from spack.util.compression import decompressor_for, extension
|
||||
|
||||
|
||||
#: List of all fetch strategies, created by FetchStrategy metaclass.
|
||||
all_strategies = []
|
||||
@@ -69,7 +66,7 @@ def _needs_stage(fun):
|
||||
"""Many methods on fetch strategies require a stage to be set
|
||||
using set_stage(). This decorator adds a check for self.stage."""
|
||||
|
||||
@wraps(fun)
|
||||
@functools.wraps(fun)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if not self.stage:
|
||||
raise NoStageError(fun)
|
||||
@@ -85,18 +82,14 @@ def _ensure_one_stage_entry(stage_path):
|
||||
return os.path.join(stage_path, stage_entries[0])
|
||||
|
||||
|
||||
class FSMeta(type):
|
||||
"""This metaclass registers all fetch strategies in a list."""
|
||||
def __init__(cls, name, bases, dict):
|
||||
type.__init__(cls, name, bases, dict)
|
||||
if cls.enabled:
|
||||
all_strategies.append(cls)
|
||||
def fetcher(cls):
|
||||
"""Decorator used to register fetch strategies."""
|
||||
all_strategies.append(cls)
|
||||
return cls
|
||||
|
||||
|
||||
class FetchStrategy(with_metaclass(FSMeta, object)):
|
||||
class FetchStrategy(object):
|
||||
"""Superclass of all fetch strategies."""
|
||||
enabled = False # Non-abstract subclasses should be enabled.
|
||||
|
||||
#: The URL attribute must be specified either at the package class
|
||||
#: level, or as a keyword argument to ``version()``. It is used to
|
||||
#: distinguish fetchers for different versions in the package DSL.
|
||||
@@ -113,16 +106,7 @@ def __init__(self, **kwargs):
|
||||
self.stage = None
|
||||
# Enable or disable caching for this strategy based on
|
||||
# 'no_cache' option from version directive.
|
||||
self._cache_enabled = not kwargs.pop('no_cache', False)
|
||||
|
||||
def set_stage(self, stage):
|
||||
"""This is called by Stage before any of the fetching
|
||||
methods are called on the stage."""
|
||||
self.stage = stage
|
||||
|
||||
@property
|
||||
def cache_enabled(self):
|
||||
return self._cache_enabled
|
||||
self.cache_enabled = not kwargs.pop('no_cache', False)
|
||||
|
||||
# Subclasses need to implement these methods
|
||||
def fetch(self):
|
||||
@@ -186,13 +170,18 @@ def mirror_id(self):
|
||||
def __str__(self): # Should be human readable URL.
|
||||
return "FetchStrategy.__str___"
|
||||
|
||||
# This method is used to match fetch strategies to version()
|
||||
# arguments in packages.
|
||||
@classmethod
|
||||
def matches(cls, args):
|
||||
"""Predicate that matches fetch strategies to arguments of
|
||||
the version directive.
|
||||
|
||||
Args:
|
||||
args: arguments of the version directive
|
||||
"""
|
||||
return cls.url_attr in args
|
||||
|
||||
|
||||
@fetcher
|
||||
class BundleFetchStrategy(FetchStrategy):
|
||||
"""
|
||||
Fetch strategy associated with bundle, or no-code, packages.
|
||||
@@ -204,9 +193,6 @@ class BundleFetchStrategy(FetchStrategy):
|
||||
TODO: Remove this class by refactoring resource handling and the link
|
||||
between composite stages and composite fetch strategies (see #11981).
|
||||
"""
|
||||
#: This is a concrete fetch strategy for no-code packages.
|
||||
enabled = True
|
||||
|
||||
#: There is no associated URL keyword in ``version()`` for no-code
|
||||
#: packages but this property is required for some strategy-related
|
||||
#: functions (e.g., check_pkg_attributes).
|
||||
@@ -236,7 +222,6 @@ class FetchStrategyComposite(object):
|
||||
Implements the GoF composite pattern.
|
||||
"""
|
||||
matches = FetchStrategy.matches
|
||||
set_stage = FetchStrategy.set_stage
|
||||
|
||||
def source_id(self):
|
||||
component_ids = tuple(i.source_id() for i in self)
|
||||
@@ -244,13 +229,13 @@ def source_id(self):
|
||||
return component_ids
|
||||
|
||||
|
||||
@fetcher
|
||||
class URLFetchStrategy(FetchStrategy):
|
||||
"""URLFetchStrategy pulls source code from a URL for an archive, check the
|
||||
archive against a checksum, and decompresses the archive.
|
||||
|
||||
The destination for the resulting file(s) is the standard stage path.
|
||||
"""
|
||||
FetchStrategy that pulls source code from a URL for an archive, check the
|
||||
archive against a checksum, and decompresses the archive. The destination
|
||||
for the resulting file(s) is the standard stage source path.
|
||||
"""
|
||||
enabled = True
|
||||
url_attr = 'url'
|
||||
|
||||
# these are checksum types. The generic 'checksum' is deprecated for
|
||||
@@ -262,6 +247,7 @@ def __init__(self, url=None, checksum=None, **kwargs):
|
||||
|
||||
# Prefer values in kwargs to the positionals.
|
||||
self.url = kwargs.get('url', url)
|
||||
self.mirrors = kwargs.get('mirrors', [])
|
||||
|
||||
# digest can be set as the first argument, or from an explicit
|
||||
# kwarg by the hash name.
|
||||
@@ -297,20 +283,36 @@ def mirror_id(self):
|
||||
return os.path.sep.join(
|
||||
['archive', self.digest[:2], self.digest])
|
||||
|
||||
@property
|
||||
def candidate_urls(self):
|
||||
return [self.url] + (self.mirrors or [])
|
||||
|
||||
@_needs_stage
|
||||
def fetch(self):
|
||||
if self.archive_file:
|
||||
tty.msg("Already downloaded %s" % self.archive_file)
|
||||
return
|
||||
|
||||
for url in self.candidate_urls:
|
||||
try:
|
||||
partial_file, save_file = self._fetch_from_url(url)
|
||||
if save_file:
|
||||
os.rename(partial_file, save_file)
|
||||
break
|
||||
except FetchError as e:
|
||||
tty.msg(str(e))
|
||||
pass
|
||||
|
||||
if not self.archive_file:
|
||||
raise FailedDownloadError(self.url)
|
||||
|
||||
def _fetch_from_url(self, url):
|
||||
save_file = None
|
||||
partial_file = None
|
||||
if self.stage.save_filename:
|
||||
save_file = self.stage.save_filename
|
||||
partial_file = self.stage.save_filename + '.part'
|
||||
|
||||
tty.msg("Fetching %s" % self.url)
|
||||
|
||||
tty.msg("Fetching %s" % url)
|
||||
if partial_file:
|
||||
save_args = ['-C',
|
||||
'-', # continue partial downloads
|
||||
@@ -324,7 +326,9 @@ def fetch(self):
|
||||
'-D',
|
||||
'-', # print out HTML headers
|
||||
'-L', # resolve 3xx redirects
|
||||
self.url,
|
||||
# Timeout if can't establish a connection after 10 sec.
|
||||
'--connect-timeout', '10',
|
||||
url,
|
||||
]
|
||||
|
||||
if not spack.config.get('config:verify_ssl'):
|
||||
@@ -380,12 +384,7 @@ def fetch(self):
|
||||
flags=re.IGNORECASE)
|
||||
if content_types and 'text/html' in content_types[-1]:
|
||||
warn_content_type_mismatch(self.archive_file or "the archive")
|
||||
|
||||
if save_file:
|
||||
os.rename(partial_file, save_file)
|
||||
|
||||
if not self.archive_file:
|
||||
raise FailedDownloadError(self.url)
|
||||
return partial_file, save_file
|
||||
|
||||
@property
|
||||
@_needs_stage
|
||||
@@ -395,7 +394,7 @@ def archive_file(self):
|
||||
|
||||
@property
|
||||
def cachable(self):
|
||||
return self._cache_enabled and bool(self.digest)
|
||||
return self.cache_enabled and bool(self.digest)
|
||||
|
||||
@_needs_stage
|
||||
def expand(self):
|
||||
@@ -522,6 +521,7 @@ def __str__(self):
|
||||
return "[no url]"
|
||||
|
||||
|
||||
@fetcher
|
||||
class CacheURLFetchStrategy(URLFetchStrategy):
|
||||
"""The resource associated with a cache URL may be out of date."""
|
||||
|
||||
@@ -597,7 +597,7 @@ def archive(self, destination, **kwargs):
|
||||
|
||||
patterns = kwargs.get('exclude', None)
|
||||
if patterns is not None:
|
||||
if isinstance(patterns, string_types):
|
||||
if isinstance(patterns, six.string_types):
|
||||
patterns = [patterns]
|
||||
for p in patterns:
|
||||
tar.add_default_arg('--exclude=%s' % p)
|
||||
@@ -621,6 +621,7 @@ def __repr__(self):
|
||||
return "%s<%s>" % (self.__class__, self.url)
|
||||
|
||||
|
||||
@fetcher
|
||||
class GoFetchStrategy(VCSFetchStrategy):
|
||||
"""Fetch strategy that employs the `go get` infrastructure.
|
||||
|
||||
@@ -634,7 +635,6 @@ class GoFetchStrategy(VCSFetchStrategy):
|
||||
The fetched source will be moved to the standard stage sourcepath directory
|
||||
during the expand step.
|
||||
"""
|
||||
enabled = True
|
||||
url_attr = 'go'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -691,6 +691,7 @@ def __str__(self):
|
||||
return "[go] %s" % self.url
|
||||
|
||||
|
||||
@fetcher
|
||||
class GitFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
"""
|
||||
@@ -712,7 +713,6 @@ class GitFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
Repositories are cloned into the standard stage source path directory.
|
||||
"""
|
||||
enabled = True
|
||||
url_attr = 'git'
|
||||
optional_attrs = ['tag', 'branch', 'commit', 'submodules', 'get_full_repo']
|
||||
|
||||
@@ -746,7 +746,7 @@ def git(self):
|
||||
|
||||
@property
|
||||
def cachable(self):
|
||||
return self._cache_enabled and bool(self.commit or self.tag)
|
||||
return self.cache_enabled and bool(self.commit or self.tag)
|
||||
|
||||
def source_id(self):
|
||||
return self.commit or self.tag
|
||||
@@ -892,6 +892,7 @@ def __str__(self):
|
||||
return '[git] {0}'.format(self._repo_info())
|
||||
|
||||
|
||||
@fetcher
|
||||
class SvnFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
"""Fetch strategy that gets source code from a subversion repository.
|
||||
@@ -906,7 +907,6 @@ class SvnFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
Repositories are checked out into the standard stage source path directory.
|
||||
"""
|
||||
enabled = True
|
||||
url_attr = 'svn'
|
||||
optional_attrs = ['revision']
|
||||
|
||||
@@ -929,7 +929,7 @@ def svn(self):
|
||||
|
||||
@property
|
||||
def cachable(self):
|
||||
return self._cache_enabled and bool(self.revision)
|
||||
return self.cache_enabled and bool(self.revision)
|
||||
|
||||
def source_id(self):
|
||||
return self.revision
|
||||
@@ -991,6 +991,7 @@ def __str__(self):
|
||||
return "[svn] %s" % self.url
|
||||
|
||||
|
||||
@fetcher
|
||||
class HgFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
"""
|
||||
@@ -1013,7 +1014,6 @@ class HgFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
Repositories are cloned into the standard stage source path directory.
|
||||
"""
|
||||
enabled = True
|
||||
url_attr = 'hg'
|
||||
optional_attrs = ['revision']
|
||||
|
||||
@@ -1043,7 +1043,7 @@ def hg(self):
|
||||
|
||||
@property
|
||||
def cachable(self):
|
||||
return self._cache_enabled and bool(self.revision)
|
||||
return self.cache_enabled and bool(self.revision)
|
||||
|
||||
def source_id(self):
|
||||
return self.revision
|
||||
@@ -1108,9 +1108,9 @@ def __str__(self):
|
||||
return "[hg] %s" % self.url
|
||||
|
||||
|
||||
@fetcher
|
||||
class S3FetchStrategy(URLFetchStrategy):
|
||||
"""FetchStrategy that pulls from an S3 bucket."""
|
||||
enabled = True
|
||||
url_attr = 's3'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -1244,10 +1244,15 @@ def _from_merged_attrs(fetcher, pkg, version):
|
||||
"""Create a fetcher from merged package and version attributes."""
|
||||
if fetcher.url_attr == 'url':
|
||||
url = pkg.url_for_version(version)
|
||||
# TODO: refactor this logic into its own method or function
|
||||
# TODO: to avoid duplication
|
||||
mirrors = [spack.url.substitute_version(u, version)
|
||||
for u in getattr(pkg, 'urls', [])]
|
||||
attrs = {fetcher.url_attr: url, 'mirrors': mirrors}
|
||||
else:
|
||||
url = getattr(pkg, fetcher.url_attr)
|
||||
attrs = {fetcher.url_attr: url}
|
||||
|
||||
attrs = {fetcher.url_attr: url}
|
||||
attrs.update(pkg.versions[version])
|
||||
return fetcher(**attrs)
|
||||
|
||||
|
@@ -510,8 +510,8 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
maintainers = []
|
||||
|
||||
#: List of attributes to be excluded from a package's hash.
|
||||
metadata_attrs = ['homepage', 'url', 'list_url', 'extendable', 'parallel',
|
||||
'make_jobs']
|
||||
metadata_attrs = ['homepage', 'url', 'urls', 'list_url', 'extendable',
|
||||
'parallel', 'make_jobs']
|
||||
|
||||
def __init__(self, spec):
|
||||
# this determines how the package should be built.
|
||||
@@ -524,6 +524,12 @@ def __init__(self, spec):
|
||||
# a binary cache.
|
||||
self.installed_from_binary_cache = False
|
||||
|
||||
# Ensure that only one of these two attributes are present
|
||||
if getattr(self, 'url', None) and getattr(self, 'urls', None):
|
||||
msg = "a package can have either a 'url' or a 'urls' attribute"
|
||||
msg += " [package '{0.name}' defines both]"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
# Set a default list URL (place to find available versions)
|
||||
if not hasattr(self, 'list_url'):
|
||||
self.list_url = None
|
||||
@@ -750,7 +756,9 @@ def url_for_version(self, version):
|
||||
return version_urls[version]
|
||||
|
||||
# If no specific URL, use the default, class-level URL
|
||||
default_url = getattr(self, 'url', None)
|
||||
url = getattr(self, 'url', None)
|
||||
urls = getattr(self, 'urls', [None])
|
||||
default_url = url or urls.pop(0)
|
||||
|
||||
# if no exact match AND no class-level default, use the nearest URL
|
||||
if not default_url:
|
||||
|
@@ -30,6 +30,7 @@
|
||||
from spack.build_systems.intel import IntelPackage
|
||||
from spack.build_systems.meson import MesonPackage
|
||||
from spack.build_systems.sip import SIPPackage
|
||||
from spack.build_systems.gnu import GNUMirrorPackage
|
||||
|
||||
from spack.mixins import filter_compiler_wrappers
|
||||
|
||||
|
@@ -271,7 +271,7 @@ def __init__(
|
||||
else:
|
||||
raise ValueError(
|
||||
"Can't construct Stage without url or fetch strategy")
|
||||
self.fetcher.set_stage(self)
|
||||
self.fetcher.stage = self
|
||||
# self.fetcher can change with mirrors.
|
||||
self.default_fetcher = self.fetcher
|
||||
self.search_fn = search_fn
|
||||
@@ -458,7 +458,7 @@ def generate_fetchers():
|
||||
|
||||
for fetcher in generate_fetchers():
|
||||
try:
|
||||
fetcher.set_stage(self)
|
||||
fetcher.stage = self
|
||||
self.fetcher = fetcher
|
||||
self.fetcher.fetch()
|
||||
break
|
||||
|
@@ -240,9 +240,6 @@ def fetcher(self, target_path, digest, **kwargs):
|
||||
return MockCacheFetcher()
|
||||
|
||||
class MockCacheFetcher(object):
|
||||
def set_stage(self, stage):
|
||||
pass
|
||||
|
||||
def fetch(self):
|
||||
raise FetchError('Mock cache always fails for tests')
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import collections
|
||||
import os
|
||||
import pytest
|
||||
|
||||
@@ -10,8 +11,7 @@
|
||||
|
||||
import spack.repo
|
||||
import spack.config
|
||||
from spack.fetch_strategy import FailedDownloadError
|
||||
from spack.fetch_strategy import from_list_url, URLFetchStrategy
|
||||
import spack.fetch_strategy as fs
|
||||
from spack.spec import Spec
|
||||
from spack.stage import Stage
|
||||
from spack.version import ver
|
||||
@@ -23,10 +23,30 @@ def checksum_type(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pkg_factory():
|
||||
Pkg = collections.namedtuple(
|
||||
'Pkg', ['url_for_version', 'urls', 'url', 'versions']
|
||||
)
|
||||
|
||||
def factory(url, urls):
|
||||
|
||||
def fn(v):
|
||||
main_url = url or urls.pop(0)
|
||||
return spack.url.substitute_version(main_url, v)
|
||||
|
||||
return Pkg(
|
||||
url_for_version=fn, url=url, urls=urls,
|
||||
versions=collections.defaultdict(dict)
|
||||
)
|
||||
|
||||
return factory
|
||||
|
||||
|
||||
def test_urlfetchstrategy_sans_url():
|
||||
"""Ensure constructor with no URL fails."""
|
||||
with pytest.raises(ValueError):
|
||||
with URLFetchStrategy(None):
|
||||
with fs.URLFetchStrategy(None):
|
||||
pass
|
||||
|
||||
|
||||
@@ -34,8 +54,8 @@ def test_urlfetchstrategy_bad_url(tmpdir):
|
||||
"""Ensure fetch with bad URL fails as expected."""
|
||||
testpath = str(tmpdir)
|
||||
|
||||
with pytest.raises(FailedDownloadError):
|
||||
fetcher = URLFetchStrategy(url='file:///does-not-exist')
|
||||
with pytest.raises(fs.FailedDownloadError):
|
||||
fetcher = fs.URLFetchStrategy(url='file:///does-not-exist')
|
||||
assert fetcher is not None
|
||||
|
||||
with Stage(fetcher, path=testpath) as stage:
|
||||
@@ -106,8 +126,8 @@ def test_from_list_url(mock_packages, config, spec, url, digest):
|
||||
"""
|
||||
specification = Spec(spec).concretized()
|
||||
pkg = spack.repo.get(specification)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
fetch_strategy = fs.from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, fs.URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == url
|
||||
assert fetch_strategy.digest == digest
|
||||
|
||||
@@ -118,8 +138,8 @@ def test_from_list_url_unspecified(mock_packages, config):
|
||||
|
||||
spec = Spec('url-list-test @2.0.0').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
fetch_strategy = fs.from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, fs.URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-2.0.0.tar.gz'
|
||||
assert fetch_strategy.digest is None
|
||||
|
||||
@@ -128,7 +148,7 @@ def test_nosource_from_list_url(mock_packages, config):
|
||||
"""This test confirms BundlePackages do not have list url."""
|
||||
pkg = spack.repo.get('nosource')
|
||||
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
fetch_strategy = fs.from_list_url(pkg)
|
||||
assert fetch_strategy is None
|
||||
|
||||
|
||||
@@ -148,9 +168,26 @@ def test_url_extra_fetch(tmpdir, mock_archive):
|
||||
"""Ensure a fetch after downloading is effectively a no-op."""
|
||||
testpath = str(tmpdir)
|
||||
|
||||
fetcher = URLFetchStrategy(mock_archive.url)
|
||||
fetcher = fs.URLFetchStrategy(mock_archive.url)
|
||||
with Stage(fetcher, path=testpath) as stage:
|
||||
assert fetcher.archive_file is None
|
||||
stage.fetch()
|
||||
assert fetcher.archive_file is not None
|
||||
fetcher.fetch()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url,urls,version,expected', [
|
||||
(None,
|
||||
['https://ftpmirror.gnu.org/autoconf/autoconf-2.69.tar.gz',
|
||||
'https://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz'],
|
||||
'2.62',
|
||||
['https://ftpmirror.gnu.org/autoconf/autoconf-2.62.tar.gz',
|
||||
'https://ftp.gnu.org/gnu/autoconf/autoconf-2.62.tar.gz'])
|
||||
])
|
||||
def test_candidate_urls(pkg_factory, url, urls, version, expected):
|
||||
"""Tests that candidate urls include mirrors and that they go through
|
||||
pattern matching and substitution for versions.
|
||||
"""
|
||||
pkg = pkg_factory(url, urls)
|
||||
f = fs._from_merged_attrs(fs.URLFetchStrategy, pkg, version)
|
||||
assert f.candidate_urls == expected
|
||||
|
Reference in New Issue
Block a user