Merge remote-tracking branch 'upstream/develop' into efischer/160311-StagedPackage
# Conflicts: # lib/spack/spack/package.py
This commit is contained in:
@@ -38,7 +38,7 @@ contains tarballs for each package, named after each package.
|
||||
|
||||
.. note::
|
||||
|
||||
Archives are **not** named exactly they were in the package's fetch
|
||||
Archives are **not** named exactly the way they were in the package's fetch
|
||||
URL. They have the form ``<name>-<version>.<extension>``, where
|
||||
``<name>`` is Spack's name for the package, ``<version>`` is the
|
||||
version of the tarball, and ``<extension>`` is whatever format the
|
||||
|
@@ -1559,11 +1559,11 @@ you ask for a particular spec.
|
||||
A user may have certain preferences for how packages should
|
||||
be concretized on their system. For example, one user may prefer packages
|
||||
built with OpenMPI and the Intel compiler. Another user may prefer
|
||||
packages be built with MVAPICH and GCC.
|
||||
packages be built with MVAPICH and GCC.
|
||||
|
||||
Spack can be configured to prefer certain compilers, package
|
||||
versions, depends_on, and variants during concretization.
|
||||
The preferred configuration can be controlled via the
|
||||
The preferred configuration can be controlled via the
|
||||
``~/.spack/packages.yaml`` file for user configuations, or the
|
||||
``etc/spack/packages.yaml`` site configuration.
|
||||
|
||||
@@ -1582,32 +1582,32 @@ Here's an example packages.yaml file that sets preferred packages:
|
||||
compiler: [gcc@4.4.7, gcc@4.6:, intel, clang, pgi]
|
||||
providers:
|
||||
mpi: [mvapich, mpich, openmpi]
|
||||
|
||||
|
||||
|
||||
At a high level, this example is specifying how packages should be
|
||||
concretized. The dyninst package should prefer using gcc 4.9 and
|
||||
concretized. The dyninst package should prefer using gcc 4.9 and
|
||||
be built with debug options. The gperftools package should prefer version
|
||||
2.2 over 2.4. Every package on the system should prefer mvapich for
|
||||
its MPI and gcc 4.4.7 (except for Dyninst, which overrides this by preferring gcc 4.9).
|
||||
These options are used to fill in implicit defaults. Any of them can be overwritten
|
||||
its MPI and gcc 4.4.7 (except for Dyninst, which overrides this by preferring gcc 4.9).
|
||||
These options are used to fill in implicit defaults. Any of them can be overwritten
|
||||
on the command line if explicitly requested.
|
||||
|
||||
Each packages.yaml file begins with the string ``packages:`` and
|
||||
Each packages.yaml file begins with the string ``packages:`` and
|
||||
package names are specified on the next level. The special string ``all``
|
||||
applies settings to each package. Underneath each package name is
|
||||
one or more components: ``compiler``, ``variants``, ``version``,
|
||||
or ``providers``. Each component has an ordered list of spec
|
||||
applies settings to each package. Underneath each package name is
|
||||
one or more components: ``compiler``, ``variants``, ``version``,
|
||||
or ``providers``. Each component has an ordered list of spec
|
||||
``constraints``, with earlier entries in the list being preferred over
|
||||
later entries.
|
||||
|
||||
Sometimes a package installation may have constraints that forbid
|
||||
Sometimes a package installation may have constraints that forbid
|
||||
the first concretization rule, in which case Spack will use the first
|
||||
legal concretization rule. Going back to the example, if a user
|
||||
requests gperftools 2.3 or later, then Spack will install version 2.4
|
||||
requests gperftools 2.3 or later, then Spack will install version 2.4
|
||||
as the 2.4 version of gperftools is preferred over 2.3.
|
||||
|
||||
An explicit concretization rule in the preferred section will always
|
||||
take preference over unlisted concretizations. In the above example,
|
||||
An explicit concretization rule in the preferred section will always
|
||||
take preference over unlisted concretizations. In the above example,
|
||||
xlc isn't listed in the compiler list. Every listed compiler from
|
||||
gcc to pgi will thus be preferred over the xlc compiler.
|
||||
|
||||
@@ -1844,6 +1844,20 @@ dedicated process.
|
||||
|
||||
.. _prefix-objects:
|
||||
|
||||
|
||||
Failing the build
|
||||
----------------------
|
||||
|
||||
Sometimes you don't want a package to successfully install unless some
|
||||
condition is true. You can explicitly cause the build to fail from
|
||||
``install()`` by raising an ``InstallError``, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if spec.architecture.startswith('darwin'):
|
||||
raise InstallError('This package does not build on Mac OS X!')
|
||||
|
||||
|
||||
Prefix objects
|
||||
----------------------
|
||||
|
||||
@@ -2160,6 +2174,62 @@ package, this allows us to avoid race conditions in the library's
|
||||
build system.
|
||||
|
||||
|
||||
.. _sanity-checks:
|
||||
|
||||
Sanity checking an intallation
|
||||
--------------------------------
|
||||
|
||||
By default, Spack assumes that a build has failed if nothing is
|
||||
written to the install prefix, and that it has succeeded if anything
|
||||
(a file, a directory, etc.) is written to the install prefix after
|
||||
``install()`` completes.
|
||||
|
||||
Consider a simple autotools build like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def install(self, spec, prefix):
|
||||
configure("--prefix=" + prefix)
|
||||
make()
|
||||
make("install")
|
||||
|
||||
If you are using using standard autotools or CMake, ``configure`` and
|
||||
``make`` will not write anything to the install prefix. Only ``make
|
||||
install`` writes the files, and only once the build is already
|
||||
complete. Not all builds are like this. Many builds of scientific
|
||||
software modify the install prefix *before* ``make install``. Builds
|
||||
like this can falsely report that they were successfully installed if
|
||||
an error occurs before the install is complete but after files have
|
||||
been written to the ``prefix``.
|
||||
|
||||
|
||||
``sanity_check_is_file`` and ``sanity_check_is_dir``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can optionally specify *sanity checks* to deal with this problem.
|
||||
Add properties like this to your package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyPackage(Package):
|
||||
...
|
||||
|
||||
sanity_check_is_file = ['include/libelf.h']
|
||||
sanity_check_is_dir = [lib]
|
||||
|
||||
def install(self, spec, prefix):
|
||||
configure("--prefix=" + prefix)
|
||||
make()
|
||||
make("install")
|
||||
|
||||
Now, after ``install()`` runs, Spack will check whether
|
||||
``$prefix/include/libelf.h`` exists and is a file, and whether
|
||||
``$prefix/lib`` exists and is a directory. If the checks fail, then
|
||||
the build will fail and the install prefix will be removed. If they
|
||||
succeed, Spack considers the build succeeful and keeps the prefix in
|
||||
place.
|
||||
|
||||
|
||||
.. _file-manipulation:
|
||||
|
||||
File manipulation functions
|
||||
|
@@ -235,11 +235,11 @@ def setter(name, value):
|
||||
if not has_method(cls, '_cmp_key'):
|
||||
raise TypeError("'%s' doesn't define _cmp_key()." % cls.__name__)
|
||||
|
||||
setter('__eq__', lambda s,o: o is not None and s._cmp_key() == o._cmp_key())
|
||||
setter('__eq__', lambda s,o: (s is o) or (o is not None and s._cmp_key() == o._cmp_key()))
|
||||
setter('__lt__', lambda s,o: o is not None and s._cmp_key() < o._cmp_key())
|
||||
setter('__le__', lambda s,o: o is not None and s._cmp_key() <= o._cmp_key())
|
||||
|
||||
setter('__ne__', lambda s,o: o is None or s._cmp_key() != o._cmp_key())
|
||||
setter('__ne__', lambda s,o: (s is not o) and (o is None or s._cmp_key() != o._cmp_key()))
|
||||
setter('__gt__', lambda s,o: o is None or s._cmp_key() > o._cmp_key())
|
||||
setter('__ge__', lambda s,o: o is None or s._cmp_key() >= o._cmp_key())
|
||||
|
||||
|
@@ -188,3 +188,10 @@
|
||||
import spack.util.executable
|
||||
from spack.util.executable import *
|
||||
__all__ += spack.util.executable.__all__
|
||||
|
||||
from spack.package import \
|
||||
install_dependency_symlinks, flatten_dependencies, DependencyConflictError, \
|
||||
InstallError, ExternalPackageError
|
||||
__all__ += [
|
||||
'install_dependency_symlinks', 'flatten_dependencies', 'DependencyConflictError',
|
||||
'InstallError', 'ExternalPackageError']
|
||||
|
@@ -3,7 +3,7 @@
|
||||
build environment. All of this is set up by package.py just before
|
||||
install() is called.
|
||||
|
||||
There are two parts to the bulid environment:
|
||||
There are two parts to the build environment:
|
||||
|
||||
1. Python build environment (i.e. install() method)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
the package's module scope. Ths allows package writers to call
|
||||
them all directly in Package.install() without writing 'self.'
|
||||
everywhere. No, this isn't Pythonic. Yes, it makes the code more
|
||||
readable and more like the shell script from whcih someone is
|
||||
readable and more like the shell script from which someone is
|
||||
likely porting.
|
||||
|
||||
2. Build execution environment
|
||||
@@ -27,17 +27,18 @@
|
||||
Skimming this module is a nice way to get acquainted with the types of
|
||||
calls you can make from within the install() function.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
from llnl.util.filesystem import *
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import spack
|
||||
import spack.compilers as compilers
|
||||
from spack.util.executable import Executable, which
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import *
|
||||
from spack.environment import EnvironmentModifications, validate
|
||||
from spack.util.environment import *
|
||||
from spack.util.executable import Executable, which
|
||||
|
||||
#
|
||||
# This can be set by the user to globally disable parallel builds.
|
||||
@@ -83,85 +84,88 @@ def __call__(self, *args, **kwargs):
|
||||
return super(MakeExecutable, self).__call__(*args, **kwargs)
|
||||
|
||||
|
||||
def set_compiler_environment_variables(pkg):
|
||||
assert(pkg.spec.concrete)
|
||||
compiler = pkg.compiler
|
||||
|
||||
def set_compiler_environment_variables(pkg, env):
|
||||
assert pkg.spec.concrete
|
||||
# Set compiler variables used by CMake and autotools
|
||||
assert all(key in pkg.compiler.link_paths
|
||||
for key in ('cc', 'cxx', 'f77', 'fc'))
|
||||
assert all(key in pkg.compiler.link_paths for key in ('cc', 'cxx', 'f77', 'fc'))
|
||||
|
||||
# Populate an object with the list of environment modifications
|
||||
# and return it
|
||||
# TODO : add additional kwargs for better diagnostics, like requestor, ttyout, ttyerr, etc.
|
||||
link_dir = spack.build_env_path
|
||||
os.environ['CC'] = join_path(link_dir, pkg.compiler.link_paths['cc'])
|
||||
os.environ['CXX'] = join_path(link_dir, pkg.compiler.link_paths['cxx'])
|
||||
os.environ['F77'] = join_path(link_dir, pkg.compiler.link_paths['f77'])
|
||||
os.environ['FC'] = join_path(link_dir, pkg.compiler.link_paths['fc'])
|
||||
env.set('CC', join_path(link_dir, pkg.compiler.link_paths['cc']))
|
||||
env.set('CXX', join_path(link_dir, pkg.compiler.link_paths['cxx']))
|
||||
env.set('F77', join_path(link_dir, pkg.compiler.link_paths['f77']))
|
||||
env.set('FC', join_path(link_dir, pkg.compiler.link_paths['fc']))
|
||||
|
||||
# Set SPACK compiler variables so that our wrapper knows what to call
|
||||
compiler = pkg.compiler
|
||||
if compiler.cc:
|
||||
os.environ['SPACK_CC'] = compiler.cc
|
||||
env.set('SPACK_CC', compiler.cc)
|
||||
if compiler.cxx:
|
||||
os.environ['SPACK_CXX'] = compiler.cxx
|
||||
env.set('SPACK_CXX', compiler.cxx)
|
||||
if compiler.f77:
|
||||
os.environ['SPACK_F77'] = compiler.f77
|
||||
env.set('SPACK_F77', compiler.f77)
|
||||
if compiler.fc:
|
||||
os.environ['SPACK_FC'] = compiler.fc
|
||||
env.set('SPACK_FC', compiler.fc)
|
||||
|
||||
os.environ['SPACK_COMPILER_SPEC'] = str(pkg.spec.compiler)
|
||||
env.set('SPACK_COMPILER_SPEC', str(pkg.spec.compiler))
|
||||
return env
|
||||
|
||||
|
||||
def set_build_environment_variables(pkg):
|
||||
"""This ensures a clean install environment when we build packages.
|
||||
def set_build_environment_variables(pkg, env):
|
||||
"""
|
||||
This ensures a clean install environment when we build packages
|
||||
"""
|
||||
# Add spack build environment path with compiler wrappers first in
|
||||
# the path. We add both spack.env_path, which includes default
|
||||
# wrappers (cc, c++, f77, f90), AND a subdirectory containing
|
||||
# compiler-specific symlinks. The latter ensures that builds that
|
||||
# are sensitive to the *name* of the compiler see the right name
|
||||
# when we're building wtih the wrappers.
|
||||
# when we're building with the wrappers.
|
||||
#
|
||||
# Conflicts on case-insensitive systems (like "CC" and "cc") are
|
||||
# handled by putting one in the <build_env_path>/case-insensitive
|
||||
# directory. Add that to the path too.
|
||||
env_paths = []
|
||||
def add_env_path(path):
|
||||
env_paths.append(path)
|
||||
ci = join_path(path, 'case-insensitive')
|
||||
if os.path.isdir(ci): env_paths.append(ci)
|
||||
add_env_path(spack.build_env_path)
|
||||
add_env_path(join_path(spack.build_env_path, pkg.compiler.name))
|
||||
for item in [spack.build_env_path, join_path(spack.build_env_path, pkg.compiler.name)]:
|
||||
env_paths.append(item)
|
||||
ci = join_path(item, 'case-insensitive')
|
||||
if os.path.isdir(ci):
|
||||
env_paths.append(ci)
|
||||
|
||||
path_put_first("PATH", env_paths)
|
||||
path_set(SPACK_ENV_PATH, env_paths)
|
||||
for item in reversed(env_paths):
|
||||
env.prepend_path('PATH', item)
|
||||
env.set_path(SPACK_ENV_PATH, env_paths)
|
||||
|
||||
# Prefixes of all of the package's dependencies go in
|
||||
# SPACK_DEPENDENCIES
|
||||
# Prefixes of all of the package's dependencies go in SPACK_DEPENDENCIES
|
||||
dep_prefixes = [d.prefix for d in pkg.spec.traverse(root=False)]
|
||||
path_set(SPACK_DEPENDENCIES, dep_prefixes)
|
||||
env.set_path(SPACK_DEPENDENCIES, dep_prefixes)
|
||||
env.set_path('CMAKE_PREFIX_PATH', dep_prefixes) # Add dependencies to CMAKE_PREFIX_PATH
|
||||
|
||||
# Install prefix
|
||||
os.environ[SPACK_PREFIX] = pkg.prefix
|
||||
env.set(SPACK_PREFIX, pkg.prefix)
|
||||
|
||||
# Install root prefix
|
||||
os.environ[SPACK_INSTALL] = spack.install_path
|
||||
env.set(SPACK_INSTALL, spack.install_path)
|
||||
|
||||
# Remove these vars from the environment during build because they
|
||||
# can affect how some packages find libraries. We want to make
|
||||
# sure that builds never pull in unintended external dependencies.
|
||||
pop_keys(os.environ, "LD_LIBRARY_PATH", "LD_RUN_PATH", "DYLD_LIBRARY_PATH")
|
||||
env.unset('LD_LIBRARY_PATH')
|
||||
env.unset('LD_RUN_PATH')
|
||||
env.unset('DYLD_LIBRARY_PATH')
|
||||
|
||||
# Add bin directories from dependencies to the PATH for the build.
|
||||
bin_dirs = ['%s/bin' % prefix for prefix in dep_prefixes]
|
||||
path_put_first('PATH', [bin for bin in bin_dirs if os.path.isdir(bin)])
|
||||
bin_dirs = reversed(filter(os.path.isdir, ['%s/bin' % prefix for prefix in dep_prefixes]))
|
||||
for item in bin_dirs:
|
||||
env.prepend_path('PATH', item)
|
||||
|
||||
# Working directory for the spack command itself, for debug logs.
|
||||
if spack.debug:
|
||||
os.environ[SPACK_DEBUG] = "TRUE"
|
||||
os.environ[SPACK_SHORT_SPEC] = pkg.spec.short_spec
|
||||
os.environ[SPACK_DEBUG_LOG_DIR] = spack.spack_working_dir
|
||||
|
||||
# Add dependencies to CMAKE_PREFIX_PATH
|
||||
path_set("CMAKE_PREFIX_PATH", dep_prefixes)
|
||||
env.set(SPACK_DEBUG, 'TRUE')
|
||||
env.set(SPACK_SHORT_SPEC, pkg.spec.short_spec)
|
||||
env.set(SPACK_DEBUG_LOG_DIR, spack.spack_working_dir)
|
||||
|
||||
# Add any pkgconfig directories to PKG_CONFIG_PATH
|
||||
pkg_config_dirs = []
|
||||
@@ -170,10 +174,12 @@ def add_env_path(path):
|
||||
pcdir = join_path(p, libdir, 'pkgconfig')
|
||||
if os.path.isdir(pcdir):
|
||||
pkg_config_dirs.append(pcdir)
|
||||
path_set("PKG_CONFIG_PATH", pkg_config_dirs)
|
||||
env.set_path('PKG_CONFIG_PATH', pkg_config_dirs)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def set_module_variables_for_package(pkg, m):
|
||||
def set_module_variables_for_package(pkg, module):
|
||||
"""Populate the module scope of install() with some useful functions.
|
||||
This makes things easier for package writers.
|
||||
"""
|
||||
@@ -183,6 +189,8 @@ def set_module_variables_for_package(pkg, m):
|
||||
jobs = 1
|
||||
elif pkg.make_jobs:
|
||||
jobs = pkg.make_jobs
|
||||
|
||||
m = module
|
||||
m.make_jobs = jobs
|
||||
|
||||
# TODO: make these build deps that can be installed if not found.
|
||||
@@ -217,7 +225,7 @@ def set_module_variables_for_package(pkg, m):
|
||||
m.spack_cc = join_path(link_dir, pkg.compiler.link_paths['cc'])
|
||||
m.spack_cxx = join_path(link_dir, pkg.compiler.link_paths['cxx'])
|
||||
m.spack_f77 = join_path(link_dir, pkg.compiler.link_paths['f77'])
|
||||
m.spack_f90 = join_path(link_dir, pkg.compiler.link_paths['fc'])
|
||||
m.spack_fc = join_path(link_dir, pkg.compiler.link_paths['fc'])
|
||||
|
||||
# Emulate some shell commands for convenience
|
||||
m.pwd = os.getcwd
|
||||
@@ -262,24 +270,63 @@ def parent_class_modules(cls):
|
||||
return result
|
||||
|
||||
|
||||
def setup_module_variables_for_dag(pkg):
|
||||
"""Set module-scope variables for all packages in the DAG."""
|
||||
for spec in pkg.spec.traverse(order='post'):
|
||||
# If a user makes their own package repo, e.g.
|
||||
# spack.repos.mystuff.libelf.Libelf, and they inherit from
|
||||
# an existing class like spack.repos.original.libelf.Libelf,
|
||||
# then set the module variables for both classes so the
|
||||
# parent class can still use them if it gets called.
|
||||
spkg = spec.package
|
||||
modules = parent_class_modules(spkg.__class__)
|
||||
for mod in modules:
|
||||
set_module_variables_for_package(spkg, mod)
|
||||
set_module_variables_for_package(spkg, spkg.module)
|
||||
|
||||
|
||||
def setup_package(pkg):
|
||||
"""Execute all environment setup routines."""
|
||||
set_compiler_environment_variables(pkg)
|
||||
set_build_environment_variables(pkg)
|
||||
spack_env = EnvironmentModifications()
|
||||
run_env = EnvironmentModifications()
|
||||
|
||||
# If a user makes their own package repo, e.g.
|
||||
# spack.repos.mystuff.libelf.Libelf, and they inherit from
|
||||
# an existing class like spack.repos.original.libelf.Libelf,
|
||||
# then set the module variables for both classes so the
|
||||
# parent class can still use them if it gets called.
|
||||
modules = parent_class_modules(pkg.__class__)
|
||||
for mod in modules:
|
||||
set_module_variables_for_package(pkg, mod)
|
||||
# Before proceeding, ensure that specs and packages are consistent
|
||||
#
|
||||
# This is a confusing behavior due to how packages are
|
||||
# constructed. `setup_dependent_package` may set attributes on
|
||||
# specs in the DAG for use by other packages' install
|
||||
# method. However, spec.package will look up a package via
|
||||
# spack.repo, which defensively copies specs into packages. This
|
||||
# code ensures that all packages in the DAG have pieces of the
|
||||
# same spec object at build time.
|
||||
#
|
||||
# This is safe for the build process, b/c the build process is a
|
||||
# throwaway environment, but it is kind of dirty.
|
||||
#
|
||||
# TODO: Think about how to avoid this fix and do something cleaner.
|
||||
for s in pkg.spec.traverse(): s.package.spec = s
|
||||
|
||||
# Allow dependencies to set up environment as well.
|
||||
for dep_spec in pkg.spec.traverse(root=False):
|
||||
dep_spec.package.setup_dependent_environment(
|
||||
pkg.module, dep_spec, pkg.spec)
|
||||
set_compiler_environment_variables(pkg, spack_env)
|
||||
set_build_environment_variables(pkg, spack_env)
|
||||
setup_module_variables_for_dag(pkg)
|
||||
|
||||
# Allow dependencies to modify the module
|
||||
spec = pkg.spec
|
||||
for dependency_spec in spec.traverse(root=False):
|
||||
dpkg = dependency_spec.package
|
||||
dpkg.setup_dependent_package(pkg.module, spec)
|
||||
|
||||
# Allow dependencies to set up environment as well
|
||||
for dependency_spec in spec.traverse(root=False):
|
||||
dpkg = dependency_spec.package
|
||||
dpkg.setup_dependent_environment(spack_env, run_env, spec)
|
||||
|
||||
# Allow the package to apply some settings.
|
||||
pkg.setup_environment(spack_env, run_env)
|
||||
|
||||
# Make sure nothing's strange about the Spack environment.
|
||||
validate(spack_env, tty.warn)
|
||||
spack_env.apply_modifications()
|
||||
|
||||
|
||||
def fork(pkg, function):
|
||||
@@ -296,23 +343,23 @@ def child_fun():
|
||||
# do stuff
|
||||
build_env.fork(pkg, child_fun)
|
||||
|
||||
Forked processes are run with the build environemnt set up by
|
||||
Forked processes are run with the build environment set up by
|
||||
spack.build_environment. This allows package authors to have
|
||||
full control over the environment, etc. without offecting
|
||||
full control over the environment, etc. without affecting
|
||||
other builds that might be executed in the same spack call.
|
||||
|
||||
If something goes wrong, the child process is expected toprint
|
||||
If something goes wrong, the child process is expected to print
|
||||
the error and the parent process will exit with error as
|
||||
well. If things go well, the child exits and the parent
|
||||
carries on.
|
||||
"""
|
||||
try:
|
||||
pid = os.fork()
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
raise InstallError("Unable to fork build process: %s" % e)
|
||||
|
||||
if pid == 0:
|
||||
# Give the child process the package's build environemnt.
|
||||
# Give the child process the package's build environment.
|
||||
setup_package(pkg)
|
||||
|
||||
try:
|
||||
@@ -323,7 +370,7 @@ def child_fun():
|
||||
# which interferes with unit tests.
|
||||
os._exit(0)
|
||||
|
||||
except spack.error.SpackError, e:
|
||||
except spack.error.SpackError as e:
|
||||
e.die()
|
||||
|
||||
except:
|
||||
@@ -338,8 +385,7 @@ def child_fun():
|
||||
# message. Just make the parent exit with an error code.
|
||||
pid, returncode = os.waitpid(pid, 0)
|
||||
if returncode != 0:
|
||||
raise InstallError("Installation process had nonzero exit code."
|
||||
.format(str(returncode)))
|
||||
raise InstallError("Installation process had nonzero exit code.".format(str(returncode)))
|
||||
|
||||
|
||||
class InstallError(spack.error.SpackError):
|
||||
|
@@ -208,7 +208,7 @@ def find_repository(spec, args):
|
||||
return repo
|
||||
|
||||
|
||||
def fetch_tarballs(url, name, args):
|
||||
def fetch_tarballs(url, name, version):
|
||||
"""Try to find versions of the supplied archive by scraping the web.
|
||||
|
||||
Prompts the user to select how many to download if many are found.
|
||||
@@ -222,7 +222,7 @@ def fetch_tarballs(url, name, args):
|
||||
archives_to_fetch = 1
|
||||
if not versions:
|
||||
# If the fetch failed for some reason, revert to what the user provided
|
||||
versions = { "version" : url }
|
||||
versions = { version : url }
|
||||
elif len(versions) > 1:
|
||||
tty.msg("Found %s versions of %s:" % (len(versions), name),
|
||||
*spack.cmd.elide_list(
|
||||
@@ -256,7 +256,7 @@ def create(parser, args):
|
||||
tty.msg("Creating template for package %s" % name)
|
||||
|
||||
# Fetch tarballs (prompting user if necessary)
|
||||
versions, urls = fetch_tarballs(url, name, args)
|
||||
versions, urls = fetch_tarballs(url, name, version)
|
||||
|
||||
# Try to guess what configure system is used.
|
||||
guesser = ConfigureGuesser()
|
||||
|
@@ -75,8 +75,8 @@ def diy(self, args):
|
||||
edit_package(spec.name, spack.repo.first_repo(), None, True)
|
||||
return
|
||||
|
||||
if not spec.version.concrete:
|
||||
tty.die("spack diy spec must have a single, concrete version.")
|
||||
if not spec.versions.concrete:
|
||||
tty.die("spack diy spec must have a single, concrete version. Did you forget a package version number?")
|
||||
|
||||
spec.concretize()
|
||||
package = spack.repo.get(spec)
|
||||
|
@@ -80,7 +80,7 @@ def module_find(mtype, spec_array):
|
||||
if not os.path.isfile(mod.file_name):
|
||||
tty.die("No %s module is installed for %s" % (mtype, spec))
|
||||
|
||||
print mod.use_name
|
||||
print(mod.use_name)
|
||||
|
||||
|
||||
def module_refresh():
|
||||
|
@@ -22,6 +22,7 @@
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
@@ -63,12 +64,12 @@ def uninstall(parser, args):
|
||||
matching_specs = spack.installed_db.query(spec)
|
||||
if not args.all and len(matching_specs) > 1:
|
||||
tty.error("%s matches multiple packages:" % spec)
|
||||
print
|
||||
print()
|
||||
display_specs(matching_specs, long=True)
|
||||
print
|
||||
print "You can either:"
|
||||
print " a) Use a more specific spec, or"
|
||||
print " b) use spack uninstall -a to uninstall ALL matching specs."
|
||||
print()
|
||||
print("You can either:")
|
||||
print(" a) Use a more specific spec, or")
|
||||
print(" b) use spack uninstall -a to uninstall ALL matching specs.")
|
||||
sys.exit(1)
|
||||
|
||||
if len(matching_specs) == 0:
|
||||
@@ -79,7 +80,7 @@ def uninstall(parser, args):
|
||||
try:
|
||||
# should work if package is known to spack
|
||||
pkgs.append(s.package)
|
||||
except spack.repository.UnknownPackageError, e:
|
||||
except spack.repository.UnknownPackageError as e:
|
||||
# The package.py file has gone away -- but still
|
||||
# want to uninstall.
|
||||
spack.Package(s).do_uninstall(force=True)
|
||||
@@ -94,11 +95,11 @@ def num_installed_deps(pkg):
|
||||
for pkg in pkgs:
|
||||
try:
|
||||
pkg.do_uninstall(force=args.force)
|
||||
except PackageStillNeededError, e:
|
||||
except PackageStillNeededError as e:
|
||||
tty.error("Will not uninstall %s" % e.spec.format("$_$@$%@$#", color=True))
|
||||
print
|
||||
print "The following packages depend on it:"
|
||||
print('')
|
||||
print("The following packages depend on it:")
|
||||
display_specs(e.dependents, long=True)
|
||||
print
|
||||
print "You can use spack uninstall -f to force this action."
|
||||
print('')
|
||||
print("You can use spack uninstall -f to force this action.")
|
||||
sys.exit(1)
|
||||
|
@@ -40,7 +40,8 @@ class Gcc(Compiler):
|
||||
fc_names = ['gfortran']
|
||||
|
||||
# MacPorts builds gcc versions with prefixes and -mp-X.Y suffixes.
|
||||
suffixes = [r'-mp-\d\.\d']
|
||||
# Homebrew and Linuxes may build gcc with -X, -X.Y suffixes
|
||||
suffixes = [r'-mp-\d\.\d', r'-\d\.\d', r'-\d']
|
||||
|
||||
# Named wrapper links within spack.build_env_path
|
||||
link_paths = {'cc' : 'gcc/gcc',
|
||||
|
@@ -51,10 +51,10 @@ class DefaultConcretizer(object):
|
||||
"""
|
||||
|
||||
def _valid_virtuals_and_externals(self, spec):
|
||||
"""Returns a list of spec/external-path pairs for both virtuals and externals
|
||||
that can concretize this spec."""
|
||||
# Get a list of candidate packages that could satisfy this spec
|
||||
packages = []
|
||||
"""Returns a list of candidate virtual dep providers and external
|
||||
packages that coiuld be used to concretize a spec."""
|
||||
# First construct a list of concrete candidates to replace spec with.
|
||||
candidates = [spec]
|
||||
if spec.virtual:
|
||||
providers = spack.repo.providers_for(spec)
|
||||
if not providers:
|
||||
@@ -64,96 +64,72 @@ def _valid_virtuals_and_externals(self, spec):
|
||||
if not spec_w_preferred_providers:
|
||||
spec_w_preferred_providers = spec
|
||||
provider_cmp = partial(spack.pkgsort.provider_compare, spec_w_preferred_providers.name, spec.name)
|
||||
packages = sorted(providers, cmp=provider_cmp)
|
||||
else:
|
||||
packages = [spec]
|
||||
candidates = sorted(providers, cmp=provider_cmp)
|
||||
|
||||
# For each candidate package, if it has externals add those to the candidates
|
||||
# if it's not buildable, then only add the externals.
|
||||
candidates = []
|
||||
all_compilers = spack.compilers.all_compilers()
|
||||
for pkg in packages:
|
||||
externals = spec_externals(pkg)
|
||||
buildable = is_spec_buildable(pkg)
|
||||
if buildable:
|
||||
candidates.append((pkg, None))
|
||||
# For each candidate package, if it has externals, add those to the usable list.
|
||||
# if it's not buildable, then *only* add the externals.
|
||||
usable = []
|
||||
for cspec in candidates:
|
||||
if is_spec_buildable(cspec):
|
||||
usable.append(cspec)
|
||||
externals = spec_externals(cspec)
|
||||
for ext in externals:
|
||||
if ext[0].satisfies(spec):
|
||||
candidates.append(ext)
|
||||
if not candidates:
|
||||
if ext.satisfies(spec):
|
||||
usable.append(ext)
|
||||
|
||||
# If nothing is in the usable list now, it's because we aren't
|
||||
# allowed to build anything.
|
||||
if not usable:
|
||||
raise NoBuildError(spec)
|
||||
|
||||
def cmp_externals(a, b):
|
||||
if a[0].name != b[0].name:
|
||||
#We're choosing between different providers. Maintain order from above sort
|
||||
if a.name != b.name:
|
||||
# We're choosing between different providers, so
|
||||
# maintain order from provider sort
|
||||
return candidates.index(a) - candidates.index(b)
|
||||
result = cmp_specs(a[0], b[0])
|
||||
|
||||
result = cmp_specs(a, b)
|
||||
if result != 0:
|
||||
return result
|
||||
if not a[1] and b[1]:
|
||||
return 1
|
||||
if not b[1] and a[1]:
|
||||
return -1
|
||||
return cmp(a[1], b[1])
|
||||
|
||||
candidates = sorted(candidates, cmp=cmp_externals)
|
||||
return candidates
|
||||
# prefer external packages to internal packages.
|
||||
if a.external is None or b.external is None:
|
||||
return -cmp(a.external, b.external)
|
||||
else:
|
||||
return cmp(a.external, b.external)
|
||||
|
||||
usable.sort(cmp=cmp_externals)
|
||||
return usable
|
||||
|
||||
|
||||
def concretize_virtual_and_external(self, spec):
|
||||
"""From a list of candidate virtual and external packages, concretize to one that
|
||||
is ABI compatible with the rest of the DAG."""
|
||||
def choose_virtual_or_external(self, spec):
|
||||
"""Given a list of candidate virtual and external packages, try to
|
||||
find one that is most ABI compatible.
|
||||
"""
|
||||
candidates = self._valid_virtuals_and_externals(spec)
|
||||
if not candidates:
|
||||
return False
|
||||
return candidates
|
||||
|
||||
# Find the nearest spec in the dag that has a compiler. We'll use that
|
||||
# spec to test compiler compatibility.
|
||||
other_spec = find_spec(spec, lambda(x): x.compiler)
|
||||
if not other_spec:
|
||||
other_spec = spec.root
|
||||
# Find the nearest spec in the dag that has a compiler. We'll
|
||||
# use that spec to calibrate compiler compatibility.
|
||||
abi_exemplar = find_spec(spec, lambda(x): x.compiler)
|
||||
if not abi_exemplar:
|
||||
abi_exemplar = spec.root
|
||||
|
||||
# Choose an ABI-compatible candidate, or the first match otherwise.
|
||||
candidate = None
|
||||
if other_spec:
|
||||
candidate = next((c for c in candidates if spack.abi.compatible(c[0], other_spec)), None)
|
||||
if not candidate:
|
||||
# Try a looser ABI matching
|
||||
candidate = next((c for c in candidates if spack.abi.compatible(c[0], other_spec, loose=True)), None)
|
||||
if not candidate:
|
||||
# No ABI matches. Pick the top choice based on the orignal preferences.
|
||||
candidate = candidates[0]
|
||||
candidate_spec = candidate[0]
|
||||
external = candidate[1]
|
||||
changed = False
|
||||
# Make a list including ABI compatibility of specs with the exemplar.
|
||||
strict = [spack.abi.compatible(c, abi_exemplar) for c in candidates]
|
||||
loose = [spack.abi.compatible(c, abi_exemplar, loose=True) for c in candidates]
|
||||
keys = zip(strict, loose, candidates)
|
||||
|
||||
# If we're external then trim the dependencies
|
||||
if external:
|
||||
if (spec.dependencies):
|
||||
changed = True
|
||||
spec.dependencies = DependencyMap()
|
||||
candidate_spec.dependencies = DependencyMap()
|
||||
# Sort candidates from most to least compatibility.
|
||||
# Note:
|
||||
# 1. We reverse because True > False.
|
||||
# 2. Sort is stable, so c's keep their order.
|
||||
keys.sort(key=lambda k:k[:2], reverse=True)
|
||||
|
||||
def fequal(candidate_field, spec_field):
|
||||
return (not candidate_field) or (candidate_field == spec_field)
|
||||
if (fequal(candidate_spec.name, spec.name) and
|
||||
fequal(candidate_spec.versions, spec.versions) and
|
||||
fequal(candidate_spec.compiler, spec.compiler) and
|
||||
fequal(candidate_spec.architecture, spec.architecture) and
|
||||
fequal(candidate_spec.dependencies, spec.dependencies) and
|
||||
fequal(candidate_spec.variants, spec.variants) and
|
||||
fequal(external, spec.external)):
|
||||
return changed
|
||||
|
||||
# Refine this spec to the candidate.
|
||||
if spec.virtual:
|
||||
spec._replace_with(candidate_spec)
|
||||
changed = True
|
||||
if spec._dup(candidate_spec, deps=False, cleardeps=False):
|
||||
changed = True
|
||||
spec.external = external
|
||||
|
||||
return changed
|
||||
# Pull the candidates back out and return them in order
|
||||
candidates = [c for s,l,c in keys]
|
||||
return candidates
|
||||
|
||||
|
||||
def concretize_version(self, spec):
|
||||
@@ -183,6 +159,10 @@ def concretize_version(self, spec):
|
||||
if any(v.satisfies(sv) for sv in spec.versions)],
|
||||
cmp=cmp_versions)
|
||||
|
||||
def prefer_key(v):
|
||||
return pkg.versions.get(Version(v)).get('preferred', False)
|
||||
valid_versions.sort(key=prefer_key, reverse=True)
|
||||
|
||||
if valid_versions:
|
||||
spec.versions = ver([valid_versions[0]])
|
||||
else:
|
||||
@@ -238,7 +218,7 @@ def concretize_variants(self, spec):
|
||||
the default variants from the package specification.
|
||||
"""
|
||||
changed = False
|
||||
for name, variant in spec.package.variants.items():
|
||||
for name, variant in spec.package_class.variants.items():
|
||||
if name not in spec.variants:
|
||||
spec.variants[name] = spack.spec.VariantSpec(name, variant.default)
|
||||
changed = True
|
||||
@@ -265,7 +245,7 @@ def concretize_compiler(self, spec):
|
||||
return False
|
||||
|
||||
#Find the another spec that has a compiler, or the root if none do
|
||||
other_spec = find_spec(spec, lambda(x) : x.compiler)
|
||||
other_spec = spec if spec.compiler else find_spec(spec, lambda(x) : x.compiler)
|
||||
if not other_spec:
|
||||
other_spec = spec.root
|
||||
other_compiler = other_spec.compiler
|
||||
@@ -312,7 +292,7 @@ def find_spec(spec, condition):
|
||||
if condition(spec):
|
||||
return spec
|
||||
|
||||
return None # Nohting matched the condition.
|
||||
return None # Nothing matched the condition.
|
||||
|
||||
|
||||
def cmp_specs(lhs, rhs):
|
||||
|
@@ -237,7 +237,29 @@
|
||||
'type' : 'object',
|
||||
'default' : {},
|
||||
}
|
||||
},},},},},}
|
||||
},},},},},},
|
||||
'modules': {
|
||||
'$schema': 'http://json-schema.org/schema#',
|
||||
'title': 'Spack module file configuration file schema',
|
||||
'type': 'object',
|
||||
'additionalProperties': False,
|
||||
'patternProperties': {
|
||||
r'modules:?': {
|
||||
'type': 'object',
|
||||
'default': {},
|
||||
'additionalProperties': False,
|
||||
'properties': {
|
||||
'enable': {
|
||||
'type': 'array',
|
||||
'default': [],
|
||||
'items': {
|
||||
'type': 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
"""OrderedDict of config scopes keyed by name.
|
||||
@@ -405,11 +427,11 @@ def _read_config_file(filename, schema):
|
||||
validate_section(data, schema)
|
||||
return data
|
||||
|
||||
except MarkedYAMLError, e:
|
||||
except MarkedYAMLError as e:
|
||||
raise ConfigFileError(
|
||||
"Error parsing yaml%s: %s" % (str(e.context_mark), e.problem))
|
||||
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
raise ConfigFileError(
|
||||
"Error reading configuration file %s: %s" % (filename, str(e)))
|
||||
|
||||
@@ -539,22 +561,25 @@ def print_section(section):
|
||||
|
||||
|
||||
def spec_externals(spec):
|
||||
"""Return a list of spec, directory pairs for each external location for spec"""
|
||||
"""Return a list of external specs (with external directory path filled in),
|
||||
one for each known external installation."""
|
||||
allpkgs = get_config('packages')
|
||||
name = spec.name
|
||||
spec_locations = []
|
||||
|
||||
external_specs = []
|
||||
pkg_paths = allpkgs.get(name, {}).get('paths', None)
|
||||
if not pkg_paths:
|
||||
return []
|
||||
|
||||
for pkg,path in pkg_paths.iteritems():
|
||||
if not spec.satisfies(pkg):
|
||||
continue
|
||||
for external_spec, path in pkg_paths.iteritems():
|
||||
if not path:
|
||||
# skip entries without paths (avoid creating extra Specs)
|
||||
continue
|
||||
spec_locations.append( (spack.spec.Spec(pkg), path) )
|
||||
return spec_locations
|
||||
|
||||
external_spec = spack.spec.Spec(external_spec, external=path)
|
||||
if external_spec.satisfies(spec):
|
||||
external_specs.append(external_spec)
|
||||
return external_specs
|
||||
|
||||
|
||||
def is_spec_buildable(spec):
|
||||
|
@@ -150,7 +150,7 @@ def remove_install_directory(self, spec):
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except exceptions.OSError, e:
|
||||
except exceptions.OSError as e:
|
||||
raise RemoveFailedError(spec, path, e)
|
||||
|
||||
path = os.path.dirname(path)
|
||||
|
252
lib/spack/spack/environment.py
Normal file
252
lib/spack/spack/environment.py
Normal file
@@ -0,0 +1,252 @@
|
||||
import os
|
||||
import os.path
|
||||
import collections
|
||||
import inspect
|
||||
|
||||
|
||||
class NameModifier(object):
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
self.args = {'name': name}
|
||||
self.args.update(kwargs)
|
||||
|
||||
|
||||
class NameValueModifier(object):
|
||||
def __init__(self, name, value, **kwargs):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.args = {'name': name, 'value': value}
|
||||
self.args.update(kwargs)
|
||||
|
||||
|
||||
class SetEnv(NameValueModifier):
|
||||
def execute(self):
|
||||
os.environ[self.name] = str(self.value)
|
||||
|
||||
|
||||
class UnsetEnv(NameModifier):
|
||||
def execute(self):
|
||||
os.environ.pop(self.name, None) # Avoid throwing if the variable was not set
|
||||
|
||||
|
||||
class SetPath(NameValueModifier):
|
||||
def execute(self):
|
||||
string_path = concatenate_paths(self.value)
|
||||
os.environ[self.name] = string_path
|
||||
|
||||
|
||||
class AppendPath(NameValueModifier):
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(':') if environment_value else []
|
||||
directories.append(os.path.normpath(self.value))
|
||||
os.environ[self.name] = ':'.join(directories)
|
||||
|
||||
|
||||
class PrependPath(NameValueModifier):
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(':') if environment_value else []
|
||||
directories = [os.path.normpath(self.value)] + directories
|
||||
os.environ[self.name] = ':'.join(directories)
|
||||
|
||||
|
||||
class RemovePath(NameValueModifier):
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(':') if environment_value else []
|
||||
directories = [os.path.normpath(x) for x in directories if x != os.path.normpath(self.value)]
|
||||
os.environ[self.name] = ':'.join(directories)
|
||||
|
||||
|
||||
class EnvironmentModifications(object):
|
||||
"""
|
||||
Keeps track of requests to modify the current environment.
|
||||
|
||||
Each call to a method to modify the environment stores the extra information on the caller in the request:
|
||||
- 'filename' : filename of the module where the caller is defined
|
||||
- 'lineno': line number where the request occurred
|
||||
- 'context' : line of code that issued the request that failed
|
||||
"""
|
||||
|
||||
def __init__(self, other=None):
|
||||
"""
|
||||
Initializes a new instance, copying commands from other if it is not None
|
||||
|
||||
Args:
|
||||
other: another instance of EnvironmentModifications from which (optional)
|
||||
"""
|
||||
self.env_modifications = []
|
||||
if other is not None:
|
||||
self.extend(other)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.env_modifications)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.env_modifications)
|
||||
|
||||
def extend(self, other):
|
||||
self._check_other(other)
|
||||
self.env_modifications.extend(other.env_modifications)
|
||||
|
||||
@staticmethod
|
||||
def _check_other(other):
|
||||
if not isinstance(other, EnvironmentModifications):
|
||||
raise TypeError('other must be an instance of EnvironmentModifications')
|
||||
|
||||
def _get_outside_caller_attributes(self):
|
||||
stack = inspect.stack()
|
||||
try:
|
||||
_, filename, lineno, _, context, index = stack[2]
|
||||
context = context[index].strip()
|
||||
except Exception:
|
||||
filename, lineno, context = 'unknown file', 'unknown line', 'unknown context'
|
||||
args = {
|
||||
'filename': filename,
|
||||
'lineno': lineno,
|
||||
'context': context
|
||||
}
|
||||
return args
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
"""
|
||||
Stores in the current object a request to set an environment variable
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be set
|
||||
value: value of the environment variable
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = SetEnv(name, value, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def unset(self, name, **kwargs):
|
||||
"""
|
||||
Stores in the current object a request to unset an environment variable
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be set
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = UnsetEnv(name, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def set_path(self, name, elts, **kwargs):
|
||||
"""
|
||||
Stores a request to set a path generated from a list.
|
||||
|
||||
Args:
|
||||
name: name o the environment variable to be set.
|
||||
elts: elements of the path to set.
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = SetPath(name, elts, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def append_path(self, name, path, **kwargs):
|
||||
"""
|
||||
Stores in the current object a request to append a path to a path list
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be appended
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = AppendPath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def prepend_path(self, name, path, **kwargs):
|
||||
"""
|
||||
Same as `append_path`, but the path is pre-pended
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be pre-pended
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = PrependPath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def remove_path(self, name, path, **kwargs):
|
||||
"""
|
||||
Stores in the current object a request to remove a path from a path list
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be removed
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = RemovePath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def group_by_name(self):
|
||||
"""
|
||||
Returns a dict of the modifications grouped by variable name
|
||||
|
||||
Returns:
|
||||
dict mapping the environment variable name to the modifications to be done on it
|
||||
"""
|
||||
modifications = collections.defaultdict(list)
|
||||
for item in self:
|
||||
modifications[item.name].append(item)
|
||||
return modifications
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clears the current list of modifications
|
||||
"""
|
||||
self.env_modifications.clear()
|
||||
|
||||
def apply_modifications(self):
|
||||
"""
|
||||
Applies the modifications and clears the list
|
||||
"""
|
||||
modifications = self.group_by_name()
|
||||
# Apply the modifications to the environment variables one variable at a time
|
||||
for name, actions in sorted(modifications.items()):
|
||||
for x in actions:
|
||||
x.execute()
|
||||
|
||||
|
||||
def concatenate_paths(paths):
|
||||
"""
|
||||
Concatenates an iterable of paths into a string of column separated paths
|
||||
|
||||
Args:
|
||||
paths: iterable of paths
|
||||
|
||||
Returns:
|
||||
string
|
||||
"""
|
||||
return ':'.join(str(item) for item in paths)
|
||||
|
||||
|
||||
def set_or_unset_not_first(variable, changes, errstream):
|
||||
"""
|
||||
Check if we are going to set or unset something after other modifications have already been requested
|
||||
"""
|
||||
indexes = [ii for ii, item in enumerate(changes) if ii != 0 and type(item) in [SetEnv, UnsetEnv]]
|
||||
if indexes:
|
||||
good = '\t \t{context} at {filename}:{lineno}'
|
||||
nogood = '\t--->\t{context} at {filename}:{lineno}'
|
||||
errstream('Suspicious requests to set or unset the variable \'{var}\' found'.format(var=variable))
|
||||
for ii, item in enumerate(changes):
|
||||
print_format = nogood if ii in indexes else good
|
||||
errstream(print_format.format(**item.args))
|
||||
|
||||
|
||||
def validate(env, errstream):
|
||||
"""
|
||||
Validates the environment modifications to check for the presence of suspicious patterns. Prompts a warning for
|
||||
everything that was found
|
||||
|
||||
Current checks:
|
||||
- set or unset variables after other changes on the same variable
|
||||
|
||||
Args:
|
||||
env: list of environment modifications
|
||||
"""
|
||||
modifications = env.group_by_name()
|
||||
for variable, list_of_changes in sorted(modifications.items()):
|
||||
set_or_unset_not_first(variable, list_of_changes, errstream)
|
@@ -22,14 +22,12 @@
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
"""This module contains code for creating environment modules, which
|
||||
can include dotkits, tcl modules, lmod, and others.
|
||||
"""
|
||||
This module contains code for creating environment modules, which can include dotkits, tcl modules, lmod, and others.
|
||||
|
||||
The various types of modules are installed by post-install hooks and
|
||||
removed after an uninstall by post-uninstall hooks. This class
|
||||
consolidates the logic for creating an abstract description of the
|
||||
information that module systems need. Currently that includes a
|
||||
number of directories to be appended to paths in the user's environment:
|
||||
The various types of modules are installed by post-install hooks and removed after an uninstall by post-uninstall hooks.
|
||||
This class consolidates the logic for creating an abstract description of the information that module systems need.
|
||||
Currently that includes a number of directories to be appended to paths in the user's environment:
|
||||
|
||||
* /bin directories to be appended to PATH
|
||||
* /lib* directories for LD_LIBRARY_PATH
|
||||
@@ -37,30 +35,30 @@
|
||||
* /man* and /share/man* directories for MANPATH
|
||||
* the package prefix for CMAKE_PREFIX_PATH
|
||||
|
||||
This module also includes logic for coming up with unique names for
|
||||
the module files so that they can be found by the various
|
||||
shell-support files in $SPACK/share/spack/setup-env.*.
|
||||
This module also includes logic for coming up with unique names for the module files so that they can be found by the
|
||||
various shell-support files in $SPACK/share/spack/setup-env.*.
|
||||
|
||||
Each hook in hooks/ implements the logic for writing its specific type
|
||||
of module file.
|
||||
Each hook in hooks/ implements the logic for writing its specific type of module file.
|
||||
"""
|
||||
__all__ = ['EnvModule', 'Dotkit', 'TclModule']
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import textwrap
|
||||
import shutil
|
||||
from glob import glob
|
||||
import textwrap
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import join_path, mkdirp
|
||||
|
||||
import spack
|
||||
import spack.config
|
||||
from llnl.util.filesystem import join_path, mkdirp
|
||||
from spack.environment import *
|
||||
|
||||
"""Registry of all types of modules. Entries created by EnvModule's
|
||||
metaclass."""
|
||||
__all__ = ['EnvModule', 'Dotkit', 'TclModule']
|
||||
|
||||
# Registry of all types of modules. Entries created by EnvModule's metaclass
|
||||
module_types = {}
|
||||
|
||||
CONFIGURATION = spack.config.get_config('modules')
|
||||
|
||||
|
||||
def print_help():
|
||||
"""For use by commands to tell user how to activate shell support."""
|
||||
@@ -79,75 +77,76 @@ def print_help():
|
||||
"")
|
||||
|
||||
|
||||
def inspect_path(prefix):
|
||||
"""
|
||||
Inspects the prefix of an installation to search for common layouts. Issues a request to modify the environment
|
||||
accordingly when an item is found.
|
||||
|
||||
Args:
|
||||
prefix: prefix of the installation
|
||||
|
||||
Returns:
|
||||
instance of EnvironmentModifications containing the requested modifications
|
||||
"""
|
||||
env = EnvironmentModifications()
|
||||
# Inspect the prefix to check for the existence of common directories
|
||||
prefix_inspections = {
|
||||
'bin': ('PATH',),
|
||||
'man': ('MANPATH',),
|
||||
'lib': ('LIBRARY_PATH', 'LD_LIBRARY_PATH'),
|
||||
'lib64': ('LIBRARY_PATH', 'LD_LIBRARY_PATH'),
|
||||
'include': ('CPATH',)
|
||||
}
|
||||
for attribute, variables in prefix_inspections.items():
|
||||
expected = getattr(prefix, attribute)
|
||||
if os.path.isdir(expected):
|
||||
for variable in variables:
|
||||
env.prepend_path(variable, expected)
|
||||
# PKGCONFIG
|
||||
for expected in (join_path(prefix.lib, 'pkgconfig'), join_path(prefix.lib64, 'pkgconfig')):
|
||||
if os.path.isdir(expected):
|
||||
env.prepend_path('PKG_CONFIG_PATH', expected)
|
||||
# CMake related variables
|
||||
env.prepend_path('CMAKE_PREFIX_PATH', prefix)
|
||||
return env
|
||||
|
||||
|
||||
class EnvModule(object):
|
||||
name = 'env_module'
|
||||
formats = {}
|
||||
|
||||
class __metaclass__(type):
|
||||
def __init__(cls, name, bases, dict):
|
||||
type.__init__(cls, name, bases, dict)
|
||||
if cls.name != 'env_module':
|
||||
if cls.name != 'env_module' and cls.name in CONFIGURATION['enable']:
|
||||
module_types[cls.name] = cls
|
||||
|
||||
|
||||
def __init__(self, spec=None):
|
||||
# category in the modules system
|
||||
# TODO: come up with smarter category names.
|
||||
self.category = "spack"
|
||||
|
||||
# Descriptions for the module system's UI
|
||||
self.short_description = ""
|
||||
self.long_description = ""
|
||||
|
||||
# dict pathname -> list of directories to be prepended to in
|
||||
# the module file.
|
||||
self._paths = None
|
||||
self.spec = spec
|
||||
self.pkg = spec.package # Just stored for convenience
|
||||
|
||||
# short description default is just the package + version
|
||||
# packages can provide this optional attribute
|
||||
self.short_description = spec.format("$_ $@")
|
||||
if hasattr(self.pkg, 'short_description'):
|
||||
self.short_description = self.pkg.short_description
|
||||
|
||||
# long description is the docstring with reduced whitespace.
|
||||
self.long_description = None
|
||||
if self.spec.package.__doc__:
|
||||
self.long_description = re.sub(r'\s+', ' ', self.spec.package.__doc__)
|
||||
|
||||
|
||||
@property
|
||||
def paths(self):
|
||||
if self._paths is None:
|
||||
self._paths = {}
|
||||
|
||||
def add_path(path_name, directory):
|
||||
path = self._paths.setdefault(path_name, [])
|
||||
path.append(directory)
|
||||
|
||||
# Add paths if they exist.
|
||||
for var, directory in [
|
||||
('PATH', self.spec.prefix.bin),
|
||||
('MANPATH', self.spec.prefix.man),
|
||||
('MANPATH', self.spec.prefix.share_man),
|
||||
('LIBRARY_PATH', self.spec.prefix.lib),
|
||||
('LIBRARY_PATH', self.spec.prefix.lib64),
|
||||
('LD_LIBRARY_PATH', self.spec.prefix.lib),
|
||||
('LD_LIBRARY_PATH', self.spec.prefix.lib64),
|
||||
('CPATH', self.spec.prefix.include),
|
||||
('PKG_CONFIG_PATH', join_path(self.spec.prefix.lib, 'pkgconfig')),
|
||||
('PKG_CONFIG_PATH', join_path(self.spec.prefix.lib64, 'pkgconfig'))]:
|
||||
|
||||
if os.path.isdir(directory):
|
||||
add_path(var, directory)
|
||||
|
||||
# Add python path unless it's an actual python installation
|
||||
# TODO: is there a better way to do this?
|
||||
if self.spec.name != 'python':
|
||||
site_packages = glob(join_path(self.spec.prefix.lib, "python*/site-packages"))
|
||||
if site_packages:
|
||||
add_path('PYTHONPATH', site_packages[0])
|
||||
|
||||
if self.spec.package.extends(spack.spec.Spec('ruby')):
|
||||
add_path('GEM_PATH', self.spec.prefix)
|
||||
|
||||
# short description is just the package + version
|
||||
# TODO: maybe packages can optionally provide it.
|
||||
self.short_description = self.spec.format("$_ $@")
|
||||
|
||||
# long description is the docstring with reduced whitespace.
|
||||
if self.spec.package.__doc__:
|
||||
self.long_description = re.sub(r'\s+', ' ', self.spec.package.__doc__)
|
||||
|
||||
return self._paths
|
||||
def category(self):
|
||||
# Anything defined at the package level takes precedence
|
||||
if hasattr(self.pkg, 'category'):
|
||||
return self.pkg.category
|
||||
# Extensions
|
||||
for extendee in self.pkg.extendees:
|
||||
return '{extendee} extension'.format(extendee=extendee)
|
||||
# Not very descriptive fallback
|
||||
return 'spack installed package'
|
||||
|
||||
|
||||
def write(self):
|
||||
@@ -156,18 +155,41 @@ def write(self):
|
||||
if not os.path.exists(module_dir):
|
||||
mkdirp(module_dir)
|
||||
|
||||
# If there are no paths, no need for a dotkit.
|
||||
if not self.paths:
|
||||
# Environment modifications guessed by inspecting the
|
||||
# installation prefix
|
||||
env = inspect_path(self.spec.prefix)
|
||||
|
||||
# Let the extendee modify their extensions before asking for
|
||||
# package-specific modifications
|
||||
spack_env = EnvironmentModifications()
|
||||
for item in self.pkg.extendees:
|
||||
package = self.spec[item].package
|
||||
package.setup_dependent_package(self.pkg.module, self.spec)
|
||||
package.setup_dependent_environment(spack_env, env, self.spec)
|
||||
|
||||
# Package-specific environment modifications
|
||||
self.spec.package.setup_environment(spack_env, env)
|
||||
|
||||
# TODO : implement site-specific modifications and filters
|
||||
if not env:
|
||||
return
|
||||
|
||||
with open(self.file_name, 'w') as f:
|
||||
self._write(f)
|
||||
self.write_header(f)
|
||||
for line in self.process_environment_command(env):
|
||||
f.write(line)
|
||||
|
||||
|
||||
def _write(self, stream):
|
||||
"""To be implemented by subclasses."""
|
||||
def write_header(self, stream):
|
||||
raise NotImplementedError()
|
||||
|
||||
def process_environment_command(self, env):
|
||||
for command in env:
|
||||
try:
|
||||
yield self.formats[type(command)].format(**command.args)
|
||||
except KeyError:
|
||||
tty.warn('Cannot handle command of type {command} : skipping request'.format(command=type(command)))
|
||||
tty.warn('{context} at {filename}:{lineno}'.format(**command.args))
|
||||
|
||||
|
||||
@property
|
||||
def file_name(self):
|
||||
@@ -175,14 +197,12 @@ def file_name(self):
|
||||
where this module lives."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@property
|
||||
def use_name(self):
|
||||
"""Subclasses should implement this to return the name the
|
||||
module command uses to refer to the package."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def remove(self):
|
||||
mod_file = self.file_name
|
||||
if os.path.exists(mod_file):
|
||||
@@ -193,19 +213,23 @@ class Dotkit(EnvModule):
|
||||
name = 'dotkit'
|
||||
path = join_path(spack.share_path, "dotkit")
|
||||
|
||||
formats = {
|
||||
PrependPath: 'dk_alter {name} {value}\n',
|
||||
SetEnv: 'dk_setenv {name} {value}\n'
|
||||
}
|
||||
|
||||
@property
|
||||
def file_name(self):
|
||||
return join_path(Dotkit.path, self.spec.architecture,
|
||||
'%s.dk' % self.use_name)
|
||||
return join_path(Dotkit.path, self.spec.architecture, '%s.dk' % self.use_name)
|
||||
|
||||
@property
|
||||
def use_name(self):
|
||||
return "%s-%s-%s-%s-%s" % (self.spec.name, self.spec.version,
|
||||
self.spec.compiler.name,
|
||||
self.spec.compiler.version,
|
||||
self.spec.compiler.version,
|
||||
self.spec.dag_hash())
|
||||
|
||||
def _write(self, dk_file):
|
||||
def write_header(self, dk_file):
|
||||
# Category
|
||||
if self.category:
|
||||
dk_file.write('#c %s\n' % self.category)
|
||||
@@ -219,50 +243,41 @@ def _write(self, dk_file):
|
||||
for line in textwrap.wrap(self.long_description, 72):
|
||||
dk_file.write("#h %s\n" % line)
|
||||
|
||||
# Path alterations
|
||||
for var, dirs in self.paths.items():
|
||||
for directory in dirs:
|
||||
dk_file.write("dk_alter %s %s\n" % (var, directory))
|
||||
|
||||
# Let CMake find this package.
|
||||
dk_file.write("dk_alter CMAKE_PREFIX_PATH %s\n" % self.spec.prefix)
|
||||
|
||||
|
||||
class TclModule(EnvModule):
|
||||
name = 'tcl'
|
||||
path = join_path(spack.share_path, "modules")
|
||||
|
||||
formats = {
|
||||
PrependPath: 'prepend-path {name} \"{value}\"\n',
|
||||
AppendPath: 'append-path {name} \"{value}\"\n',
|
||||
RemovePath: 'remove-path {name} \"{value}\"\n',
|
||||
SetEnv: 'setenv {name} \"{value}\"\n',
|
||||
UnsetEnv: 'unsetenv {name}\n'
|
||||
}
|
||||
|
||||
@property
|
||||
def file_name(self):
|
||||
return join_path(TclModule.path, self.spec.architecture, self.use_name)
|
||||
|
||||
|
||||
@property
|
||||
def use_name(self):
|
||||
return "%s-%s-%s-%s-%s" % (self.spec.name, self.spec.version,
|
||||
self.spec.compiler.name,
|
||||
self.spec.compiler.version,
|
||||
self.spec.compiler.version,
|
||||
self.spec.dag_hash())
|
||||
|
||||
|
||||
def _write(self, m_file):
|
||||
# TODO: cateogry?
|
||||
m_file.write('#%Module1.0\n')
|
||||
|
||||
def write_header(self, module_file):
|
||||
# TCL Modulefile header
|
||||
module_file.write('#%Module1.0\n')
|
||||
# TODO : category ?
|
||||
# Short description
|
||||
if self.short_description:
|
||||
m_file.write('module-whatis \"%s\"\n\n' % self.short_description)
|
||||
module_file.write('module-whatis \"%s\"\n\n' % self.short_description)
|
||||
|
||||
# Long description
|
||||
if self.long_description:
|
||||
m_file.write('proc ModulesHelp { } {\n')
|
||||
module_file.write('proc ModulesHelp { } {\n')
|
||||
doc = re.sub(r'"', '\"', self.long_description)
|
||||
m_file.write("puts stderr \"%s\"\n" % doc)
|
||||
m_file.write('}\n\n')
|
||||
|
||||
# Path alterations
|
||||
for var, dirs in self.paths.items():
|
||||
for directory in dirs:
|
||||
m_file.write("prepend-path %s \"%s\"\n" % (var, directory))
|
||||
|
||||
m_file.write("prepend-path CMAKE_PREFIX_PATH \"%s\"\n" % self.spec.prefix)
|
||||
module_file.write("puts stderr \"%s\"\n" % doc)
|
||||
module_file.write('}\n\n')
|
||||
|
@@ -34,44 +34,34 @@
|
||||
README.
|
||||
"""
|
||||
import os
|
||||
import errno
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import itertools
|
||||
import subprocess
|
||||
import platform as py_platform
|
||||
import multiprocessing
|
||||
from urlparse import urlparse, urljoin
|
||||
import textwrap
|
||||
from StringIO import StringIO
|
||||
import shutil
|
||||
import sys
|
||||
import string
|
||||
import time
|
||||
import glob
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.tty.log import log_output
|
||||
from llnl.util.link_tree import LinkTree
|
||||
from llnl.util.filesystem import *
|
||||
from llnl.util.lang import *
|
||||
|
||||
import spack
|
||||
import spack.error
|
||||
import spack.compilers
|
||||
import spack.mirror
|
||||
import spack.hooks
|
||||
import spack.directives
|
||||
import spack.repository
|
||||
import spack.build_environment
|
||||
import spack.compilers
|
||||
import spack.directives
|
||||
import spack.error
|
||||
import spack.fetch_strategy as fs
|
||||
import spack.hooks
|
||||
import spack.mirror
|
||||
import spack.repository
|
||||
import spack.url
|
||||
import spack.util.web
|
||||
import spack.fetch_strategy as fs
|
||||
from spack.version import *
|
||||
from StringIO import StringIO
|
||||
from llnl.util.filesystem import *
|
||||
from llnl.util.lang import *
|
||||
from llnl.util.link_tree import LinkTree
|
||||
from llnl.util.tty.log import log_output
|
||||
from spack.stage import Stage, ResourceStage, StageComposite
|
||||
from spack.util.compression import allowed_archive, extension
|
||||
from spack.util.executable import ProcessError, which
|
||||
from spack.util.compression import allowed_archive
|
||||
from spack.util.environment import dump_environment
|
||||
from spack import directory_layout
|
||||
from spack.util.executable import ProcessError
|
||||
from spack.version import *
|
||||
from urlparse import urlparse
|
||||
|
||||
"""Allowed URL schemes for spack packages."""
|
||||
_ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"]
|
||||
@@ -322,6 +312,18 @@ class SomePackage(Package):
|
||||
"""Most packages are NOT extendable. Set to True if you want extensions."""
|
||||
extendable = False
|
||||
|
||||
"""List of prefix-relative file paths (or a single path). If these do
|
||||
not exist after install, or if they exist but are not files,
|
||||
sanity checks fail.
|
||||
"""
|
||||
sanity_check_is_file = []
|
||||
|
||||
"""List of prefix-relative directory paths (or a single path). If
|
||||
these do not exist after install, or if they exist but are not
|
||||
directories, sanity checks will fail.
|
||||
"""
|
||||
sanity_check_is_dir = []
|
||||
|
||||
|
||||
def __init__(self, spec):
|
||||
# this determines how the package should be built.
|
||||
@@ -919,12 +921,11 @@ def build_process():
|
||||
|
||||
# Ensure that something was actually installed.
|
||||
if 'install' in self.install_phases:
|
||||
self._sanity_check_install()
|
||||
self.sanity_check_prefix()
|
||||
|
||||
|
||||
# Copy provenance into the install directory on success
|
||||
if 'provenance' in self.install_phases:
|
||||
|
||||
log_install_path = spack.install_layout.build_log_path(self.spec)
|
||||
env_install_path = spack.install_layout.build_env_path(self.spec)
|
||||
packages_dir = spack.install_layout.build_packages_path(self.spec)
|
||||
@@ -985,7 +986,21 @@ def build_process():
|
||||
spack.hooks.post_install(self)
|
||||
|
||||
|
||||
def _sanity_check_install(self):
|
||||
def sanity_check_prefix(self):
|
||||
"""This function checks whether install succeeded."""
|
||||
def check_paths(path_list, filetype, predicate):
|
||||
if isinstance(path_list, basestring):
|
||||
path_list = [path_list]
|
||||
|
||||
for path in path_list:
|
||||
abs_path = os.path.join(self.prefix, path)
|
||||
if not predicate(abs_path):
|
||||
raise InstallError("Install failed for %s. No such %s in prefix: %s"
|
||||
% (self.name, filetype, path))
|
||||
|
||||
check_paths(self.sanity_check_is_file, 'file', os.path.isfile)
|
||||
check_paths(self.sanity_check_is_dir, 'directory', os.path.isdir)
|
||||
|
||||
installed = set(os.listdir(self.prefix))
|
||||
installed.difference_update(spack.install_layout.hidden_file_paths)
|
||||
if not installed:
|
||||
@@ -1015,38 +1030,127 @@ def module(self):
|
||||
return __import__(self.__class__.__module__,
|
||||
fromlist=[self.__class__.__name__])
|
||||
|
||||
def setup_environment(self, spack_env, run_env):
|
||||
"""Set up the compile and runtime environemnts for a package.
|
||||
|
||||
def setup_dependent_environment(self, module, spec, dependent_spec):
|
||||
"""Called before the install() method of dependents.
|
||||
`spack_env` and `run_env` are `EnvironmentModifications`
|
||||
objects. Package authors can call methods on them to alter
|
||||
the environment within Spack and at runtime.
|
||||
|
||||
Both `spack_env` and `run_env` are applied within the build
|
||||
process, before this package's `install()` method is called.
|
||||
|
||||
Modifications in `run_env` will *also* be added to the
|
||||
generated environment modules for this package.
|
||||
|
||||
Default implementation does nothing, but this can be
|
||||
overridden by an extendable package to set up the install
|
||||
environment for its extensions. This is useful if there are
|
||||
some common steps to installing all extensions for a
|
||||
certain package.
|
||||
overridden if the package needs a particular environment.
|
||||
|
||||
Some examples:
|
||||
Examples:
|
||||
|
||||
1. Installing python modules generally requires PYTHONPATH to
|
||||
point to the lib/pythonX.Y/site-packages directory in the
|
||||
module's install prefix. This could set that variable.
|
||||
1. Qt extensions need `QTDIR` set.
|
||||
|
||||
2. Extensions often need to invoke the 'python' interpreter
|
||||
from the Python installation being extended. This routine can
|
||||
put a 'python' Execuable object in the module scope for the
|
||||
extension package to simplify extension installs.
|
||||
Args:
|
||||
spack_env (EnvironmentModifications): list of
|
||||
modifications to be applied when this package is built
|
||||
within Spack.
|
||||
|
||||
3. A lot of Qt extensions need QTDIR set. This can be used to do that.
|
||||
run_env (EnvironmentModifications): list of environment
|
||||
changes to be applied when this package is run outside
|
||||
of Spack.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def setup_dependent_environment(self, spack_env, run_env, dependent_spec):
|
||||
"""Set up the environment of packages that depend on this one.
|
||||
|
||||
This is similar to `setup_environment`, but it is used to
|
||||
modify the compile and runtime environments of packages that
|
||||
*depend* on this one. This gives packages like Python and
|
||||
others that follow the extension model a way to implement
|
||||
common environment or compile-time settings for dependencies.
|
||||
|
||||
By default, this delegates to self.setup_environment()
|
||||
|
||||
Example :
|
||||
|
||||
1. Installing python modules generally requires
|
||||
`PYTHONPATH` to point to the lib/pythonX.Y/site-packages
|
||||
directory in the module's install prefix. This could
|
||||
set that variable.
|
||||
|
||||
Args:
|
||||
|
||||
spack_env (EnvironmentModifications): list of
|
||||
modifications to be applied when the dependent package
|
||||
is bulit within Spack.
|
||||
|
||||
run_env (EnvironmentModifications): list of environment
|
||||
changes to be applied when the dependent package is
|
||||
run outside of Spack.
|
||||
|
||||
dependent_spec (Spec): The spec of the dependent package
|
||||
about to be built. This allows the extendee (self) to
|
||||
query the dependent's state. Note that *this*
|
||||
package's spec is available as `self.spec`.
|
||||
|
||||
This is useful if there are some common steps to installing
|
||||
all extensions for a certain package.
|
||||
|
||||
"""
|
||||
self.setup_environment(spack_env, run_env)
|
||||
|
||||
|
||||
def setup_dependent_package(self, module, dependent_spec):
|
||||
"""Set up Python module-scope variables for dependent packages.
|
||||
|
||||
Called before the install() method of dependents.
|
||||
|
||||
Default implementation does nothing, but this can be
|
||||
overridden by an extendable package to set up the module of
|
||||
its extensions. This is useful if there are some common steps
|
||||
to installing all extensions for a certain package.
|
||||
|
||||
Example :
|
||||
|
||||
1. Extensions often need to invoke the `python`
|
||||
interpreter from the Python installation being
|
||||
extended. This routine can put a 'python' Executable
|
||||
object in the module scope for the extension package to
|
||||
simplify extension installs.
|
||||
|
||||
2. MPI compilers could set some variables in the
|
||||
dependent's scope that point to `mpicc`, `mpicxx`,
|
||||
etc., allowing them to be called by common names
|
||||
regardless of which MPI is used.
|
||||
|
||||
3. BLAS/LAPACK implementations can set some variables
|
||||
indicating the path to their libraries, since these
|
||||
paths differ by BLAS/LAPACK implementation.
|
||||
|
||||
Args:
|
||||
|
||||
module (module): The Python `module` object of the
|
||||
dependent package. Packages can use this to set
|
||||
module-scope variables for the dependent to use.
|
||||
|
||||
dependent_spec (Spec): The spec of the dependent package
|
||||
about to be built. This allows the extendee (self) to
|
||||
query the dependent's state. Note that *this*
|
||||
package's spec is available as `self.spec`.
|
||||
|
||||
This is useful if there are some common steps to installing
|
||||
all extensions for a certain package.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Package implementations override this with their own build configuration."""
|
||||
raise InstallError("Package %s provides no install method!" % self.name)
|
||||
|
||||
|
||||
def do_uninstall(self, force=False):
|
||||
if not self.installed:
|
||||
raise InstallError(str(self.spec) + " is not installed.")
|
||||
@@ -1250,6 +1354,27 @@ def rpath_args(self):
|
||||
return " ".join("-Wl,-rpath,%s" % p for p in self.rpath)
|
||||
|
||||
|
||||
def install_dependency_symlinks(pkg, spec, prefix):
|
||||
"""Execute a dummy install and flatten dependencies"""
|
||||
flatten_dependencies(spec, prefix)
|
||||
|
||||
def flatten_dependencies(spec, flat_dir):
|
||||
"""Make each dependency of spec present in dir via symlink."""
|
||||
for dep in spec.traverse(root=False):
|
||||
name = dep.name
|
||||
|
||||
dep_path = spack.install_layout.path_for_spec(dep)
|
||||
dep_files = LinkTree(dep_path)
|
||||
|
||||
os.mkdir(flat_dir+'/'+name)
|
||||
|
||||
conflict = dep_files.find_conflict(flat_dir+'/'+name)
|
||||
if conflict:
|
||||
raise DependencyConflictError(conflict)
|
||||
|
||||
dep_files.merge(flat_dir+'/'+name)
|
||||
|
||||
|
||||
def validate_package_url(url_string):
|
||||
"""Determine whether spack can handle a particular URL or not."""
|
||||
url = urlparse(url_string)
|
||||
@@ -1467,6 +1592,10 @@ def __init__(self, message, long_msg=None):
|
||||
super(InstallError, self).__init__(message, long_msg)
|
||||
|
||||
|
||||
class ExternalPackageError(InstallError):
|
||||
"""Raised by install() when a package is only for external use."""
|
||||
|
||||
|
||||
class PackageStillNeededError(InstallError):
|
||||
"""Raised when package is still needed by another on uninstall."""
|
||||
def __init__(self, spec, dependents):
|
||||
@@ -1517,3 +1646,11 @@ def __init__(self, path):
|
||||
class ActivationError(ExtensionError):
|
||||
def __init__(self, msg, long_msg=None):
|
||||
super(ActivationError, self).__init__(msg, long_msg)
|
||||
|
||||
|
||||
class DependencyConflictError(spack.error.SpackError):
|
||||
"""Raised when the dependencies cannot be flattened as asked for."""
|
||||
def __init__(self, conflict):
|
||||
super(DependencyConflictError, self).__init__(
|
||||
"%s conflicts with another file in the flattened directory." %(
|
||||
conflict))
|
||||
|
@@ -316,6 +316,11 @@ def get(self, spec, new=False):
|
||||
return self.repo_for_pkg(spec).get(spec)
|
||||
|
||||
|
||||
def get_pkg_class(self, pkg_name):
|
||||
"""Find a class for the spec's package and return the class object."""
|
||||
return self.repo_for_pkg(pkg_name).get_pkg_class(pkg_name)
|
||||
|
||||
|
||||
@_autospec
|
||||
def dump_provenance(self, spec, path):
|
||||
"""Dump provenance information for a spec to a particular path.
|
||||
@@ -550,7 +555,7 @@ def get(self, spec, new=False):
|
||||
|
||||
key = hash(spec)
|
||||
if new or key not in self._instances:
|
||||
package_class = self._get_pkg_class(spec.name)
|
||||
package_class = self.get_pkg_class(spec.name)
|
||||
try:
|
||||
copy = spec.copy() # defensive copy. Package owns its spec.
|
||||
self._instances[key] = package_class(copy)
|
||||
@@ -715,7 +720,7 @@ def _get_pkg_module(self, pkg_name):
|
||||
return self._modules[pkg_name]
|
||||
|
||||
|
||||
def _get_pkg_class(self, pkg_name):
|
||||
def get_pkg_class(self, pkg_name):
|
||||
"""Get the class for the package out of its module.
|
||||
|
||||
First loads (or fetches from cache) a module for the
|
||||
|
@@ -353,7 +353,7 @@ def constrain(self, other):
|
||||
@property
|
||||
def concrete(self):
|
||||
return self.spec._concrete or all(
|
||||
v in self for v in self.spec.package.variants)
|
||||
v in self for v in self.spec.package_class.variants)
|
||||
|
||||
|
||||
def copy(self):
|
||||
@@ -418,9 +418,11 @@ def __init__(self, spec_like, *dep_like, **kwargs):
|
||||
# cases we've read them from a file want to assume normal.
|
||||
# This allows us to manipulate specs that Spack doesn't have
|
||||
# package.py files for.
|
||||
self._normal = kwargs.get('normal', False)
|
||||
self._normal = kwargs.get('normal', False)
|
||||
self._concrete = kwargs.get('concrete', False)
|
||||
self.external = None
|
||||
|
||||
# Allow a spec to be constructed with an external path.
|
||||
self.external = kwargs.get('external', None)
|
||||
|
||||
# This allows users to construct a spec DAG with literals.
|
||||
# Note that given two specs a and b, Spec(a) copies a, but
|
||||
@@ -498,6 +500,14 @@ def package(self):
|
||||
return spack.repo.get(self)
|
||||
|
||||
|
||||
@property
|
||||
def package_class(self):
|
||||
"""Internal package call gets only the class object for a package.
|
||||
Use this to just get package metadata.
|
||||
"""
|
||||
return spack.repo.get_pkg_class(self.name)
|
||||
|
||||
|
||||
@property
|
||||
def virtual(self):
|
||||
"""Right now, a spec is virtual if no package exists with its name.
|
||||
@@ -786,8 +796,30 @@ def _replace_with(self, concrete):
|
||||
"""Replace this virtual spec with a concrete spec."""
|
||||
assert(self.virtual)
|
||||
for name, dependent in self.dependents.items():
|
||||
# remove self from all dependents.
|
||||
del dependent.dependencies[self.name]
|
||||
dependent._add_dependency(concrete)
|
||||
|
||||
# add the replacement, unless it is already a dep of dependent.
|
||||
if concrete.name not in dependent.dependencies:
|
||||
dependent._add_dependency(concrete)
|
||||
|
||||
|
||||
def _replace_node(self, replacement):
|
||||
"""Replace this spec with another.
|
||||
|
||||
Connects all dependents of this spec to its replacement, and
|
||||
disconnects this spec from any dependencies it has. New spec
|
||||
will have any dependencies the replacement had, and may need
|
||||
to be normalized.
|
||||
|
||||
"""
|
||||
for name, dependent in self.dependents.items():
|
||||
del dependent.dependencies[self.name]
|
||||
dependent._add_dependency(replacement)
|
||||
|
||||
for name, dep in self.dependencies.items():
|
||||
del dep.dependents[self.name]
|
||||
del self.dependencies[dep.name]
|
||||
|
||||
|
||||
def _expand_virtual_packages(self):
|
||||
@@ -807,18 +839,80 @@ def _expand_virtual_packages(self):
|
||||
this are infrequent, but should implement this before it is
|
||||
a problem.
|
||||
"""
|
||||
# Make an index of stuff this spec already provides
|
||||
self_index = ProviderIndex(self.traverse(), restrict=True)
|
||||
|
||||
changed = False
|
||||
done = False
|
||||
while not done:
|
||||
done = True
|
||||
for spec in list(self.traverse()):
|
||||
if spack.concretizer.concretize_virtual_and_external(spec):
|
||||
done = False
|
||||
replacement = None
|
||||
if spec.virtual:
|
||||
replacement = self._find_provider(spec, self_index)
|
||||
if replacement:
|
||||
# TODO: may break if in-place on self but
|
||||
# shouldn't happen if root is traversed first.
|
||||
spec._replace_with(replacement)
|
||||
done=False
|
||||
break
|
||||
|
||||
if not replacement:
|
||||
# Get a list of possible replacements in order of preference.
|
||||
candidates = spack.concretizer.choose_virtual_or_external(spec)
|
||||
|
||||
# Try the replacements in order, skipping any that cause
|
||||
# satisfiability problems.
|
||||
for replacement in candidates:
|
||||
if replacement is spec:
|
||||
break
|
||||
|
||||
# Replace spec with the candidate and normalize
|
||||
copy = self.copy()
|
||||
copy[spec.name]._dup(replacement.copy(deps=False))
|
||||
|
||||
try:
|
||||
# If there are duplicate providers or duplicate provider
|
||||
# deps, consolidate them and merge constraints.
|
||||
copy.normalize(force=True)
|
||||
break
|
||||
except SpecError as e:
|
||||
# On error, we'll try the next replacement.
|
||||
continue
|
||||
|
||||
# If replacement is external then trim the dependencies
|
||||
if replacement.external:
|
||||
if (spec.dependencies):
|
||||
changed = True
|
||||
spec.dependencies = DependencyMap()
|
||||
replacement.dependencies = DependencyMap()
|
||||
|
||||
# TODO: could this and the stuff in _dup be cleaned up?
|
||||
def feq(cfield, sfield):
|
||||
return (not cfield) or (cfield == sfield)
|
||||
|
||||
if replacement is spec or (feq(replacement.name, spec.name) and
|
||||
feq(replacement.versions, spec.versions) and
|
||||
feq(replacement.compiler, spec.compiler) and
|
||||
feq(replacement.architecture, spec.architecture) and
|
||||
feq(replacement.dependencies, spec.dependencies) and
|
||||
feq(replacement.variants, spec.variants) and
|
||||
feq(replacement.external, spec.external)):
|
||||
continue
|
||||
|
||||
# Refine this spec to the candidate. This uses
|
||||
# replace_with AND dup so that it can work in
|
||||
# place. TODO: make this more efficient.
|
||||
if spec.virtual:
|
||||
spec._replace_with(replacement)
|
||||
changed = True
|
||||
if spec._dup(replacement, deps=False, cleardeps=False):
|
||||
changed = True
|
||||
|
||||
# If there are duplicate providers or duplicate provider deps, this
|
||||
# consolidates them and merge constraints.
|
||||
changed |= self.normalize(force=True)
|
||||
self_index.update(spec)
|
||||
done=False
|
||||
break
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
@@ -842,7 +936,7 @@ def concretize(self):
|
||||
force = False
|
||||
|
||||
while changed:
|
||||
changes = (self.normalize(force=force),
|
||||
changes = (self.normalize(force),
|
||||
self._expand_virtual_packages(),
|
||||
self._concretize_helper())
|
||||
changed = any(changes)
|
||||
@@ -968,8 +1062,8 @@ def _evaluate_dependency_conditions(self, name):
|
||||
|
||||
def _find_provider(self, vdep, provider_index):
|
||||
"""Find provider for a virtual spec in the provider index.
|
||||
Raise an exception if there is a conflicting virtual
|
||||
dependency already in this spec.
|
||||
Raise an exception if there is a conflicting virtual
|
||||
dependency already in this spec.
|
||||
"""
|
||||
assert(vdep.virtual)
|
||||
providers = provider_index.providers_for(vdep)
|
||||
@@ -1010,17 +1104,14 @@ def _merge_dependency(self, dep, visited, spec_deps, provider_index):
|
||||
"""
|
||||
changed = False
|
||||
|
||||
# If it's a virtual dependency, try to find a provider and
|
||||
# merge that.
|
||||
# If it's a virtual dependency, try to find an existing
|
||||
# provider in the spec, and merge that.
|
||||
if dep.virtual:
|
||||
visited.add(dep.name)
|
||||
provider = self._find_provider(dep, provider_index)
|
||||
if provider:
|
||||
dep = provider
|
||||
|
||||
else:
|
||||
# if it's a real dependency, check whether it provides
|
||||
# something already required in the spec.
|
||||
index = ProviderIndex([dep], restrict=True)
|
||||
for vspec in (v for v in spec_deps.values() if v.virtual):
|
||||
if index.providers_for(vspec):
|
||||
@@ -1117,13 +1208,14 @@ def normalize(self, force=False):
|
||||
# Get all the dependencies into one DependencyMap
|
||||
spec_deps = self.flat_dependencies(copy=False)
|
||||
|
||||
# Initialize index of virtual dependency providers
|
||||
index = ProviderIndex(spec_deps.values(), restrict=True)
|
||||
# Initialize index of virtual dependency providers if
|
||||
# concretize didn't pass us one already
|
||||
provider_index = ProviderIndex(spec_deps.values(), restrict=True)
|
||||
|
||||
# traverse the package DAG and fill out dependencies according
|
||||
# to package files & their 'when' specs
|
||||
visited = set()
|
||||
any_change = self._normalize_helper(visited, spec_deps, index)
|
||||
any_change = self._normalize_helper(visited, spec_deps, provider_index)
|
||||
|
||||
# If there are deps specified but not visited, they're not
|
||||
# actually deps of this package. Raise an error.
|
||||
@@ -1161,7 +1253,7 @@ def validate_names(self):
|
||||
|
||||
# Ensure that variants all exist.
|
||||
for vname, variant in spec.variants.items():
|
||||
if vname not in spec.package.variants:
|
||||
if vname not in spec.package_class.variants:
|
||||
raise UnknownVariantError(spec.name, vname)
|
||||
|
||||
|
||||
@@ -1402,13 +1494,12 @@ def _dup(self, other, **kwargs):
|
||||
Whether deps should be copied too. Set to false to copy a
|
||||
spec but not its dependencies.
|
||||
"""
|
||||
|
||||
# We don't count dependencies as changes here
|
||||
changed = True
|
||||
if hasattr(self, 'name'):
|
||||
changed = (self.name != other.name and self.versions != other.versions and \
|
||||
self.architecture != other.architecture and self.compiler != other.compiler and \
|
||||
self.variants != other.variants and self._normal != other._normal and \
|
||||
changed = (self.name != other.name and self.versions != other.versions and
|
||||
self.architecture != other.architecture and self.compiler != other.compiler and
|
||||
self.variants != other.variants and self._normal != other._normal and
|
||||
self.concrete != other.concrete and self.external != other.external)
|
||||
|
||||
# Local node attributes get copied first.
|
||||
|
@@ -66,7 +66,8 @@
|
||||
'database',
|
||||
'namespace_trie',
|
||||
'yaml',
|
||||
'sbang']
|
||||
'sbang',
|
||||
'environment']
|
||||
|
||||
|
||||
def list_tests():
|
||||
|
@@ -24,6 +24,7 @@
|
||||
##############################################################################
|
||||
import spack
|
||||
from spack.spec import Spec, CompilerSpec
|
||||
from spack.version import ver
|
||||
from spack.concretize import find_spec
|
||||
from spack.test.mock_packages_test import *
|
||||
|
||||
@@ -77,6 +78,14 @@ def test_concretize_variant(self):
|
||||
self.check_concretize('mpich')
|
||||
|
||||
|
||||
def test_concretize_preferred_version(self):
|
||||
spec = self.check_concretize('python')
|
||||
self.assertEqual(spec.versions, ver('2.7.11'))
|
||||
|
||||
spec = self.check_concretize('python@3.5.1')
|
||||
self.assertEqual(spec.versions, ver('3.5.1'))
|
||||
|
||||
|
||||
def test_concretize_with_virtual(self):
|
||||
self.check_concretize('mpileaks ^mpi')
|
||||
self.check_concretize('mpileaks ^mpi@:1.1')
|
||||
@@ -142,6 +151,34 @@ def test_concretize_with_provides_when(self):
|
||||
for spec in spack.repo.providers_for('mpi@3')))
|
||||
|
||||
|
||||
def test_concretize_two_virtuals(self):
|
||||
"""Test a package with multiple virtual dependencies."""
|
||||
s = Spec('hypre').concretize()
|
||||
|
||||
|
||||
def test_concretize_two_virtuals_with_one_bound(self):
|
||||
"""Test a package with multiple virtual dependencies and one preset."""
|
||||
s = Spec('hypre ^openblas').concretize()
|
||||
|
||||
|
||||
def test_concretize_two_virtuals_with_two_bound(self):
|
||||
"""Test a package with multiple virtual dependencies and two of them preset."""
|
||||
s = Spec('hypre ^openblas ^netlib-lapack').concretize()
|
||||
|
||||
|
||||
def test_concretize_two_virtuals_with_dual_provider(self):
|
||||
"""Test a package with multiple virtual dependencies and force a provider
|
||||
that provides both."""
|
||||
s = Spec('hypre ^openblas-with-lapack').concretize()
|
||||
|
||||
|
||||
def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(self):
|
||||
"""Test a package with multiple virtual dependencies and force a provider
|
||||
that provides both, and another conflicting package that provides one."""
|
||||
s = Spec('hypre ^openblas-with-lapack ^netlib-lapack')
|
||||
self.assertRaises(spack.spec.MultipleProviderError, s.concretize)
|
||||
|
||||
|
||||
def test_virtual_is_fully_expanded_for_callpath(self):
|
||||
# force dependence on fake "zmpi" by asking for MPI 10.0
|
||||
spec = Spec('callpath ^mpi@10.0')
|
||||
@@ -282,3 +319,9 @@ def test_find_spec_none(self):
|
||||
Spec('e'))
|
||||
self.assertEqual(None, find_spec(s['b'], lambda s: '+foo' in s))
|
||||
|
||||
|
||||
def test_compiler_child(self):
|
||||
s = Spec('mpileaks%clang ^dyninst%gcc')
|
||||
s.concretize()
|
||||
self.assertTrue(s['mpileaks'].satisfies('%clang'))
|
||||
self.assertTrue(s['dyninst'].satisfies('%gcc'))
|
||||
|
@@ -26,6 +26,7 @@
|
||||
These tests check the database is functioning properly,
|
||||
both in memory and in its file
|
||||
"""
|
||||
import os.path
|
||||
import multiprocessing
|
||||
import shutil
|
||||
import tempfile
|
||||
|
73
lib/spack/spack/test/environment.py
Normal file
73
lib/spack/spack/test/environment.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import unittest
|
||||
import os
|
||||
from spack.environment import EnvironmentModifications
|
||||
|
||||
|
||||
class EnvironmentTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
os.environ.clear()
|
||||
os.environ['UNSET_ME'] = 'foo'
|
||||
os.environ['EMPTY_PATH_LIST'] = ''
|
||||
os.environ['PATH_LIST'] = '/path/second:/path/third'
|
||||
os.environ['REMOVE_PATH_LIST'] = '/a/b:/duplicate:/a/c:/remove/this:/a/d:/duplicate/:/f/g'
|
||||
|
||||
def test_set(self):
|
||||
env = EnvironmentModifications()
|
||||
env.set('A', 'dummy value')
|
||||
env.set('B', 3)
|
||||
env.apply_modifications()
|
||||
self.assertEqual('dummy value', os.environ['A'])
|
||||
self.assertEqual(str(3), os.environ['B'])
|
||||
|
||||
def test_unset(self):
|
||||
env = EnvironmentModifications()
|
||||
self.assertEqual('foo', os.environ['UNSET_ME'])
|
||||
env.unset('UNSET_ME')
|
||||
env.apply_modifications()
|
||||
self.assertRaises(KeyError, os.environ.__getitem__, 'UNSET_ME')
|
||||
|
||||
def test_set_path(self):
|
||||
env = EnvironmentModifications()
|
||||
env.set_path('A', ['foo', 'bar', 'baz'])
|
||||
env.apply_modifications()
|
||||
self.assertEqual('foo:bar:baz', os.environ['A'])
|
||||
|
||||
def test_path_manipulation(self):
|
||||
env = EnvironmentModifications()
|
||||
|
||||
env.append_path('PATH_LIST', '/path/last')
|
||||
env.prepend_path('PATH_LIST', '/path/first')
|
||||
|
||||
env.append_path('EMPTY_PATH_LIST', '/path/middle')
|
||||
env.append_path('EMPTY_PATH_LIST', '/path/last')
|
||||
env.prepend_path('EMPTY_PATH_LIST', '/path/first')
|
||||
|
||||
env.append_path('NEWLY_CREATED_PATH_LIST', '/path/middle')
|
||||
env.append_path('NEWLY_CREATED_PATH_LIST', '/path/last')
|
||||
env.prepend_path('NEWLY_CREATED_PATH_LIST', '/path/first')
|
||||
|
||||
env.remove_path('REMOVE_PATH_LIST', '/remove/this')
|
||||
env.remove_path('REMOVE_PATH_LIST', '/duplicate/')
|
||||
|
||||
env.apply_modifications()
|
||||
self.assertEqual('/path/first:/path/second:/path/third:/path/last', os.environ['PATH_LIST'])
|
||||
self.assertEqual('/path/first:/path/middle:/path/last', os.environ['EMPTY_PATH_LIST'])
|
||||
self.assertEqual('/path/first:/path/middle:/path/last', os.environ['NEWLY_CREATED_PATH_LIST'])
|
||||
self.assertEqual('/a/b:/a/c:/a/d:/f/g', os.environ['REMOVE_PATH_LIST'])
|
||||
|
||||
def test_extra_arguments(self):
|
||||
env = EnvironmentModifications()
|
||||
env.set('A', 'dummy value', who='Pkg1')
|
||||
for x in env:
|
||||
assert 'who' in x.args
|
||||
env.apply_modifications()
|
||||
self.assertEqual('dummy value', os.environ['A'])
|
||||
|
||||
def test_extend(self):
|
||||
env = EnvironmentModifications()
|
||||
env.set('A', 'dummy value')
|
||||
env.set('B', 3)
|
||||
copy_construct = EnvironmentModifications(env)
|
||||
self.assertEqual(len(copy_construct), 2)
|
||||
for x, y in zip(env, copy_construct):
|
||||
assert x is y
|
@@ -142,7 +142,7 @@ def split_url_extension(path):
|
||||
|
||||
def downloaded_file_extension(path):
|
||||
"""This returns the type of archive a URL refers to. This is
|
||||
sometimes confusing becasue of URLs like:
|
||||
sometimes confusing because of URLs like:
|
||||
|
||||
(1) https://github.com/petdance/ack/tarball/1.93_02
|
||||
|
||||
|
@@ -27,13 +27,12 @@
|
||||
from itertools import product
|
||||
from spack.util.executable import which
|
||||
|
||||
# Supported archvie extensions.
|
||||
# Supported archive extensions.
|
||||
PRE_EXTS = ["tar"]
|
||||
EXTS = ["gz", "bz2", "xz", "Z", "zip", "tgz"]
|
||||
|
||||
# Add EXTS last so that .tar.gz is matched *before* tar.gz
|
||||
ALLOWED_ARCHIVE_TYPES = [".".join(l) for l in product(PRE_EXTS, EXTS)] + EXTS
|
||||
|
||||
# Add PRE_EXTS and EXTS last so that .tar.gz is matched *before* .tar or .gz
|
||||
ALLOWED_ARCHIVE_TYPES = [".".join(l) for l in product(PRE_EXTS, EXTS)] + PRE_EXTS + EXTS
|
||||
|
||||
def allowed_archive(path):
|
||||
return any(path.endswith(t) for t in ALLOWED_ARCHIVE_TYPES)
|
||||
|
@@ -59,14 +59,8 @@ def path_put_first(var_name, directories):
|
||||
path_set(var_name, new_path)
|
||||
|
||||
|
||||
def pop_keys(dictionary, *keys):
|
||||
for key in keys:
|
||||
if key in dictionary:
|
||||
dictionary.pop(key)
|
||||
|
||||
|
||||
def dump_environment(path):
|
||||
"""Dump the current environment out to a file."""
|
||||
with open(path, 'w') as env_file:
|
||||
for key,val in sorted(os.environ.items()):
|
||||
for key, val in sorted(os.environ.items()):
|
||||
env_file.write("%s=%s\n" % (key, val))
|
||||
|
Reference in New Issue
Block a user