Spack BundlePackage: a group of other packages (#11981)
This adds a special package type to Spack which is used to aggregate a set of packages that a user might commonly install together; it does not include any source code itself and does not require a download URL like other Spack packages. It may include an 'install' method to generate scripts, and Spack will run post-install hooks (including module generation). * Add new BundlePackage type * Update the Xsdk package to be a BundlePackage and remove the 'install' method (previously it had a noop install method) * "spack create --template" now takes "bundle" as an option * Rename cmd_create_repo fixture to "mock_test_repo" and relocate it to shared pytest fixtures * Add unit tests for BundlePackage behavior
This commit is contained in:
parent
47238b9714
commit
c9e214f6d3
@ -11,7 +11,6 @@
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import mkdirp
|
||||
|
||||
import spack.cmd
|
||||
import spack.util.web
|
||||
import spack.repo
|
||||
from spack.spec import Spec
|
||||
@ -58,35 +57,33 @@ class {class_name}({base_class_name}):
|
||||
|
||||
# FIXME: Add a proper url for your package's homepage here.
|
||||
homepage = "http://www.example.com"
|
||||
url = "{url}"
|
||||
{url_def}
|
||||
|
||||
{versions}
|
||||
|
||||
{dependencies}
|
||||
|
||||
{body}
|
||||
{body_def}
|
||||
'''
|
||||
|
||||
|
||||
class PackageTemplate(object):
|
||||
"""Provides the default values to be used for the package file template"""
|
||||
class BundlePackageTemplate(object):
|
||||
"""
|
||||
Provides the default values to be used for a bundle package file template.
|
||||
"""
|
||||
|
||||
base_class_name = 'Package'
|
||||
base_class_name = 'BundlePackage'
|
||||
|
||||
dependencies = """\
|
||||
# FIXME: Add dependencies if required.
|
||||
# depends_on('foo')"""
|
||||
|
||||
body = """\
|
||||
def install(self, spec, prefix):
|
||||
# FIXME: Unknown build system
|
||||
make()
|
||||
make('install')"""
|
||||
url_def = " # There is no URL since there is no code to download."
|
||||
body_def = " # There is no need for install() since there is no code."
|
||||
|
||||
def __init__(self, name, url, versions):
|
||||
def __init__(self, name, versions):
|
||||
self.name = name
|
||||
self.class_name = mod_to_class(name)
|
||||
self.url = url
|
||||
self.versions = versions
|
||||
|
||||
def write(self, pkg_path):
|
||||
@ -98,10 +95,29 @@ def write(self, pkg_path):
|
||||
name=self.name,
|
||||
class_name=self.class_name,
|
||||
base_class_name=self.base_class_name,
|
||||
url=self.url,
|
||||
url_def=self.url_def,
|
||||
versions=self.versions,
|
||||
dependencies=self.dependencies,
|
||||
body=self.body))
|
||||
body_def=self.body_def))
|
||||
|
||||
|
||||
class PackageTemplate(BundlePackageTemplate):
|
||||
"""Provides the default values to be used for the package file template"""
|
||||
|
||||
base_class_name = 'Package'
|
||||
|
||||
body_def = """\
|
||||
def install(self, spec, prefix):
|
||||
# FIXME: Unknown build system
|
||||
make()
|
||||
make('install')"""
|
||||
|
||||
url_line = """ url = \"{url}\""""
|
||||
|
||||
def __init__(self, name, url, versions):
|
||||
super(PackageTemplate, self).__init__(name, versions)
|
||||
|
||||
self.url_def = self.url_line.format(url=url)
|
||||
|
||||
|
||||
class AutotoolsPackageTemplate(PackageTemplate):
|
||||
@ -376,6 +392,7 @@ def __init__(self, name, *args):
|
||||
'autotools': AutotoolsPackageTemplate,
|
||||
'autoreconf': AutoreconfPackageTemplate,
|
||||
'cmake': CMakePackageTemplate,
|
||||
'bundle': BundlePackageTemplate,
|
||||
'qmake': QMakePackageTemplate,
|
||||
'scons': SconsPackageTemplate,
|
||||
'waf': WafPackageTemplate,
|
||||
@ -438,7 +455,7 @@ def __call__(self, stage, url):
|
||||
# Most octave extensions are hosted on Octave-Forge:
|
||||
# http://octave.sourceforge.net/index.html
|
||||
# They all have the same base URL.
|
||||
if 'downloads.sourceforge.net/octave/' in url:
|
||||
if url is not None and 'downloads.sourceforge.net/octave/' in url:
|
||||
self.build_system = 'octave'
|
||||
return
|
||||
|
||||
@ -507,15 +524,22 @@ def get_name(args):
|
||||
# Default package name
|
||||
name = 'example'
|
||||
|
||||
if args.name:
|
||||
if args.name is not None:
|
||||
# Use a user-supplied name if one is present
|
||||
name = args.name
|
||||
tty.msg("Using specified package name: '{0}'".format(name))
|
||||
elif args.url:
|
||||
if len(args.name.strip()) > 0:
|
||||
tty.msg("Using specified package name: '{0}'".format(name))
|
||||
else:
|
||||
tty.die("A package name must be provided when using the option.")
|
||||
elif args.url is not None:
|
||||
# Try to guess the package name based on the URL
|
||||
try:
|
||||
name = parse_name(args.url)
|
||||
tty.msg("This looks like a URL for {0}".format(name))
|
||||
if name != args.url:
|
||||
desc = 'URL'
|
||||
else:
|
||||
desc = 'package name'
|
||||
tty.msg("This looks like a {0} for {1}".format(desc, name))
|
||||
except UndetectableNameError:
|
||||
tty.die("Couldn't guess a name for this package.",
|
||||
" Please report this bug. In the meantime, try running:",
|
||||
@ -567,21 +591,27 @@ def get_versions(args, name):
|
||||
BuildSystemGuesser object
|
||||
"""
|
||||
|
||||
# Default version, hash, and guesser
|
||||
versions = """\
|
||||
# Default version with hash
|
||||
hashed_versions = """\
|
||||
# FIXME: Add proper versions and checksums here.
|
||||
# version('1.2.3', '0123456789abcdef0123456789abcdef')"""
|
||||
|
||||
# Default version without hash
|
||||
unhashed_versions = """\
|
||||
# FIXME: Add proper versions here.
|
||||
# version('1.2.4')"""
|
||||
|
||||
# Default guesser
|
||||
guesser = BuildSystemGuesser()
|
||||
|
||||
if args.url:
|
||||
if args.url is not None and args.template != 'bundle':
|
||||
# Find available versions
|
||||
try:
|
||||
url_dict = spack.util.web.find_versions_of_archive(args.url)
|
||||
except UndetectableVersionError:
|
||||
# Use fake versions
|
||||
tty.warn("Couldn't detect version in: {0}".format(args.url))
|
||||
return versions, guesser
|
||||
return hashed_versions, guesser
|
||||
|
||||
if not url_dict:
|
||||
# If no versions were found, revert to what the user provided
|
||||
@ -591,6 +621,8 @@ def get_versions(args, name):
|
||||
versions = spack.util.web.get_checksums_for_versions(
|
||||
url_dict, name, first_stage_function=guesser,
|
||||
keep_stage=args.keep_stage)
|
||||
else:
|
||||
versions = unhashed_versions
|
||||
|
||||
return versions, guesser
|
||||
|
||||
@ -610,15 +642,14 @@ def get_build_system(args, guesser):
|
||||
Returns:
|
||||
str: The name of the build system template to use
|
||||
"""
|
||||
|
||||
# Default template
|
||||
template = 'generic'
|
||||
|
||||
if args.template:
|
||||
if args.template is not None:
|
||||
# Use a user-supplied template if one is present
|
||||
template = args.template
|
||||
tty.msg("Using specified package template: '{0}'".format(template))
|
||||
elif args.url:
|
||||
elif args.url is not None:
|
||||
# Use whatever build system the guesser detected
|
||||
template = guesser.build_system
|
||||
if template == 'generic':
|
||||
@ -681,8 +712,11 @@ def create(parser, args):
|
||||
build_system = get_build_system(args, guesser)
|
||||
|
||||
# Create the package template object
|
||||
constr_args = {'name': name, 'versions': versions}
|
||||
package_class = templates[build_system]
|
||||
package = package_class(name, url, versions)
|
||||
if package_class != BundlePackageTemplate:
|
||||
constr_args['url'] = url
|
||||
package = package_class(**constr_args)
|
||||
tty.msg("Created template for {0} package".format(package.name))
|
||||
|
||||
# Create a directory for the new package
|
||||
|
@ -174,16 +174,19 @@ def print_text_info(pkg):
|
||||
not v.isdevelop(),
|
||||
v)
|
||||
preferred = sorted(pkg.versions, key=key_fn).pop()
|
||||
url = ''
|
||||
if pkg.has_code:
|
||||
url = fs.for_package_version(pkg, preferred)
|
||||
|
||||
f = fs.for_package_version(pkg, preferred)
|
||||
line = version(' {0}'.format(pad(preferred))) + color.cescape(f)
|
||||
line = version(' {0}'.format(pad(preferred))) + color.cescape(url)
|
||||
color.cprint(line)
|
||||
color.cprint('')
|
||||
color.cprint(section_title('Safe versions: '))
|
||||
|
||||
for v in reversed(sorted(pkg.versions)):
|
||||
f = fs.for_package_version(pkg, v)
|
||||
line = version(' {0}'.format(pad(v))) + color.cescape(f)
|
||||
if pkg.has_code:
|
||||
url = fs.for_package_version(pkg, v)
|
||||
line = version(' {0}'.format(pad(v))) + color.cescape(url)
|
||||
color.cprint(line)
|
||||
|
||||
color.cprint('')
|
||||
@ -193,12 +196,13 @@ def print_text_info(pkg):
|
||||
for line in formatter.lines:
|
||||
color.cprint(line)
|
||||
|
||||
color.cprint('')
|
||||
color.cprint(section_title('Installation Phases:'))
|
||||
phase_str = ''
|
||||
for phase in pkg.phases:
|
||||
phase_str += " {0}".format(phase)
|
||||
color.cprint(phase_str)
|
||||
if hasattr(pkg, 'phases') and pkg.phases:
|
||||
color.cprint('')
|
||||
color.cprint(section_title('Installation Phases:'))
|
||||
phase_str = ''
|
||||
for phase in pkg.phases:
|
||||
phase_str += " {0}".format(phase)
|
||||
color.cprint(phase_str)
|
||||
|
||||
for deptype in ('build', 'link', 'run'):
|
||||
color.cprint('')
|
||||
|
@ -258,6 +258,12 @@ def inc(fstype, category, attr=None):
|
||||
for pkg in spack.repo.path.all_packages():
|
||||
npkgs += 1
|
||||
|
||||
if not pkg.has_code:
|
||||
for _ in pkg.versions:
|
||||
inc('No code', 'total')
|
||||
nvers += 1
|
||||
continue
|
||||
|
||||
# look at each version
|
||||
for v, args in pkg.versions.items():
|
||||
# figure out what type of fetcher it is
|
||||
|
@ -17,13 +17,14 @@ class OpenMpi(Package):
|
||||
|
||||
The available directives are:
|
||||
|
||||
* ``version``
|
||||
* ``conflicts``
|
||||
* ``depends_on``
|
||||
* ``provides``
|
||||
* ``extends``
|
||||
* ``patch``
|
||||
* ``variant``
|
||||
* ``provides``
|
||||
* ``resource``
|
||||
* ``variant``
|
||||
* ``version``
|
||||
|
||||
"""
|
||||
|
||||
@ -44,7 +45,7 @@ class OpenMpi(Package):
|
||||
from spack.dependency import Dependency, default_deptype, canonical_deptype
|
||||
from spack.fetch_strategy import from_kwargs
|
||||
from spack.resource import Resource
|
||||
from spack.version import Version
|
||||
from spack.version import Version, VersionChecksumError
|
||||
|
||||
__all__ = []
|
||||
|
||||
@ -138,8 +139,8 @@ def __new__(cls, name, bases, attr_dict):
|
||||
cls, name, bases, attr_dict)
|
||||
|
||||
def __init__(cls, name, bases, attr_dict):
|
||||
# The class is being created: if it is a package we must ensure
|
||||
# that the directives are called on the class to set it up
|
||||
# The instance is being initialized: if it is a package we must ensure
|
||||
# that the directives are called to set it up.
|
||||
|
||||
if 'spack.pkg' in cls.__module__:
|
||||
# Ensure the presence of the dictionaries associated
|
||||
@ -260,16 +261,22 @@ def remove_directives(arg):
|
||||
|
||||
@directive('versions')
|
||||
def version(ver, checksum=None, **kwargs):
|
||||
"""Adds a version and metadata describing how to fetch its source code.
|
||||
"""Adds a version and, if appropriate, metadata for fetching its code.
|
||||
|
||||
Metadata is stored as a dict of ``kwargs`` in the package class's
|
||||
``versions`` dictionary.
|
||||
The ``version`` directives are aggregated into a ``versions`` dictionary
|
||||
attribute with ``Version`` keys and metadata values, where the metadata
|
||||
is stored as a dictionary of ``kwargs``.
|
||||
|
||||
The ``dict`` of arguments is turned into a valid fetch strategy
|
||||
later. See ``spack.fetch_strategy.for_package_version()``.
|
||||
The ``dict`` of arguments is turned into a valid fetch strategy for
|
||||
code packages later. See ``spack.fetch_strategy.for_package_version()``.
|
||||
"""
|
||||
def _execute_version(pkg):
|
||||
if checksum:
|
||||
if checksum is not None:
|
||||
if hasattr(pkg, 'has_code') and not pkg.has_code:
|
||||
raise VersionChecksumError(
|
||||
"{0}: Checksums not allowed in no-code packages"
|
||||
"(see '{1}' version).".format(pkg.name, ver))
|
||||
|
||||
kwargs['checksum'] = checksum
|
||||
|
||||
# Store kwargs for the package to later with a fetch_strategy.
|
||||
@ -451,18 +458,23 @@ def patch(url_or_filename, level=1, when=None, working_dir=".", **kwargs):
|
||||
|
||||
"""
|
||||
def _execute_patch(pkg_or_dep):
|
||||
pkg = pkg_or_dep
|
||||
if isinstance(pkg, Dependency):
|
||||
pkg = pkg.pkg
|
||||
|
||||
if hasattr(pkg, 'has_code') and not pkg.has_code:
|
||||
raise UnsupportedPackageDirective(
|
||||
'Patches are not allowed in {0}: package has no code.'.
|
||||
format(pkg.name))
|
||||
|
||||
when_spec = make_when_spec(when)
|
||||
if not when_spec:
|
||||
return
|
||||
|
||||
# if this spec is identical to some other, then append this
|
||||
# If this spec is identical to some other, then append this
|
||||
# patch to the existing list.
|
||||
cur_patches = pkg_or_dep.patches.setdefault(when_spec, [])
|
||||
|
||||
pkg = pkg_or_dep
|
||||
if isinstance(pkg, Dependency):
|
||||
pkg = pkg.pkg
|
||||
|
||||
global _patch_order_index
|
||||
ordering_key = (pkg.name, _patch_order_index)
|
||||
_patch_order_index += 1
|
||||
@ -639,3 +651,7 @@ class CircularReferenceError(DirectiveError):
|
||||
|
||||
class DependencyPatchError(DirectiveError):
|
||||
"""Raised for errors with patching dependencies."""
|
||||
|
||||
|
||||
class UnsupportedPackageDirective(DirectiveError):
|
||||
"""Raised when an invalid or unsupported package directive is specified."""
|
||||
|
@ -165,6 +165,51 @@ def matches(cls, args):
|
||||
return cls.url_attr in args
|
||||
|
||||
|
||||
class BundleFetchStrategy(FetchStrategy):
|
||||
"""
|
||||
Fetch strategy associated with bundle, or no-code, packages.
|
||||
|
||||
Having a basic fetch strategy is a requirement for executing post-install
|
||||
hooks. Consequently, this class provides the API but does little more
|
||||
than log messages.
|
||||
|
||||
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).
|
||||
url_attr = ''
|
||||
|
||||
def fetch(self):
|
||||
tty.msg("No code to fetch.")
|
||||
return True
|
||||
|
||||
def check(self):
|
||||
tty.msg("No code to check.")
|
||||
|
||||
def expand(self):
|
||||
tty.msg("No archive to expand.")
|
||||
|
||||
def reset(self):
|
||||
tty.msg("No code to reset.")
|
||||
|
||||
def archive(self, destination):
|
||||
tty.msg("No code to archive.")
|
||||
|
||||
@property
|
||||
def cachable(self):
|
||||
tty.msg("No code to cache.")
|
||||
return False
|
||||
|
||||
def source_id(self):
|
||||
tty.msg("No code to be uniquely identified.")
|
||||
return ''
|
||||
|
||||
|
||||
@pattern.composite(interface=FetchStrategy)
|
||||
class FetchStrategyComposite(object):
|
||||
"""Composite for a FetchStrategy object.
|
||||
@ -1117,6 +1162,12 @@ def _from_merged_attrs(fetcher, pkg, version):
|
||||
def for_package_version(pkg, version):
|
||||
"""Determine a fetch strategy based on the arguments supplied to
|
||||
version() in the package description."""
|
||||
|
||||
# No-code packages have a custom fetch strategy to work around issues
|
||||
# with resource staging.
|
||||
if not pkg.has_code:
|
||||
return BundleFetchStrategy()
|
||||
|
||||
check_pkg_attributes(pkg)
|
||||
|
||||
if not isinstance(version, Version):
|
||||
@ -1159,6 +1210,7 @@ def from_list_url(pkg):
|
||||
"""If a package provides a URL which lists URLs for resources by
|
||||
version, this can can create a fetcher for a URL discovered for
|
||||
the specified package's version."""
|
||||
|
||||
if pkg.list_url:
|
||||
try:
|
||||
versions = pkg.fetch_remote_versions()
|
||||
|
@ -137,10 +137,8 @@ class PackageMeta(
|
||||
spack.directives.DirectiveMeta,
|
||||
spack.mixins.PackageMixinsMeta
|
||||
):
|
||||
"""Conveniently transforms attributes to permit extensible phases
|
||||
|
||||
Iterates over the attribute 'phases' and creates / updates private
|
||||
InstallPhase attributes in the class that is being initialized
|
||||
"""
|
||||
Package metaclass for supporting directives (e.g., depends_on) and phases
|
||||
"""
|
||||
phase_fmt = '_InstallPhase_{0}'
|
||||
|
||||
@ -148,7 +146,14 @@ class PackageMeta(
|
||||
_InstallPhase_run_after = {}
|
||||
|
||||
def __new__(cls, name, bases, attr_dict):
|
||||
"""
|
||||
Instance creation is preceded by phase attribute transformations.
|
||||
|
||||
Conveniently transforms attributes to permit extensible phases by
|
||||
iterating over the attribute 'phases' and creating / updating private
|
||||
InstallPhase attributes in the class that will be initialized in
|
||||
__init__.
|
||||
"""
|
||||
if 'phases' in attr_dict:
|
||||
# Turn the strings in 'phases' into InstallPhase instances
|
||||
# and add them as private attributes
|
||||
@ -338,11 +343,16 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
|
||||
***The Package class***
|
||||
|
||||
A package defines how to fetch, verify (via, e.g., sha256), build,
|
||||
and install a piece of software. A Package also defines what other
|
||||
packages it depends on, so that dependencies can be installed along
|
||||
with the package itself. Packages are written in pure python by
|
||||
users of Spack.
|
||||
At its core, a package consists of a set of software to be installed.
|
||||
A package may focus on a piece of software and its associated software
|
||||
dependencies or it may simply be a set, or bundle, of software. The
|
||||
former requires defining how to fetch, verify (via, e.g., sha256), build,
|
||||
and install that software and the packages it depends on, so that
|
||||
dependencies can be installed along with the package itself. The latter,
|
||||
sometimes referred to as a ``no-source`` package, requires only defining
|
||||
the packages to be built.
|
||||
|
||||
Packages are written in pure Python.
|
||||
|
||||
There are two main parts of a Spack package:
|
||||
|
||||
@ -353,12 +363,12 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
used as input to the concretizer.
|
||||
|
||||
2. **Package instances**. Once instantiated, a package is
|
||||
essentially an installer for a particular piece of
|
||||
software. Spack calls methods like ``do_install()`` on the
|
||||
``Package`` object, and it uses those to drive user-implemented
|
||||
methods like ``patch()``, ``install()``, and other build steps.
|
||||
To install software, An instantiated package needs a *concrete*
|
||||
spec, which guides the behavior of the various install methods.
|
||||
essentially a software installer. Spack calls methods like
|
||||
``do_install()`` on the ``Package`` object, and it uses those to
|
||||
drive user-implemented methods like ``patch()``, ``install()``, and
|
||||
other build steps. To install software, an instantiated package
|
||||
needs a *concrete* spec, which guides the behavior of the various
|
||||
install methods.
|
||||
|
||||
Packages are imported from repos (see ``repo.py``).
|
||||
|
||||
@ -377,12 +387,15 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
|
||||
p = Package() # Done for you by spack
|
||||
|
||||
p.do_fetch() # downloads tarball from a URL
|
||||
p.do_fetch() # downloads tarball from a URL (or VCS)
|
||||
p.do_stage() # expands tarball in a temp directory
|
||||
p.do_patch() # applies patches to expanded source
|
||||
p.do_install() # calls package's install() function
|
||||
p.do_uninstall() # removes install directory
|
||||
|
||||
although packages that do not have code have nothing to fetch so omit
|
||||
``p.do_fetch()``.
|
||||
|
||||
There are also some other commands that clean the build area:
|
||||
|
||||
.. code-block:: python
|
||||
@ -392,7 +405,7 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
# re-expands the archive.
|
||||
|
||||
The convention used here is that a ``do_*`` function is intended to be
|
||||
called internally by Spack commands (in spack.cmd). These aren't for
|
||||
called internally by Spack commands (in ``spack.cmd``). These aren't for
|
||||
package writers to override, and doing so may break the functionality
|
||||
of the Package class.
|
||||
|
||||
@ -400,7 +413,8 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
override anything in this class. That is not usually required.
|
||||
|
||||
For most use cases. Package creators typically just add attributes
|
||||
like ``url`` and ``homepage``, or functions like ``install()``.
|
||||
like ``homepage`` and, for a code-based package, ``url``, or functions
|
||||
such as ``install()``.
|
||||
There are many custom ``Package`` subclasses in the
|
||||
``spack.build_systems`` package that make things even easier for
|
||||
specific build systems.
|
||||
@ -410,6 +424,10 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
# These are default values for instance variables.
|
||||
#
|
||||
|
||||
#: Most Spack packages are used to install source or binary code while
|
||||
#: those that do not can be used to install a set of other Spack packages.
|
||||
has_code = True
|
||||
|
||||
#: By default we build in parallel. Subclasses can override this.
|
||||
parallel = True
|
||||
|
||||
@ -482,7 +500,7 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
#: Do not include @ here in order not to unnecessarily ping the users.
|
||||
maintainers = []
|
||||
|
||||
#: List of attributes which affect do not affect a package's content.
|
||||
#: List of attributes to be excluded from a package's hash.
|
||||
metadata_attrs = ['homepage', 'url', 'list_url', 'extendable', 'parallel',
|
||||
'make_jobs']
|
||||
|
||||
@ -497,21 +515,6 @@ def __init__(self, spec):
|
||||
# a binary cache.
|
||||
self.installed_from_binary_cache = False
|
||||
|
||||
# Check versions in the versions dict.
|
||||
for v in self.versions:
|
||||
assert (isinstance(v, Version))
|
||||
|
||||
# Check version descriptors
|
||||
for v in sorted(self.versions):
|
||||
assert (isinstance(self.versions[v], dict))
|
||||
|
||||
# Version-ize the keys in versions dict
|
||||
try:
|
||||
self.versions = dict((Version(v), h)
|
||||
for v, h in self.versions.items())
|
||||
except ValueError as e:
|
||||
raise ValueError("In package %s: %s" % (self.name, e.message))
|
||||
|
||||
# Set a default list URL (place to find available versions)
|
||||
if not hasattr(self, 'list_url'):
|
||||
self.list_url = None
|
||||
@ -1032,6 +1035,9 @@ def do_fetch(self, mirror_only=False):
|
||||
if not self.spec.concrete:
|
||||
raise ValueError("Can only fetch concrete packages.")
|
||||
|
||||
if not self.has_code:
|
||||
raise InvalidPackageOpError("Can only fetch a package with a URL.")
|
||||
|
||||
start_time = time.time()
|
||||
checksum = spack.config.get('config:checksum')
|
||||
if checksum and self.version not in self.versions:
|
||||
@ -1069,11 +1075,20 @@ def do_stage(self, mirror_only=False):
|
||||
if not self.spec.concrete:
|
||||
raise ValueError("Can only stage concrete packages.")
|
||||
|
||||
self.do_fetch(mirror_only) # this will create the stage
|
||||
self.stage.expand_archive()
|
||||
# Always create the stage directory at this point. Why? A no-code
|
||||
# package may want to use the installation process to install metadata.
|
||||
self.stage.create()
|
||||
|
||||
if not os.listdir(self.stage.path):
|
||||
raise FetchError("Archive was empty for %s" % self.name)
|
||||
# Fetch/expand any associated code.
|
||||
if self.has_code:
|
||||
self.do_fetch(mirror_only)
|
||||
self.stage.expand_archive()
|
||||
|
||||
if not os.listdir(self.stage.path):
|
||||
raise FetchError("Archive was empty for %s" % self.name)
|
||||
else:
|
||||
# Support for post-install hooks requires a stage.source_path
|
||||
mkdirp(self.stage.source_path)
|
||||
|
||||
def do_patch(self):
|
||||
"""Applies patches if they haven't been applied already."""
|
||||
@ -1542,7 +1557,7 @@ def do_install(self, **kwargs):
|
||||
Package._install_bootstrap_compiler(dep.package, **kwargs)
|
||||
dep.package.do_install(**dep_kwargs)
|
||||
|
||||
# Then, install the compiler if it is not already installed.
|
||||
# Then install the compiler if it is not already installed.
|
||||
if install_deps:
|
||||
Package._install_bootstrap_compiler(self, **kwargs)
|
||||
|
||||
@ -1674,8 +1689,8 @@ def build_process():
|
||||
# Ensure the metadata path exists as well
|
||||
mkdirp(spack.store.layout.metadata_path(self.spec), mode=perms)
|
||||
|
||||
# Fork a child to do the actual installation
|
||||
# we preserve verbosity settings across installs.
|
||||
# Fork a child to do the actual installation.
|
||||
# Preserve verbosity settings across installs.
|
||||
PackageBase._verbose = spack.build_environment.fork(
|
||||
self, build_process, dirty=dirty, fake=fake)
|
||||
|
||||
@ -2357,6 +2372,19 @@ def _run_default_install_time_test_callbacks(self):
|
||||
build_system_flags = PackageBase.build_system_flags
|
||||
|
||||
|
||||
class BundlePackage(PackageBase):
|
||||
"""General purpose bundle, or no-code, package class."""
|
||||
#: There are no phases by default but the property is required to support
|
||||
#: post-install hooks (e.g., for module generation).
|
||||
phases = []
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = 'BundlePackage'
|
||||
|
||||
#: Bundle packages do not have associated source or binary code.
|
||||
has_code = False
|
||||
|
||||
|
||||
class Package(PackageBase):
|
||||
"""General purpose class with a single ``install``
|
||||
phase that needs to be coded by packagers.
|
||||
@ -2528,6 +2556,10 @@ def __init__(self, cls):
|
||||
"Package %s has no version with a URL." % cls.__name__)
|
||||
|
||||
|
||||
class InvalidPackageOpError(PackageError):
|
||||
"""Raised when someone tries perform an invalid operation on a package."""
|
||||
|
||||
|
||||
class ExtensionError(PackageError):
|
||||
"""Superclass for all errors having to do with extension packages."""
|
||||
|
||||
|
@ -11,7 +11,9 @@
|
||||
import llnl.util.filesystem
|
||||
from llnl.util.filesystem import *
|
||||
|
||||
from spack.package import Package, run_before, run_after, on_package_attributes
|
||||
from spack.package import \
|
||||
Package, BundlePackage, \
|
||||
run_before, run_after, on_package_attributes
|
||||
from spack.package import inject_flags, env_flags, build_system_flags
|
||||
from spack.build_systems.makefile import MakefilePackage
|
||||
from spack.build_systems.aspell_dict import AspellDictPackage
|
||||
|
@ -9,29 +9,13 @@
|
||||
|
||||
import spack.cmd.create
|
||||
import spack.util.editor
|
||||
|
||||
from spack.url import UndetectableNameError
|
||||
from spack.main import SpackCommand
|
||||
|
||||
|
||||
create = SpackCommand('create')
|
||||
|
||||
|
||||
@pytest.fixture("module")
|
||||
def cmd_create_repo(tmpdir_factory):
|
||||
repo_namespace = 'cmd_create_repo'
|
||||
repodir = tmpdir_factory.mktemp(repo_namespace)
|
||||
repodir.ensure(spack.repo.packages_dir_name, dir=True)
|
||||
yaml = repodir.join('repo.yaml')
|
||||
yaml.write("""
|
||||
repo:
|
||||
namespace: cmd_create_repo
|
||||
""")
|
||||
|
||||
repo = spack.repo.RepoPath(str(repodir))
|
||||
with spack.repo.swap(repo):
|
||||
yield repo, repodir
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def parser():
|
||||
"""Returns the parser for the module"""
|
||||
@ -40,18 +24,91 @@ def parser():
|
||||
return prs
|
||||
|
||||
|
||||
def test_create_template(parser, cmd_create_repo):
|
||||
@pytest.mark.parametrize('args,name_index,expected', [
|
||||
(['test-package'], 0, [r'TestPackage(Package)', r'def install(self']),
|
||||
(['-n', 'test-named-package', 'file://example.tar.gz'], 1,
|
||||
[r'TestNamedPackage(Package)', r'def install(self']),
|
||||
(['file://example.tar.gz'], -1,
|
||||
[r'Example(Package)', r'def install(self']),
|
||||
(['-t', 'bundle', 'test-bundle'], 2, [r'TestBundle(BundlePackage)'])
|
||||
])
|
||||
def test_create_template(parser, mock_test_repo, args, name_index, expected):
|
||||
"""Test template creation."""
|
||||
repo, repodir = cmd_create_repo
|
||||
repo, repodir = mock_test_repo
|
||||
|
||||
name = 'test-package'
|
||||
args = parser.parse_args(['--skip-editor', name])
|
||||
spack.cmd.create.create(parser, args)
|
||||
constr_args = parser.parse_args(['--skip-editor'] + args)
|
||||
spack.cmd.create.create(parser, constr_args)
|
||||
|
||||
filename = repo.filename_for_package_name(name)
|
||||
pkg_name = args[name_index] if name_index > -1 else 'example'
|
||||
filename = repo.filename_for_package_name(pkg_name)
|
||||
assert os.path.exists(filename)
|
||||
|
||||
with open(filename, 'r') as package_file:
|
||||
content = ' '.join(package_file.readlines())
|
||||
for entry in [r'TestPackage(Package)', r'def install(self']:
|
||||
assert content.find(entry) > -1
|
||||
for entry in expected:
|
||||
assert entry in content
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,expected', [
|
||||
(' ', 'name must be provided'),
|
||||
('bad#name', 'name can only contain'),
|
||||
])
|
||||
def test_create_template_bad_name(parser, mock_test_repo, name, expected):
|
||||
"""Test template creation with bad name options."""
|
||||
constr_args = parser.parse_args(['--skip-editor', '-n', name])
|
||||
with pytest.raises(SystemExit, matches=expected):
|
||||
spack.cmd.create.create(parser, constr_args)
|
||||
|
||||
|
||||
def test_build_system_guesser_no_stage(parser):
|
||||
"""Test build system guesser when stage not provided."""
|
||||
guesser = spack.cmd.create.BuildSystemGuesser()
|
||||
|
||||
# Ensure get the expected build system
|
||||
with pytest.raises(AttributeError,
|
||||
matches="'NoneType' object has no attribute"):
|
||||
guesser(None, '/the/url/does/not/matter')
|
||||
|
||||
|
||||
def test_build_system_guesser_octave(parser):
|
||||
"""
|
||||
Test build system guesser for the special case, where the same base URL
|
||||
identifies the build system rather than guessing the build system from
|
||||
files contained in the archive.
|
||||
"""
|
||||
url, expected = 'downloads.sourceforge.net/octave/', 'octave'
|
||||
guesser = spack.cmd.create.BuildSystemGuesser()
|
||||
|
||||
# Ensure get the expected build system
|
||||
guesser(None, url)
|
||||
assert guesser.build_system == expected
|
||||
|
||||
# Also ensure get the correct template
|
||||
args = parser.parse_args([url])
|
||||
bs = spack.cmd.create.get_build_system(args, guesser)
|
||||
assert bs == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url,expected', [
|
||||
('testname', 'testname'),
|
||||
('file://example.com/archive.tar.gz', 'archive'),
|
||||
])
|
||||
def test_get_name_urls(parser, url, expected):
|
||||
"""Test get_name with different URLs."""
|
||||
args = parser.parse_args([url])
|
||||
name = spack.cmd.create.get_name(args)
|
||||
assert name == expected
|
||||
|
||||
|
||||
def test_get_name_error(parser, monkeypatch):
|
||||
"""Test get_name UndetectableNameError exception path."""
|
||||
def _parse_name_offset(path, v):
|
||||
raise UndetectableNameError(path)
|
||||
|
||||
monkeypatch.setattr(spack.url, 'parse_name_offset', _parse_name_offset)
|
||||
|
||||
url = 'downloads.sourceforge.net/noapp/'
|
||||
args = parser.parse_args([url])
|
||||
|
||||
with pytest.raises(SystemExit, matches="Couldn't guess a name"):
|
||||
spack.cmd.create.get_name(args)
|
||||
|
@ -41,7 +41,8 @@ def _print(*args):
|
||||
'trilinos',
|
||||
'boost',
|
||||
'python',
|
||||
'dealii'
|
||||
'dealii',
|
||||
'xsdk' # a BundlePackage
|
||||
])
|
||||
def test_it_just_runs(pkg):
|
||||
info(pkg)
|
||||
|
@ -10,7 +10,7 @@
|
||||
import spack.concretize
|
||||
import spack.repo
|
||||
|
||||
from spack.concretize import find_spec
|
||||
from spack.concretize import find_spec, NoValidVersionError
|
||||
from spack.spec import Spec, CompilerSpec
|
||||
from spack.spec import ConflictsInSpecError, SpecError
|
||||
from spack.version import ver
|
||||
@ -565,3 +565,9 @@ def test_simultaneous_concretization_of_specs(self, abstract_specs):
|
||||
# Make sure the concrete spec are top-level specs with no dependents
|
||||
for spec in concrete_specs:
|
||||
assert not spec.dependents()
|
||||
|
||||
@pytest.mark.parametrize('spec', ['noversion', 'noversion-bundle'])
|
||||
def test_noversion_pkg(self, spec):
|
||||
"""Test concretization failures for no-version packages."""
|
||||
with pytest.raises(NoValidVersionError, match="no valid versions"):
|
||||
Spec(spec).concretized()
|
||||
|
@ -944,3 +944,53 @@ def invalid_spec(request):
|
||||
"""Specs that do not parse cleanly due to invalid formatting.
|
||||
"""
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture("module")
|
||||
def mock_test_repo(tmpdir_factory):
|
||||
"""Create an empty repository."""
|
||||
repo_namespace = 'mock_test_repo'
|
||||
repodir = tmpdir_factory.mktemp(repo_namespace)
|
||||
repodir.ensure(spack.repo.packages_dir_name, dir=True)
|
||||
yaml = repodir.join('repo.yaml')
|
||||
yaml.write("""
|
||||
repo:
|
||||
namespace: mock_test_repo
|
||||
""")
|
||||
|
||||
repo = spack.repo.RepoPath(str(repodir))
|
||||
with spack.repo.swap(repo):
|
||||
yield repo, repodir
|
||||
|
||||
shutil.rmtree(str(repodir))
|
||||
|
||||
|
||||
##########
|
||||
# Class and fixture to work around problems raising exceptions in directives,
|
||||
# which cause tests like test_from_list_url to hang for Python 2.x metaclass
|
||||
# processing.
|
||||
#
|
||||
# At this point only version and patch directive handling has been addressed.
|
||||
##########
|
||||
|
||||
class MockBundle(object):
|
||||
has_code = False
|
||||
name = 'mock-bundle'
|
||||
versions = {}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_directive_bundle():
|
||||
"""Return a mock bundle package for directive tests."""
|
||||
return MockBundle()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clear_directive_functions():
|
||||
"""Clear all overidden directive functions for subsequent tests."""
|
||||
yield
|
||||
|
||||
# Make sure any directive functions overidden by tests are cleared before
|
||||
# proceeding with subsequent tests that may depend on the original
|
||||
# functions.
|
||||
spack.directives.DirectiveMeta._directives_to_be_executed = []
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
from llnl.util.filesystem import mkdirp, touch, working_dir
|
||||
|
||||
from spack.package import InstallError, PackageBase, PackageStillNeededError
|
||||
from spack.package import \
|
||||
InstallError, InvalidPackageOpError, PackageBase, PackageStillNeededError
|
||||
import spack.patch
|
||||
import spack.repo
|
||||
import spack.store
|
||||
@ -326,6 +327,38 @@ def test_uninstall_by_spec_errors(mutable_database):
|
||||
PackageBase.uninstall_by_spec(rec.spec)
|
||||
|
||||
|
||||
def test_nosource_pkg_install(install_mockery, mock_fetch, mock_packages):
|
||||
"""Test install phases with the nosource package."""
|
||||
spec = Spec('nosource').concretized()
|
||||
pkg = spec.package
|
||||
|
||||
# Make sure install works even though there is no associated code.
|
||||
pkg.do_install()
|
||||
|
||||
# Also make sure an error is raised if `do_fetch` is called.
|
||||
with pytest.raises(InvalidPackageOpError,
|
||||
match="fetch a package with a URL"):
|
||||
pkg.do_fetch()
|
||||
|
||||
|
||||
def test_nosource_pkg_install_post_install(
|
||||
install_mockery, mock_fetch, mock_packages):
|
||||
"""Test install phases with the nosource package with post-install."""
|
||||
spec = Spec('nosource-install').concretized()
|
||||
pkg = spec.package
|
||||
|
||||
# Make sure both the install and post-install package methods work.
|
||||
pkg.do_install()
|
||||
|
||||
# Ensure the file created in the package's `install` method exists.
|
||||
install_txt = os.path.join(spec.prefix, 'install.txt')
|
||||
assert os.path.isfile(install_txt)
|
||||
|
||||
# Ensure the file created in the package's `post-install` method exists.
|
||||
post_install_txt = os.path.join(spec.prefix, 'post-install.txt')
|
||||
assert os.path.isfile(post_install_txt)
|
||||
|
||||
|
||||
def test_pkg_build_paths(install_mockery):
|
||||
# Get a basic concrete spec for the trivial install package.
|
||||
spec = Spec('trivial-install-test-package').concretized()
|
||||
|
@ -56,7 +56,7 @@ def test_all_virtual_packages_have_default_providers():
|
||||
|
||||
|
||||
def test_package_version_consistency():
|
||||
"""Make sure all versions on builtin packages can produce a fetcher."""
|
||||
"""Make sure all versions on builtin packages produce a fetcher."""
|
||||
for name in spack.repo.all_package_names():
|
||||
pkg = spack.repo.get(name)
|
||||
spack.fetch_strategy.check_pkg_attributes(pkg)
|
||||
|
@ -12,6 +12,8 @@
|
||||
from spack.util.naming import mod_to_class
|
||||
from spack.spec import Spec
|
||||
from spack.util.package_hash import package_content
|
||||
from spack.version import VersionChecksumError
|
||||
import spack.directives
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('config', 'mock_packages')
|
||||
@ -369,3 +371,20 @@ def test_rpath_args(mutable_database):
|
||||
rpath_args = rec.spec.package.rpath_args
|
||||
assert '-rpath' in rpath_args
|
||||
assert 'mpich' in rpath_args
|
||||
|
||||
|
||||
def test_bundle_version_checksum(mock_directive_bundle,
|
||||
clear_directive_functions):
|
||||
"""Test raising exception on a version checksum with a bundle package."""
|
||||
with pytest.raises(VersionChecksumError, match="Checksums not allowed"):
|
||||
version = spack.directives.version('1.0', checksum='1badpkg')
|
||||
version(mock_directive_bundle)
|
||||
|
||||
|
||||
def test_bundle_patch_directive(mock_directive_bundle,
|
||||
clear_directive_functions):
|
||||
"""Test raising exception on a patch directive with a bundle package."""
|
||||
with pytest.raises(spack.directives.UnsupportedPackageDirective,
|
||||
match="Patches are not allowed"):
|
||||
patch = spack.directives.patch('mock/patch.txt')
|
||||
patch(mock_directive_bundle)
|
||||
|
@ -85,61 +85,32 @@ def test_fetch(
|
||||
assert 'echo Building...' in contents
|
||||
|
||||
|
||||
def test_from_list_url(mock_packages, config):
|
||||
@pytest.mark.parametrize('spec,url,digest', [
|
||||
('url-list-test @0.0.0', 'foo-0.0.0.tar.gz', 'abc000'),
|
||||
('url-list-test @1.0.0', 'foo-1.0.0.tar.gz', 'abc100'),
|
||||
('url-list-test @3.0', 'foo-3.0.tar.gz', 'abc30'),
|
||||
('url-list-test @4.5', 'foo-4.5.tar.gz', 'abc45'),
|
||||
('url-list-test @2.0.0b2', 'foo-2.0.0b2.tar.gz', 'abc200b2'),
|
||||
('url-list-test @3.0a1', 'foo-3.0a1.tar.gz', 'abc30a1'),
|
||||
('url-list-test @4.5-rc5', 'foo-4.5-rc5.tar.gz', 'abc45rc5'),
|
||||
])
|
||||
def test_from_list_url(mock_packages, config, spec, url, digest):
|
||||
"""
|
||||
Test URLs in the url-list-test package, which means they should
|
||||
have checksums in the package.
|
||||
"""
|
||||
specification = Spec(spec).concretized()
|
||||
pkg = spack.repo.get(specification)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == url
|
||||
assert fetch_strategy.digest == digest
|
||||
|
||||
|
||||
def test_from_list_url_unspecified(mock_packages, config):
|
||||
"""Test non-specific URLs from the url-list-test package."""
|
||||
pkg = spack.repo.get('url-list-test')
|
||||
|
||||
# These URLs are all in the url-list-test package and should have
|
||||
# checksums taken from the package.
|
||||
spec = Spec('url-list-test @0.0.0').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-0.0.0.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc000'
|
||||
|
||||
spec = Spec('url-list-test @1.0.0').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-1.0.0.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc100'
|
||||
|
||||
spec = Spec('url-list-test @3.0').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-3.0.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc30'
|
||||
|
||||
spec = Spec('url-list-test @4.5').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-4.5.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc45'
|
||||
|
||||
spec = Spec('url-list-test @2.0.0b2').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-2.0.0b2.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc200b2'
|
||||
|
||||
spec = Spec('url-list-test @3.0a1').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-3.0a1.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc30a1'
|
||||
|
||||
spec = Spec('url-list-test @4.5-rc5').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
assert isinstance(fetch_strategy, URLFetchStrategy)
|
||||
assert os.path.basename(fetch_strategy.url) == 'foo-4.5-rc5.tar.gz'
|
||||
assert fetch_strategy.digest == 'abc45rc5'
|
||||
|
||||
# this one is not in the url-list-test package.
|
||||
spec = Spec('url-list-test @2.0.0').concretized()
|
||||
pkg = spack.repo.get(spec)
|
||||
fetch_strategy = from_list_url(pkg)
|
||||
@ -148,6 +119,14 @@ def test_from_list_url(mock_packages, config):
|
||||
assert fetch_strategy.digest is None
|
||||
|
||||
|
||||
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)
|
||||
assert fetch_strategy is None
|
||||
|
||||
|
||||
def test_hash_detection(checksum_type):
|
||||
algo = crypto.hash_fun_for_algo(checksum_type)()
|
||||
h = 'f' * (algo.digest_size * 2) # hex -> bytes
|
||||
|
@ -30,6 +30,7 @@
|
||||
from functools import wraps
|
||||
from six import string_types
|
||||
|
||||
import spack.error
|
||||
from spack.util.spack_yaml import syaml_dict
|
||||
|
||||
|
||||
@ -848,3 +849,11 @@ def ver(obj):
|
||||
return obj
|
||||
else:
|
||||
raise TypeError("ver() can't convert %s to version!" % type(obj))
|
||||
|
||||
|
||||
class VersionError(spack.error.SpackError):
|
||||
"""This is raised when something is wrong with a version."""
|
||||
|
||||
|
||||
class VersionChecksumError(VersionError):
|
||||
"""Raised for version checksum errors."""
|
||||
|
@ -0,0 +1,31 @@
|
||||
# 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
|
||||
from spack import *
|
||||
from llnl.util.filesystem import touch
|
||||
|
||||
|
||||
class NosourceInstall(BundlePackage):
|
||||
"""Simple bundle package with one dependency and metadata 'install'."""
|
||||
|
||||
homepage = "http://www.example.com"
|
||||
|
||||
version('2.0')
|
||||
version('1.0')
|
||||
|
||||
depends_on('dependency-install')
|
||||
|
||||
# The install phase must be specified.
|
||||
phases = ['install']
|
||||
|
||||
# The install method must also be present.
|
||||
def install(self, spec, prefix):
|
||||
touch(os.path.join(self.prefix, 'install.txt'))
|
||||
|
||||
@run_after('install')
|
||||
def post_install(self):
|
||||
touch(os.path.join(self.prefix, 'post-install.txt'))
|
17
var/spack/repos/builtin.mock/packages/nosource/package.py
Normal file
17
var/spack/repos/builtin.mock/packages/nosource/package.py
Normal file
@ -0,0 +1,17 @@
|
||||
# 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)
|
||||
|
||||
|
||||
from spack import *
|
||||
|
||||
|
||||
class Nosource(BundlePackage):
|
||||
"""Simple bundle package with one dependency"""
|
||||
|
||||
homepage = "http://www.example.com"
|
||||
|
||||
version('1.0')
|
||||
|
||||
depends_on('dependency-install')
|
@ -0,0 +1,18 @@
|
||||
# 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)
|
||||
|
||||
|
||||
from spack import *
|
||||
|
||||
|
||||
class NoversionBundle(BundlePackage):
|
||||
"""
|
||||
Simple bundle package with no version and one dependency, which
|
||||
should be rejected for lack of a version.
|
||||
"""
|
||||
|
||||
homepage = "http://www.example.com"
|
||||
|
||||
depends_on('dependency-install')
|
19
var/spack/repos/builtin.mock/packages/noversion/package.py
Normal file
19
var/spack/repos/builtin.mock/packages/noversion/package.py
Normal file
@ -0,0 +1,19 @@
|
||||
# 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)
|
||||
|
||||
from spack import *
|
||||
|
||||
|
||||
class Noversion(Package):
|
||||
"""
|
||||
Simple package with no version, which should be rejected since a version
|
||||
is required.
|
||||
"""
|
||||
|
||||
homepage = "http://www.example.com"
|
||||
url = "http://www.example.com/a-1.0.tar.gz"
|
||||
|
||||
def install(self, spec, prefix):
|
||||
touch(join_path(prefix, 'an_installation_file'))
|
@ -4,25 +4,23 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import os
|
||||
from spack import *
|
||||
|
||||
|
||||
class Xsdk(Package):
|
||||
class Xsdk(BundlePackage):
|
||||
"""Xsdk is a suite of Department of Energy (DOE) packages for numerical
|
||||
simulation. This is a Spack bundle package that installs the xSDK
|
||||
packages
|
||||
"""
|
||||
|
||||
homepage = "http://xsdk.info"
|
||||
url = 'http://ftp.mcs.anl.gov/pub/petsc/externalpackages/xsdk.tar.gz'
|
||||
|
||||
maintainers = ['balay', 'luszczek']
|
||||
|
||||
version('develop', 'a52dc710c744afa0b71429b8ec9425bc')
|
||||
version('0.4.0', 'a52dc710c744afa0b71429b8ec9425bc')
|
||||
version('0.3.0', 'a52dc710c744afa0b71429b8ec9425bc')
|
||||
version('xsdk-0.2.0', 'a52dc710c744afa0b71429b8ec9425bc')
|
||||
version('develop')
|
||||
version('0.4.0')
|
||||
version('0.3.0')
|
||||
version('xsdk-0.2.0')
|
||||
|
||||
variant('debug', default=False, description='Compile in debug mode')
|
||||
variant('cuda', default=False, description='Enable CUDA dependent packages')
|
||||
@ -123,12 +121,3 @@ class Xsdk(Package):
|
||||
|
||||
# How do we propagate debug flag to all depends on packages ?
|
||||
# If I just do spack install xsdk+debug will that propogate it down?
|
||||
|
||||
# Dummy install for now, will be removed when metapackage is available
|
||||
def install(self, spec, prefix):
|
||||
# Prevent the error message
|
||||
# ==> Error: Install failed for xsdk. Nothing was installed!
|
||||
# ==> Error: Installation process had nonzero exit code : 256
|
||||
with open(os.path.join(spec.prefix, 'bundle-package.txt'), 'w') as out:
|
||||
out.write('This is a bundle\n')
|
||||
out.close()
|
||||
|
Loading…
Reference in New Issue
Block a user