Clarify manual download required if unable to fetch package (#18242)
Clarify manual download required if unable to fetch (from mirror(s)); support (and tests) for package-specific download instructions
This commit is contained in:
parent
6b30cd18d6
commit
88749de5c9
4 changed files with 87 additions and 11 deletions
|
@ -577,7 +577,8 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||||
archive_files = []
|
archive_files = []
|
||||||
|
|
||||||
#: Boolean. Set to ``True`` for packages that require a manual download.
|
#: Boolean. Set to ``True`` for packages that require a manual download.
|
||||||
#: This is currently only used by package sanity tests.
|
#: This is currently used by package sanity tests and generation of a
|
||||||
|
#: more meaningful fetch failure error.
|
||||||
manual_download = False
|
manual_download = False
|
||||||
|
|
||||||
#: Set of additional options used when fetching package versions.
|
#: Set of additional options used when fetching package versions.
|
||||||
|
@ -1210,6 +1211,20 @@ def remove_prefix(self):
|
||||||
"""
|
"""
|
||||||
spack.store.layout.remove_install_directory(self.spec)
|
spack.store.layout.remove_install_directory(self.spec)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def download_instr(self):
|
||||||
|
"""
|
||||||
|
Defines the default manual download instructions. Packages can
|
||||||
|
override the property to provide more information.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(str): default manual download instructions
|
||||||
|
"""
|
||||||
|
required = ('Manual download is required for {0}. '
|
||||||
|
.format(self.spec.name) if self.manual_download else '')
|
||||||
|
return ('{0}Refer to {1} for download instructions.'
|
||||||
|
.format(required, self.spec.package.homepage))
|
||||||
|
|
||||||
def do_fetch(self, mirror_only=False):
|
def do_fetch(self, mirror_only=False):
|
||||||
"""
|
"""
|
||||||
Creates a stage directory and downloads the tarball for this package.
|
Creates a stage directory and downloads the tarball for this package.
|
||||||
|
@ -1244,7 +1259,8 @@ def do_fetch(self, mirror_only=False):
|
||||||
self.spec.format('{name}{@version}'), ck_msg)
|
self.spec.format('{name}{@version}'), ck_msg)
|
||||||
|
|
||||||
self.stage.create()
|
self.stage.create()
|
||||||
self.stage.fetch(mirror_only)
|
err_msg = None if not self.manual_download else self.download_instr
|
||||||
|
self.stage.fetch(mirror_only, err_msg=err_msg)
|
||||||
self._fetch_time = time.time() - start_time
|
self._fetch_time = time.time() - start_time
|
||||||
|
|
||||||
if checksum and self.version in self.versions:
|
if checksum and self.version in self.versions:
|
||||||
|
|
|
@ -400,8 +400,14 @@ def source_path(self):
|
||||||
"""Returns the well-known source directory path."""
|
"""Returns the well-known source directory path."""
|
||||||
return os.path.join(self.path, _source_path_subdir)
|
return os.path.join(self.path, _source_path_subdir)
|
||||||
|
|
||||||
def fetch(self, mirror_only=False):
|
def fetch(self, mirror_only=False, err_msg=None):
|
||||||
"""Downloads an archive or checks out code from a repository."""
|
"""Retrieves the code or archive
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mirror_only (bool): only fetch from a mirror
|
||||||
|
err_msg (str or None): the error message to display if all fetchers
|
||||||
|
fail or ``None`` for the default fetch failure message
|
||||||
|
"""
|
||||||
fetchers = []
|
fetchers = []
|
||||||
if not mirror_only:
|
if not mirror_only:
|
||||||
fetchers.append(self.default_fetcher)
|
fetchers.append(self.default_fetcher)
|
||||||
|
@ -480,9 +486,8 @@ def print_errors(errors):
|
||||||
else:
|
else:
|
||||||
print_errors(errors)
|
print_errors(errors)
|
||||||
|
|
||||||
err_msg = 'All fetchers failed for {0}'.format(self.name)
|
|
||||||
self.fetcher = self.default_fetcher
|
self.fetcher = self.default_fetcher
|
||||||
raise fs.FetchError(err_msg, None)
|
raise fs.FetchError(err_msg or 'All fetchers failed', None)
|
||||||
|
|
||||||
print_errors(errors)
|
print_errors(errors)
|
||||||
|
|
||||||
|
|
|
@ -560,3 +560,52 @@ def test_macho_make_paths():
|
||||||
'/Users/Shared/spack/pkgB/libB.dylib',
|
'/Users/Shared/spack/pkgB/libB.dylib',
|
||||||
'/usr/local/lib/libloco.dylib':
|
'/usr/local/lib/libloco.dylib':
|
||||||
'/usr/local/lib/libloco.dylib'}
|
'/usr/local/lib/libloco.dylib'}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def mock_download():
|
||||||
|
"""Mock a failing download strategy."""
|
||||||
|
class FailedDownloadStrategy(spack.fetch_strategy.FetchStrategy):
|
||||||
|
def mirror_id(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch(self):
|
||||||
|
raise spack.fetch_strategy.FailedDownloadError(
|
||||||
|
"<non-existent URL>", "This FetchStrategy always fails")
|
||||||
|
|
||||||
|
fetcher = FetchStrategyComposite()
|
||||||
|
fetcher.append(FailedDownloadStrategy())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fake_fn(self):
|
||||||
|
return fetcher
|
||||||
|
|
||||||
|
orig_fn = spack.package.PackageBase.fetcher
|
||||||
|
spack.package.PackageBase.fetcher = fake_fn
|
||||||
|
yield
|
||||||
|
spack.package.PackageBase.fetcher = orig_fn
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("manual,instr", [(False, False), (False, True),
|
||||||
|
(True, False), (True, True)])
|
||||||
|
@pytest.mark.disable_clean_stage_check
|
||||||
|
def test_manual_download(install_mockery, mock_download, monkeypatch, manual,
|
||||||
|
instr):
|
||||||
|
"""
|
||||||
|
Ensure expected fetcher fail message based on manual download and instr.
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def _instr(pkg):
|
||||||
|
return 'Download instructions for {0}'.format(pkg.spec.name)
|
||||||
|
|
||||||
|
spec = Spec('a').concretized()
|
||||||
|
pkg = spec.package
|
||||||
|
|
||||||
|
pkg.manual_download = manual
|
||||||
|
if instr:
|
||||||
|
monkeypatch.setattr(spack.package.PackageBase, 'download_instr',
|
||||||
|
_instr)
|
||||||
|
|
||||||
|
expected = pkg.download_instr if manual else 'All fetchers failed'
|
||||||
|
with pytest.raises(spack.fetch_strategy.FetchError, match=expected):
|
||||||
|
pkg.do_fetch()
|
||||||
|
|
|
@ -529,15 +529,21 @@ def test_no_search_mirror_only(
|
||||||
pass
|
pass
|
||||||
check_destroy(stage, self.stage_name)
|
check_destroy(stage, self.stage_name)
|
||||||
|
|
||||||
def test_search_if_default_fails(self, failing_fetch_strategy, search_fn):
|
@pytest.mark.parametrize(
|
||||||
|
"err_msg,expected", [('Fetch from fetch.test.com',
|
||||||
|
'Fetch from fetch.test.com'),
|
||||||
|
(None, 'All fetchers failed')])
|
||||||
|
def test_search_if_default_fails(self, failing_fetch_strategy, search_fn,
|
||||||
|
err_msg, expected):
|
||||||
stage = Stage(failing_fetch_strategy,
|
stage = Stage(failing_fetch_strategy,
|
||||||
name=self.stage_name,
|
name=self.stage_name,
|
||||||
search_fn=search_fn)
|
search_fn=search_fn)
|
||||||
|
|
||||||
with stage:
|
with stage:
|
||||||
try:
|
with pytest.raises(spack.fetch_strategy.FetchError,
|
||||||
stage.fetch(mirror_only=False)
|
match=expected):
|
||||||
except spack.fetch_strategy.FetchError:
|
stage.fetch(mirror_only=False, err_msg=err_msg)
|
||||||
pass
|
|
||||||
check_destroy(stage, self.stage_name)
|
check_destroy(stage, self.stage_name)
|
||||||
assert search_fn.performed_search
|
assert search_fn.performed_search
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue