summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/docs/packaging_guide.rst136
-rw-r--r--lib/spack/docs/tutorial/examples/Autotools/autotools_class.py460
-rw-r--r--lib/spack/docs/tutorial/examples/Cmake/cmake_class.py224
-rw-r--r--lib/spack/docs/tutorial/examples/Makefile/makefile_class.py129
-rw-r--r--lib/spack/docs/tutorial/examples/PyPackage/python_package_class.py399
-rw-r--r--lib/spack/docs/tutorial_buildsystems.rst8
-rw-r--r--lib/spack/spack/build_environment.py37
-rw-r--r--lib/spack/spack/build_systems/autotools.py23
-rw-r--r--lib/spack/spack/build_systems/cmake.py51
-rw-r--r--lib/spack/spack/package.py41
-rw-r--r--lib/spack/spack/test/cc.py8
-rw-r--r--lib/spack/spack/test/flag_handlers.py176
12 files changed, 372 insertions, 1320 deletions
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index a8971f64a9..09bd2632b3 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -2536,98 +2536,104 @@ build system.
Compiler flags
^^^^^^^^^^^^^^
-Compiler flags set by the user through the Spec object can be passed to
-the build in one of two ways. For packages inheriting from the
-``CmakePackage`` or ``AutotoolsPackage`` classes, the build environment
-passes those flags to the relevant environment variables (``CFLAGS``,
-``CXXFLAGS``, etc) that are respected by the build system. For all other
-packages, the default behavior is to inject the flags directly into the
-compiler commands using Spack's compiler wrappers.
+Compiler flags set by the user through the Spec object can be passed
+to the build in one of three ways. By default, the build environment
+injects these flags directly into the compiler commands using Spack's
+compiler wrappers. In cases where the build system requires knowledge
+of the compiler flags, they can be registered with the build system by
+alternatively passing them through environment variables or as build
+system arguments. The flag_handler method can be used to change this
+behavior.
+
+Packages can override the flag_handler method with one of three
+built-in flag_handlers. The built-in flag_handlers are named
+``inject_flags``, ``env_flags``, and ``build_system_flags``. The
+``inject_flags`` method is the default. The ``env_flags`` method puts
+all of the flags into the environment variables that ``make`` uses as
+implicit variables ('CFLAGS', 'CXXFLAGS', etc.). The
+``build_system_flags`` method adds the flags as
+arguments to the invocation of ``configure`` or ``cmake``,
+respectively.
.. warning::
- The flag handling methods described in this section are in beta.
- The exact semantics are liable to change to improve usability.
+ Passing compiler flags using build system arguments is only
+ supported for CMake and Autotools packages. Individual packages may
+ also differ in whether they properly respect these arguments.
-Individual packages can override the default behavior for the flag
-handling. Packages can define a ``default_flag_handler`` method that
-applies to all sets of flags handled by Spack, or may define
-individual methods ``cflags_handler``, ``cxxflags_handler``,
-etc. Spack will apply the individual method for a flag set if it
-exists, otherwise the ``default_flag_handler`` method if it exists,
-and fall back on the default for that package class if neither exists.
+Individual packages may also define their own ``flag_handler``
+methods. The ``flag_handler`` method takes the package instance
+(``self``), the name of the flag, and a list of the values of the
+flag. It will be called on each of the six compiler flags supported in
+Spack. It should return a triple of ``(injf, envf, bsf)`` where
+``injf`` is a list of flags to inject via the Spack compiler wrappers,
+``envf`` is a list of flags to set in the appropriate environment
+variables, and ``bsf`` is a list of flags to pass to the build system
+as arguments.
-These methods are defined on the package class, and take two
-parameters in addition to the packages itself. The ``env`` parameter
-is an ``EnvironmentModifications`` object that can be used to change
-the build environment. The ``flag_val`` parameter is a tuple. Its
-first entry is the name of the flag (``cflags``, ``cxxflags``, etc.)
-and its second entry is a list of the values for that flag.
+.. warning::
-There are three primary idioms that can be combined to create whatever
-behavior the package requires.
+ Passing a non-empty list of flags to ``bsf`` for a build system
+ that does not support build system arguments will result in an
+ error.
-1. The default behavior for packages inheriting from
-``AutotoolsPackage`` or ``CmakePackage``.
+Here are the definitions of the three built-in flag handlers:
.. code-block:: python
- def default_flag_handler(self, env, flag_val):
- env.append_flags(flag_val[0].upper(), ' '.join(flag_val[1]))
- return []
+ def inject_flags(self, name, flags):
+ return (flags, None, None)
-2. The default behavior for other packages
+ def env_flags(self, name, flags):
+ return (None, flags, None)
-.. code-block:: python
+ def build_system_flags(self, name, flags):
+ return (None, None, flags)
- def default_flag_handler(self, env, flag_val):
- return flag_val[1]
+.. note::
+ Returning ``[]`` and ``None`` are equivalent in a ``flag_handler``
+ method.
-3. Packages may have additional flags to add to the build. These flags
-can be added to either idiom above. For example:
+Packages can override the default behavior either by specifying one of
+the built-in flag handlers,
.. code-block:: python
- def default_flag_handler(self, env, flag_val):
- flags = flag_val[1]
- flags.append('-flag')
- return flags
-
-or
-
-.. code-block:: python
+ flag_handler = <PackageClass>.env_flags
- def default_flag_handler(self, env, flag_val):
- env.append_flags(flag_val[0].upper(), ' '.join(flag_val[1]))
- env.append_flags(flag_val[0].upper(), '-flag')
- return []
+where ``<PackageClass>`` can be any of the subclasses of PackageBase
+discussed in :ref:`installation_procedure`,
-Packages may also opt for methods that include aspects of any of the
-idioms above. E.g.
+or by implementing the flag_handler method. Suppose for a package
+``Foo`` we need to pass ``cflags``, ``cxxflags``, and ``cppflags``
+through the environment, the rest of the flags through compiler
+wrapper injection, and we need to add ``-lbar`` to ``ldlibs``. The
+following flag handler method accomplishes that.
.. code-block:: python
- def default_flag_handler(self, env, flag_val):
- flags = []
- if len(flag_val[1]) > 3:
- env.append_flags(flag_val[0].upper(), ' '.join(flag_val[1][3:]))
- flags = flag_val[1][:3]
- else:
- flags = flag_val[1]
- flags.append('-flag')
- return flags
+ def flag_handler(self, name, flags):
+ if name in ['cflags', 'cxxflags', 'cppflags']:
+ return (None, flags, None)
+ elif name == 'ldlibs':
+ flags.append('-lbar')
+ return (flags, None, None)
Because these methods can pass values through environment variables,
-it is important not to override these variables unnecessarily in other
-package methods. In the ``setup_environment`` and
+it is important not to override these variables unnecessarily
+(E.g. setting ``env['CFLAGS']``) in other package methods when using
+non-default flag handlers. In the ``setup_environment`` and
``setup_dependent_environment`` methods, use the ``append_flags``
method of the ``EnvironmentModifications`` class to append values to a
-list of flags whenever there is no good reason to override the
-existing value. In the ``install`` method and other methods that can
-operate on the build environment directly through the ``env``
-variable, test for environment variable existance before overriding
-values to add compiler flags.
+list of flags whenever the flag handler is ``env_flags``. If the
+package passes flags through the environment or the build system
+manually (in the install method, for example), we recommend using the
+default flag handler, or removind manual references and implementing a
+custom flag handler method that adds the desired flags to export as
+environment variables or pass to the build system. Manual flag passing
+is likely to interfere with the ``env_flags`` and
+``build_system_flags`` methods.
In rare circumstances such as compiling and running small unit tests, a
package developer may need to know what are the appropriate compiler
diff --git a/lib/spack/docs/tutorial/examples/Autotools/autotools_class.py b/lib/spack/docs/tutorial/examples/Autotools/autotools_class.py
deleted file mode 100644
index 8f2af3e21e..0000000000
--- a/lib/spack/docs/tutorial/examples/Autotools/autotools_class.py
+++ /dev/null
@@ -1,460 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/spack/spack
-# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-
-import inspect
-import os
-import os.path
-import shutil
-import stat
-from subprocess import PIPE
-from subprocess import check_call
-
-import llnl.util.tty as tty
-from llnl.util.filesystem import working_dir, join_path, force_remove
-from spack.package import PackageBase, run_after, run_before
-from spack.util.executable import Executable
-
-
-class AutotoolsPackage(PackageBase):
- """Specialized class for packages built using GNU Autotools.
-
- This class provides four phases that can be overridden:
-
- 1. :py:meth:`~.AutotoolsPackage.autoreconf`
- 2. :py:meth:`~.AutotoolsPackage.configure`
- 3. :py:meth:`~.AutotoolsPackage.build`
- 4. :py:meth:`~.AutotoolsPackage.install`
-
- They all have sensible defaults and for many packages the only thing
- necessary will be to override the helper method
- :py:meth:`~.AutotoolsPackage.configure_args`.
- For a finer tuning you may also override:
-
- +-----------------------------------------------+--------------------+
- | **Method** | **Purpose** |
- +===============================================+====================+
- | :py:attr:`~.AutotoolsPackage.build_targets` | Specify ``make`` |
- | | targets for the |
- | | build phase |
- +-----------------------------------------------+--------------------+
- | :py:attr:`~.AutotoolsPackage.install_targets` | Specify ``make`` |
- | | targets for the |
- | | install phase |
- +-----------------------------------------------+--------------------+
- | :py:meth:`~.AutotoolsPackage.check` | Run build time |
- | | tests if required |
- +-----------------------------------------------+--------------------+
-
- """
- #: Phases of a GNU Autotools package
- phases = ['autoreconf', 'configure', 'build', 'install']
- #: This attribute is used in UI queries that need to know the build
- #: system base class
- build_system_class = 'AutotoolsPackage'
- #: Whether or not to update ``config.guess`` on old architectures
- patch_config_guess = True
-
- #: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.build`
- #: phase
- build_targets = []
- #: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.install`
- #: phase
- install_targets = ['install']
-
- #: Callback names for build-time test
- build_time_test_callbacks = ['check']
-
- #: Callback names for install-time test
- install_time_test_callbacks = ['installcheck']
-
- #: Set to true to force the autoreconf step even if configure is present
- force_autoreconf = False
- #: Options to be passed to autoreconf when using the default implementation
- autoreconf_extra_args = []
-
- @run_after('autoreconf')
- def _do_patch_config_guess(self):
- """Some packages ship with an older config.guess and need to have
- this updated when installed on a newer architecture. In particular,
- config.guess fails for PPC64LE for version prior to a 2013-06-10
- build date (automake 1.13.4)."""
-
- if not self.patch_config_guess or not self.spec.satisfies(
- 'target=ppc64le'
- ):
- return
- my_config_guess = None
- config_guess = None
- if os.path.exists('config.guess'):
- # First search the top-level source directory
- my_config_guess = 'config.guess'
- else:
- # Then search in all sub directories.
- # We would like to use AC_CONFIG_AUX_DIR, but not all packages
- # ship with their configure.in or configure.ac.
- d = '.'
- dirs = [os.path.join(d, o) for o in os.listdir(d)
- if os.path.isdir(os.path.join(d, o))]
- for dirname in dirs:
- path = os.path.join(dirname, 'config.guess')
- if os.path.exists(path):
- my_config_guess = path
-
- if my_config_guess is not None:
- try:
- check_call([my_config_guess], stdout=PIPE, stderr=PIPE)
- # The package's config.guess already runs OK, so just use it
- return
- except Exception:
- pass
- else:
- return
-
- # Look for a spack-installed automake package
- if 'automake' in self.spec:
- automake_path = os.path.join(self.spec['automake'].prefix, 'share',
- 'automake-' +
- str(self.spec['automake'].version))
- path = os.path.join(automake_path, 'config.guess')
- if os.path.exists(path):
- config_guess = path
- # Look for the system's config.guess
- if config_guess is None and os.path.exists('/usr/share'):
- automake_dir = [s for s in os.listdir('/usr/share') if
- "automake" in s]
- if automake_dir:
- automake_path = os.path.join('/usr/share', automake_dir[0])
- path = os.path.join(automake_path, 'config.guess')
- if os.path.exists(path):
- config_guess = path
- if config_guess is not None:
- try:
- check_call([config_guess], stdout=PIPE, stderr=PIPE)
- mod = os.stat(my_config_guess).st_mode & 0o777 | stat.S_IWUSR
- os.chmod(my_config_guess, mod)
- shutil.copyfile(config_guess, my_config_guess)
- return
- except Exception:
- pass
-
- raise RuntimeError('Failed to find suitable config.guess')
-
- @property
- def configure_directory(self):
- """Returns the directory where 'configure' resides.
-
- :return: directory where to find configure
- """
- return self.stage.source_path
-
- @property
- def configure_abs_path(self):
- # Absolute path to configure
- configure_abs_path = join_path(
- os.path.abspath(self.configure_directory), 'configure'
- )
- return configure_abs_path
-
- @property
- def build_directory(self):
- """Override to provide another place to build the package"""
- return self.configure_directory
-
- def default_flag_handler(self, spack_env, flag_val):
- # Relies on being the first thing that can affect the spack_env
- # EnvironmentModification after it is instantiated or no other
- # method trying to affect these variables. Currently both are true
- # flag_val is a tuple (flag, value_list).
- spack_env.set(flag_val[0].upper(),
- ' '.join(flag_val[1]))
- return []
-
- @run_before('autoreconf')
- def delete_configure_to_force_update(self):
- if self.force_autoreconf:
- force_remove(self.configure_abs_path)
-
- def autoreconf(self, spec, prefix):
- """Not needed usually, configure should be already there"""
- # If configure exists nothing needs to be done
- if os.path.exists(self.configure_abs_path):
- return
- # Else try to regenerate it
- autotools = ['m4', 'autoconf', 'automake', 'libtool']
- missing = [x for x in autotools if x not in spec]
- if missing:
- msg = 'Cannot generate configure: missing dependencies {0}'
- raise RuntimeError(msg.format(missing))
- tty.msg('Configure script not found: trying to generate it')
- tty.warn('*********************************************************')
- tty.warn('* If the default procedure fails, consider implementing *')
- tty.warn('* a custom AUTORECONF phase in the package *')
- tty.warn('*********************************************************')
- with working_dir(self.configure_directory):
- m = inspect.getmodule(self)
- # This part should be redundant in principle, but
- # won't hurt
- m.libtoolize()
- m.aclocal()
- # This line is what is needed most of the time
- # --install, --verbose, --force
- autoreconf_args = ['-ivf']
- if 'pkg-config' in spec:
- autoreconf_args += [
- '-I',
- join_path(spec['pkg-config'].prefix, 'share', 'aclocal'),
- ]
- autoreconf_args += self.autoreconf_extra_args
- m.autoreconf(*autoreconf_args)
-
- @run_after('autoreconf')
- def set_configure_or_die(self):
- """Checks the presence of a ``configure`` file after the
- autoreconf phase. If it is found sets a module attribute
- appropriately, otherwise raises an error.
-
- :raises RuntimeError: if a configure script is not found in
- :py:meth:`~AutotoolsPackage.configure_directory`
- """
- # Check if a configure script is there. If not raise a RuntimeError.
- if not os.path.exists(self.configure_abs_path):
- msg = 'configure script not found in {0}'
- raise RuntimeError(msg.format(self.configure_directory))
-
- # Monkey-patch the configure script in the corresponding module
- inspect.getmodule(self).configure = Executable(
- self.configure_abs_path
- )
-
- def configure_args(self):
- """Produces a list containing all the arguments that must be passed to
- configure, except ``--prefix`` which will be pre-pended to the list.
-
- :return: list of arguments for configure
- """
- return []
-
- def configure(self, spec, prefix):
- """Runs configure with the arguments specified in
- :py:meth:`~.AutotoolsPackage.configure_args`
- and an appropriately set prefix.
- """
- options = ['--prefix={0}'.format(prefix)] + self.configure_args()
-
- with working_dir(self.build_directory, create=True):
- inspect.getmodule(self).configure(*options)
-
- def build(self, spec, prefix):
- """Makes the build targets specified by
- :py:attr:``~.AutotoolsPackage.build_targets``
- """
- with working_dir(self.build_directory):
- inspect.getmodule(self).make(*self.build_targets)
-
- def install(self, spec, prefix):
- """Makes the install targets specified by
- :py:attr:``~.AutotoolsPackage.install_targets``
- """
- with working_dir(self.build_directory):
- inspect.getmodule(self).make(*self.install_targets)
-
- run_after('build')(PackageBase._run_default_build_time_test_callbacks)
-
- def check(self):
- """Searches the Makefile for targets ``test`` and ``check``
- and runs them if found.
- """
- with working_dir(self.build_directory):
- self._if_make_target_execute('test')
- self._if_make_target_execute('check')
-
- def _activate_or_not(
- self,
- name,
- activation_word,
- deactivation_word,
- activation_value=None
- ):
- """This function contains the current implementation details of
- :py:meth:`~.AutotoolsPackage.with_or_without` and
- :py:meth:`~.AutotoolsPackage.enable_or_disable`.
-
- Args:
- name (str): name of the variant that is being processed
- activation_word (str): the default activation word ('with' in the
- case of ``with_or_without``)
- deactivation_word (str): the default deactivation word ('without'
- in the case of ``with_or_without``)
- activation_value (callable): callable that accepts a single
- value. This value is either one of the allowed values for a
- multi-valued variant or the name of a bool-valued variant.
- Returns the parameter to be used when the value is activated.
-
- The special value 'prefix' can also be assigned and will return
- ``spec[name].prefix`` as activation parameter.
-
- Examples:
-
- Given a package with:
-
- .. code-block:: python
-
- variant('foo', values=('x', 'y'), description='')
- variant('bar', default=True, description='')
-
- calling this function like:
-
- .. code-block:: python
-
- _activate_or_not(
- 'foo', 'with', 'without', activation_value='prefix'
- )
- _activate_or_not('bar', 'with', 'without')
-
- will generate the following configuration options:
-
- .. code-block:: console
-
- --with-x=<prefix-to-x> --without-y --with-bar
-
- for ``<spec-name> foo=x +bar``
-
- Returns:
- list of strings that corresponds to the activation/deactivation
- of the variant that has been processed
-
- Raises:
- KeyError: if name is not among known variants
- """
- spec = self.spec
- args = []
-
- if activation_value == 'prefix':
- activation_value = lambda x: spec[x].prefix
-
- # Defensively look that the name passed as argument is among
- # variants
- if name not in self.variants:
- msg = '"{0}" is not a variant of "{1}"'
- raise KeyError(msg.format(name, self.name))
-
- # Create a list of pairs. Each pair includes a configuration
- # option and whether or not that option is activated
- if set(self.variants[name].values) == set((True, False)):
- # BoolValuedVariant carry information about a single option.
- # Nonetheless, for uniformity of treatment we'll package them
- # in an iterable of one element.
- condition = '+{name}'.format(name=name)
- options = [(name, condition in spec)]
- else:
- condition = '{name}={value}'
- options = [
- (value, condition.format(name=name, value=value) in spec)
- for value in self.variants[name].values
- ]
-
- # For each allowed value in the list of values
- for option_value, activated in options:
- # Search for an override in the package for this value
- override_name = '{0}_or_{1}_{2}'.format(
- activation_word, deactivation_word, option_value
- )
- line_generator = getattr(self, override_name, None)
- # If not available use a sensible default
- if line_generator is None:
- def _default_generator(is_activated):
- if is_activated:
- line = '--{0}-{1}'.format(
- activation_word, option_value
- )
- if activation_value is not None and activation_value(option_value): # NOQA=ignore=E501
- line += '={0}'.format(
- activation_value(option_value)
- )
- return line
- return '--{0}-{1}'.format(deactivation_word, option_value)
- line_generator = _default_generator
- args.append(line_generator(activated))
- return args
-
- def with_or_without(self, name, activation_value=None):
- """Inspects a variant and returns the arguments that activate
- or deactivate the selected feature(s) for the configure options.
-
- This function works on all type of variants. For bool-valued variants
- it will return by default ``--with-{name}`` or ``--without-{name}``.
- For other kinds of variants it will cycle over the allowed values and
- return either ``--with-{value}`` or ``--without-{value}``.
-
- If activation_value is given, then for each possible value of the
- variant, the option ``--with-{value}=activation_value(value)`` or
- ``--without-{value}`` will be added depending on whether or not
- ``variant=value`` is in the spec.
-
- Args:
- name (str): name of a valid multi-valued variant
- activation_value (callable): callable that accepts a single
- value and returns the parameter to be used leading to an entry
- of the type ``--with-{name}={parameter}``.
-
- The special value 'prefix' can also be assigned and will return
- ``spec[name].prefix`` as activation parameter.
-
- Returns:
- list of arguments to configure
- """
- return self._activate_or_not(name, 'with', 'without', activation_value)
-
- def enable_or_disable(self, name, activation_value=None):
- """Same as :py:meth:`~.AutotoolsPackage.with_or_without` but substitute
- ``with`` with ``enable`` and ``without`` with ``disable``.
-
- Args:
- name (str): name of a valid multi-valued variant
- activation_value (callable): if present accepts a single value
- and returns the parameter to be used leading to an entry of the
- type ``--enable-{name}={parameter}``
-
- The special value 'prefix' can also be assigned and will return
- ``spec[name].prefix`` as activation parameter.
-
- Returns:
- list of arguments to configure
- """
- return self._activate_or_not(
- name, 'enable', 'disable', activation_value
- )
-
- run_after('install')(PackageBase._run_default_install_time_test_callbacks)
-
- def installcheck(self):
- """Searches the Makefile for an ``installcheck`` target
- and runs it if found.
- """
- with working_dir(self.build_directory):
- self._if_make_target_execute('installcheck')
-
- # Check that self.prefix is there after installation
- run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/docs/tutorial/examples/Cmake/cmake_class.py b/lib/spack/docs/tutorial/examples/Cmake/cmake_class.py
deleted file mode 100644
index ca9ec6f803..0000000000
--- a/lib/spack/docs/tutorial/examples/Cmake/cmake_class.py
+++ /dev/null
@@ -1,224 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/spack/spack
-# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-
-import inspect
-import os
-import platform
-
-import spack.build_environment
-from llnl.util.filesystem import working_dir, join_path
-from spack.util.environment import filter_system_paths
-from spack.directives import depends_on, variant
-from spack.package import PackageBase, InstallError, run_after
-
-
-class CMakePackage(PackageBase):
- """Specialized class for packages built using CMake
-
- For more information on the CMake build system, see:
- https://cmake.org/cmake/help/latest/
-
- This class provides three phases that can be overridden:
-
- 1. :py:meth:`~.CMakePackage.cmake`
- 2. :py:meth:`~.CMakePackage.build`
- 3. :py:meth:`~.CMakePackage.install`
-
- They all have sensible defaults and for many packages the only thing
- necessary will be to override :py:meth:`~.CMakePackage.cmake_args`.
- For a finer tuning you may also override:
-
- +-----------------------------------------------+--------------------+
- | **Method** | **Purpose** |
- +===============================================+====================+
- | :py:meth:`~.CMakePackage.root_cmakelists_dir` | Location of the |
- | | root CMakeLists.txt|
- +-----------------------------------------------+--------------------+
- | :py:meth:`~.CMakePackage.build_directory` | Directory where to |
- | | build the package |
- +-----------------------------------------------+--------------------+
-
-
- """
- #: Phases of a CMake package
- phases = ['cmake', 'build', 'install']
- #: This attribute is used in UI queries that need to know the build
- #: system base class
- build_system_class = 'CMakePackage'
-
- build_targets = []
- install_targets = ['install']
-
- build_time_test_callbacks = ['check']
-
- #: The build system generator to use.
- #:
- #: See ``cmake --help`` for a list of valid generators.
- #: Currently, "Unix Makefiles" and "Ninja" are the only generators
- #: that Spack supports. Defaults to "Unix Makefiles".
- #:
- #: See https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html
- #: for more information.
- generator = 'Unix Makefiles'
-
- # https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
- variant('build_type', default='RelWithDebInfo',
- description='CMake build type',
- values=('Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel'))
-
- depends_on('cmake', type='build')
-
- @property
- def root_cmakelists_dir(self):
- """The relative path to the directory containing CMakeLists.txt
-
- This path is relative to the root of the extracted tarball,
- not to the ``build_directory``. Defaults to the current directory.
-
- :return: directory containing CMakeLists.txt
- """
- return self.stage.source_path
-
- @property
- def std_cmake_args(self):
- """Standard cmake arguments provided as a property for
- convenience of package writers
-
- :return: standard cmake arguments
- """
- # standard CMake arguments
- return CMakePackage._std_args(self)
-
- @staticmethod
- def _std_args(pkg):
- """Computes the standard cmake arguments for a generic package"""
- try:
- generator = pkg.generator
- except AttributeError:
- generator = 'Unix Makefiles'
-
- # Make sure a valid generator was chosen
- valid_generators = ['Unix Makefiles', 'Ninja']
- if generator not in valid_generators:
- msg = "Invalid CMake generator: '{0}'\n".format(generator)
- msg += "CMakePackage currently supports the following "
- msg += "generators: '{0}'".format("', '".join(valid_generators))
- raise InstallError(msg)
-
- try:
- build_type = pkg.spec.variants['build_type'].value
- except KeyError:
- build_type = 'RelWithDebInfo'
-
- args = [
- '-G', generator,
- '-DCMAKE_INSTALL_PREFIX:PATH={0}'.format(pkg.prefix),
- '-DCMAKE_BUILD_TYPE:STRING={0}'.format(build_type),
- '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON'
- ]
-
- if platform.mac_ver()[0]:
- args.append('-DCMAKE_FIND_FRAMEWORK:STRING=LAST')
-
- # Set up CMake rpath
- args.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=FALSE')
- rpaths = ':'.join(spack.build_environment.get_rpaths(pkg))
- args.append('-DCMAKE_INSTALL_RPATH:STRING={0}'.format(rpaths))
- # CMake's find_package() looks in CMAKE_PREFIX_PATH first, help CMake
- # to find immediate link dependencies in right places:
- deps = [d.prefix for d in
- pkg.spec.dependencies(deptype=('build', 'link'))]
- deps = filter_system_paths(deps)
- args.append('-DCMAKE_PREFIX_PATH:STRING={0}'.format(';'.join(deps)))
- return args
-
- @property
- def build_directory(self):
- """Returns the directory to use when building the package
-
- :return: directory where to build the package
- """
- return join_path(self.stage.source_path, 'spack-build')
-
- def default_flag_handler(self, spack_env, flag_val):
- # Relies on being the first thing that can affect the spack_env
- # EnvironmentModification after it is instantiated or no other
- # method trying to affect these variables. Currently both are true
- # flag_val is a tuple (flag, value_list)
- spack_env.set(flag_val[0].upper(),
- ' '.join(flag_val[1]))
- return []
-
- def cmake_args(self):
- """Produces a list containing all the arguments that must be passed to
- cmake, except:
-
- * CMAKE_INSTALL_PREFIX
- * CMAKE_BUILD_TYPE
-
- which will be set automatically.
-
- :return: list of arguments for cmake
- """
- return []
-
- def cmake(self, spec, prefix):
- """Runs ``cmake`` in the build directory"""
- options = [os.path.abspath(self.root_cmakelists_dir)]
- options += self.std_cmake_args
- options += self.cmake_args()
- with working_dir(self.build_directory, create=True):
- inspect.getmodule(self).cmake(*options)
-
- def build(self, spec, prefix):
- """Make the build targets"""
- with working_dir(self.build_directory):
- if self.generator == 'Unix Makefiles':
- inspect.getmodule(self).make(*self.build_targets)
- elif self.generator == 'Ninja':
- inspect.getmodule(self).ninja(*self.build_targets)
-
- def install(self, spec, prefix):
- """Make the install targets"""
- with working_dir(self.build_directory):
- if self.generator == 'Unix Makefiles':
- inspect.getmodule(self).make(*self.install_targets)
- elif self.generator == 'Ninja':
- inspect.getmodule(self).ninja(*self.install_targets)
-
- run_after('build')(PackageBase._run_default_build_time_test_callbacks)
-
- def check(self):
- """Searches the CMake-generated Makefile for the target ``test``
- and runs it if found.
- """
- with working_dir(self.build_directory):
- if self.generator == 'Unix Makefiles':
- self._if_make_target_execute('test')
- elif self.generator == 'Ninja':
- self._if_ninja_target_execute('test')
-
- # Check that self.prefix is there after installation
- run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/docs/tutorial/examples/Makefile/makefile_class.py b/lib/spack/docs/tutorial/examples/Makefile/makefile_class.py
deleted file mode 100644
index 286b702fc9..0000000000
--- a/lib/spack/docs/tutorial/examples/Makefile/makefile_class.py
+++ /dev/null
@@ -1,129 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/spack/spack
-# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-
-import inspect
-
-import llnl.util.tty as tty
-from llnl.util.filesystem import working_dir
-from spack.package import PackageBase, run_after
-
-
-class MakefilePackage(PackageBase):
- """Specialized class for packages that are built using editable Makefiles
-
- This class provides three phases that can be overridden:
-
- 1. :py:meth:`~.MakefilePackage.edit`
- 2. :py:meth:`~.MakefilePackage.build`
- 3. :py:meth:`~.MakefilePackage.install`
-
- It is usually necessary to override the :py:meth:`~.MakefilePackage.edit`
- phase, while :py:meth:`~.MakefilePackage.build` and
- :py:meth:`~.MakefilePackage.install` have sensible defaults.
- For a finer tuning you may override:
-
- +-----------------------------------------------+--------------------+
- | **Method** | **Purpose** |
- +===============================================+====================+
- | :py:attr:`~.MakefilePackage.build_targets` | Specify ``make`` |
- | | targets for the |
- | | build phase |
- +-----------------------------------------------+--------------------+
- | :py:attr:`~.MakefilePackage.install_targets` | Specify ``make`` |
- | | targets for the |
- | | install phase |
- +-----------------------------------------------+--------------------+
- | :py:meth:`~.MakefilePackage.build_directory` | Directory where the|
- | | Makefile is located|
- +-----------------------------------------------+--------------------+
- """
- #: Phases of a package that is built with an hand-written Makefile
- phases = ['edit', 'build', 'install']
- #: This attribute is used in UI queries that need to know the build
- #: system base class
- build_system_class = 'MakefilePackage'
-
- #: Targets for ``make`` during the :py:meth:`~.MakefilePackage.build`
- #: phase
- build_targets = []
- #: Targets for ``make`` during the :py:meth:`~.MakefilePackage.install`
- #: phase
- install_targets = ['install']
-
- #: Callback names for build-time test
- build_time_test_callbacks = ['check']
-
- #: Callback names for install-time test
- install_time_test_callbacks = ['installcheck']
-
- @property
- def build_directory(self):
- """Returns the directory containing the main Makefile
-
- :return: build directory
- """
- return self.stage.source_path
-
- def edit(self, spec, prefix):
- """Edits the Makefile before calling make. This phase cannot
- be defaulted.
- """
- tty.msg('Using default implementation: skipping edit phase.')
-
- def build(self, spec, prefix):
- """Calls make, passing :py:attr:`~.MakefilePackage.build_targets`
- as targets.
- """
- with working_dir(self.build_directory):
- inspect.getmodule(self).make(*self.build_targets)
-
- def install(self, spec, prefix):
- """Calls make, passing :py:attr:`~.MakefilePackage.install_targets`
- as targets.
- """
- with working_dir(self.build_directory):
- inspect.getmodule(self).make(*self.install_targets)
-
- run_after('build')(PackageBase._run_default_build_time_test_callbacks)
-
- def check(self):
- """Searches the Makefile for targets ``test`` and ``check``
- and runs them if found.
- """
- with working_dir(self.build_directory):
- self._if_make_target_execute('test')
- self._if_make_target_execute('check')
-
- run_after('install')(PackageBase._run_default_install_time_test_callbacks)
-
- def installcheck(self):
- """Searches the Makefile for an ``installcheck`` target
- and runs it if found.
- """
- with working_dir(self.build_directory):
- self._if_make_target_execute('installcheck')
-
- # Check that self.prefix is there after installation
- run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/docs/tutorial/examples/PyPackage/python_package_class.py b/lib/spack/docs/tutorial/examples/PyPackage/python_package_class.py
deleted file mode 100644
index b07a196fff..0000000000
--- a/lib/spack/docs/tutorial/examples/PyPackage/python_package_class.py
+++ /dev/null
@@ -1,399 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/spack/spack
-# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-
-import inspect
-import os
-
-from spack.directives import depends_on, extends
-from spack.package import PackageBase, run_after
-
-from llnl.util.filesystem import working_dir
-
-
-class PythonPackage(PackageBase):
- """Specialized class for packages that are built using Python
- setup.py files
-
- This class provides the following phases that can be overridden:
-
- * build
- * build_py
- * build_ext
- * build_clib
- * build_scripts
- * clean
- * install
- * install_lib
- * install_headers
- * install_scripts
- * install_data
- * sdist
- * register
- * bdist
- * bdist_dumb
- * bdist_rpm
- * bdist_wininst
- * upload
- * check
-
- These are all standard setup.py commands and can be found by running:
-
- .. code-block:: console
-
- $ python setup.py --help-commands
-
- By default, only the 'build' and 'install' phases are run, but if you
- need to run more phases, simply modify your ``phases`` list like so:
-
- .. code-block:: python
-
- phases = ['build_ext', 'install', 'bdist']
-
- Each phase provides a function <phase> that runs:
-
- .. code-block:: console
-
- $ python setup.py --no-user-cfg <phase>
-
- Each phase also has a <phase_args> function that can pass arguments to
- this call. All of these functions are empty except for the ``install_args``
- function, which passes ``--prefix=/path/to/installation/directory``.
-
- If you need to run a phase which is not a standard setup.py command,
- you'll need to define a function for it like so:
-
- .. code-block:: python
-
- def configure(self, spec, prefix):
- self.setup_py('configure')
- """
- # Default phases
- phases = ['build', 'install']
-
- # Name of modules that the Python package provides
- # This is used to test whether or not the installation succeeded
- # These names generally come from running:
- #
- # >>> import setuptools
- # >>> setuptools.find_packages()
- #
- # in the source tarball directory
- import_modules = []
-
- # To be used in UI queries that require to know which
- # build-system class we are using
- build_system_class = 'PythonPackage'
-
- #: Callback names for build-time test
- build_time_test_callbacks = ['test']
-
- #: Callback names for install-time test
- install_time_test_callbacks = ['import_module_test']
-
- extends('python')
-
- depends_on('python', type=('build', 'run'))
-
- def setup_file(self):
- """Returns the name of the setup file to use."""
- return 'setup.py'
-
- @property
- def build_directory(self):
- """The directory containing the ``setup.py`` file."""
- return self.stage.source_path
-
- def python(self, *args, **kwargs):
- inspect.getmodule(self).python(*args, **kwargs)
-
- def setup_py(self, *args, **kwargs):
- setup = self.setup_file()
-
- with working_dir(self.build_directory):
- self.python(setup, '--no-user-cfg', *args, **kwargs)
-
- def _setup_command_available(self, command):
- """Determines whether or not a setup.py command exists.
-
- Args:
- command (str): The command to look for
-
- Returns:
- bool: True if the command is found, else False
- """
- kwargs = {
- 'output': os.devnull,
- 'error': os.devnull,
- 'fail_on_error': False
- }
-
- python = inspect.getmodule(self).python
- setup = self.setup_file()
-
- python(setup, '--no-user-cfg', command, '--help', **kwargs)
- return python.returncode == 0
-
- # The following phases and their descriptions come from:
- # $ python setup.py --help-commands
-
- # Standard commands
-
- def build(self, spec, prefix):
- """Build everything needed to install."""
- args = self.build_args(spec, prefix)
-
- self.setup_py('build', *args)
-
- def build_args(self, spec, prefix):
- """Arguments to pass to build."""
- return []
-
- def build_py(self, spec, prefix):
- '''"Build" pure Python modules (copy to build directory).'''
- args = self.build_py_args(spec, prefix)
-
- self.setup_py('build_py', *args)
-
- def build_py_args(self, spec, prefix):
- """Arguments to pass to build_py."""
- return []
-
- def build_ext(self, spec, prefix):
- """Build C/C++ extensions (compile/link to build directory)."""
- args = self.build_ext_args(spec, prefix)
-
- self.setup_py('build_ext', *args)
-
- def build_ext_args(self, spec, prefix):
- """Arguments to pass to build_ext."""
- return []
-
- def build_clib(self, spec, prefix):
- """Build C/C++ libraries used by Python extensions."""
- args = self.build_clib_args(spec, prefix)
-
- self.setup_py('build_clib', *args)
-
- def build_clib_args(self, spec, prefix):
- """Arguments to pass to build_clib."""
- return []
-
- def build_scripts(self, spec, prefix):
- '''"Build" scripts (copy and fixup #! line).'''
- args = self.build_scripts_args(spec, prefix)
-
- self.setup_py('build_scripts', *args)
-
- def clean(self, spec, prefix):
- """Clean up temporary files from 'build' command."""
- args = self.clean_args(spec, prefix)
-
- self.setup_py('clean', *args)
-
- def clean_args(self, spec, prefix):
- """Arguments to pass to clean."""
- return []
-
- def install(self, spec, prefix):
- """Install everything from build directory."""
- args = self.install_args(spec, prefix)
-
- self.setup_py('install', *args)
-
- def install_args(self, spec, prefix):
- """Arguments to pass to install."""
- args = ['--prefix={0}'.format(prefix)]
-
- # This option causes python packages (including setuptools) NOT
- # to create eggs or easy-install.pth files. Instead, they
- # install naturally into $prefix/pythonX.Y/site-packages.
- #
- # Eggs add an extra level of indirection to sys.path, slowing
- # down large HPC runs. They are also deprecated in favor of
- # wheels, which use a normal layout when installed.
- #
- # Spack manages the package directory on its own by symlinking
- # extensions into the site-packages directory, so we don't really
- # need the .pth files or egg directories, anyway.
- if ('py-setuptools' == spec.name or # this is setuptools, or
- 'py-setuptools' in spec._dependencies): # it's an immediate dep
- args += ['--single-version-externally-managed', '--root=/']
-
- return args
-
- def install_lib(self, spec, prefix):
- """Install all Python modules (extensions and pure Python)."""
- args = self.install_lib_args(spec, prefix)
-
- self.setup_py('install_lib', *args)
-
- def install_lib_args(self, spec, prefix):
- """Arguments to pass to install_lib."""
- return []
-
- def install_headers(self, spec, prefix):
- """Install C/C++ header files."""
- args = self.install_headers_args(spec, prefix)
-
- self.setup_py('install_headers', *args)
-
- def install_headers_args(self, spec, prefix):
- """Arguments to pass to install_headers."""
- return []
-
- def install_scripts(self, spec, prefix):
- """Install scripts (Python or otherwise)."""
- args = self.install_scripts_args(spec, prefix)
-
- self.setup_py('install_scripts', *args)
-
- def install_scripts_args(self, spec, prefix):
- """Arguments to pass to install_scripts."""
- return []
-
- def install_data(self, spec, prefix):
- """Install data files."""
- args = self.install_data_args(spec, prefix)
-
- self.setup_py('install_data', *args)
-
- def install_data_args(self, spec, prefix):
- """Arguments to pass to install_data."""
- return []
-
- def sdist(self, spec, prefix):
- """Create a source distribution (tarball, zip file, etc.)."""
- args = self.sdist_args(spec, prefix)
-
- self.setup_py('sdist', *args)
-
- def sdist_args(self, spec, prefix):
- """Arguments to pass to sdist."""
- return []
-
- def register(self, spec, prefix):
- """Register the distribution with the Python package index."""
- args = self.register_args(spec, prefix)
-
- self.setup_py('register', *args)
-
- def register_args(self, spec, prefix):
- """Arguments to pass to register."""
- return []
-
- def bdist(self, spec, prefix):
- """Create a built (binary) distribution."""
- args = self.bdist_args(spec, prefix)
-
- self.setup_py('bdist', *args)
-
- def bdist_args(self, spec, prefix):
- """Arguments to pass to bdist."""
- return []
-
- def bdist_dumb(self, spec, prefix):
- '''Create a "dumb" built distribution.'''
- args = self.bdist_dumb_args(spec, prefix)
-
- self.setup_py('bdist_dumb', *args)
-
- def bdist_dumb_args(self, spec, prefix):
- """Arguments to pass to bdist_dumb."""
- return []
-
- def bdist_rpm(self, spec, prefix):
- """Create an RPM distribution."""
- args = self.bdist_rpm(spec, prefix)
-
- self.setup_py('bdist_rpm', *args)
-
- def bdist_rpm_args(self, spec, prefix):
- """Arguments to pass to bdist_rpm."""
- return []
-
- def bdist_wininst(self, spec, prefix):
- """Create an executable installer for MS Windows."""
- args = self.bdist_wininst_args(spec, prefix)
-
- self.setup_py('bdist_wininst', *args)
-
- def bdist_wininst_args(self, spec, prefix):
- """Arguments to pass to bdist_wininst."""
- return []
-
- def upload(self, spec, prefix):
- """Upload binary package to PyPI."""
- args = self.upload_args(spec, prefix)
-
- self.setup_py('upload', *args)
-
- def upload_args(self, spec, prefix):
- """Arguments to pass to upload."""
- return []
-
- def check(self, spec, prefix):
- """Perform some checks on the package."""
- args = self.check_args(spec, prefix)
-
- self.setup_py('check', *args)
-
- def check_args(self, spec, prefix):
- """Arguments to pass to check."""
- return []
-
- # Testing
-
- def test(self):
- """Run unit tests after in-place build.
-
- These tests are only run if the package actually has a 'test' command.
- """
- if self._setup_command_available('test'):
- args = self.test_args(self.spec, self.prefix)
-
- self.setup_py('test', *args)
-
- def test_args(self, spec, prefix):
- """Arguments to pass to test."""
- return []
-
- run_after('build')(PackageBase._run_default_build_time_test_callbacks)
-
- def import_module_test(self):
- """Attempts to import the module that was just installed.
-
- This test is only run if the package overrides
- :py:attr:`import_modules` with a list of module names."""
-
- # Make sure we are importing the installed modules,
- # not the ones in the current directory
- with working_dir('..'):
- for module in self.import_modules:
- self.python('-c', 'import {0}'.format(module))
-
- run_after('install')(PackageBase._run_default_install_time_test_callbacks)
-
- # Check that self.prefix is there after installation
- run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/docs/tutorial_buildsystems.rst b/lib/spack/docs/tutorial_buildsystems.rst
index 8f5dce0892..d92db13062 100644
--- a/lib/spack/docs/tutorial_buildsystems.rst
+++ b/lib/spack/docs/tutorial_buildsystems.rst
@@ -103,7 +103,7 @@ This will open the :code:`AutotoolsPackage` file in your text editor.
long examples. We only show what is relevant to the packager.
-.. literalinclude:: tutorial/examples/Autotools/autotools_class.py
+.. literalinclude:: ../../../lib/spack/spack/build_systems/autotools.py
:language: python
:emphasize-lines: 42,45,62
:lines: 40-95,259-267
@@ -202,7 +202,7 @@ Let's also take a look inside the :code:`MakefilePackage` class:
Take note of the following:
-.. literalinclude:: tutorial/examples/Makefile/makefile_class.py
+.. literalinclude:: ../../../lib/spack/spack/build_systems/makefile.py
:language: python
:lines: 33-79,89-107
:emphasize-lines: 48,54,61
@@ -480,7 +480,7 @@ Let's look at these defaults in the :code:`CMakePackage` class:
And go into a bit of detail on the highlighted sections:
-.. literalinclude:: tutorial/examples/Cmake/cmake_class.py
+.. literalinclude:: ../../../lib/spack/spack/build_systems/cmake.py
:language: python
:lines: 37-92, 94-155, 174-211
:emphasize-lines: 57,68,86,94,96,99,100,101,102,111,117,135,136
@@ -675,7 +675,7 @@ at the :code:`PythonPackage` class:
We see the following:
-.. literalinclude:: tutorial/examples/PyPackage/python_package_class.py
+.. literalinclude:: ../../../lib/spack/spack/build_systems/python.py
:language: python
:lines: 35, 161-364
:linenos:
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index f1cf0d5049..cab455695e 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -128,7 +128,6 @@ class MakeExecutable(Executable):
def set_compiler_environment_variables(pkg, env):
assert(pkg.spec.concrete)
compiler = pkg.compiler
- flags = pkg.spec.compiler_flags
# Set compiler variables used by CMake and autotools
assert all(key in compiler.link_paths for key in (
@@ -160,11 +159,28 @@ def set_compiler_environment_variables(pkg, env):
env.set('SPACK_F77_RPATH_ARG', compiler.f77_rpath_arg)
env.set('SPACK_FC_RPATH_ARG', compiler.fc_rpath_arg)
- # Add every valid compiler flag to the environment, prefixed with "SPACK_"
+ # Trap spack-tracked compiler flags as appropriate.
+ # env_flags are easy to accidentally override.
+ inject_flags = {}
+ env_flags = {}
+ build_system_flags = {}
+ for flag in spack.spec.FlagMap.valid_compiler_flags():
+ injf, envf, bsf = pkg.flag_handler(flag, pkg.spec.compiler_flags[flag])
+ inject_flags[flag] = injf or []
+ env_flags[flag] = envf or []
+ build_system_flags[flag] = bsf or []
+
+ # Place compiler flags as specified by flag_handler
for flag in spack.spec.FlagMap.valid_compiler_flags():
# Concreteness guarantees key safety here
- if flags[flag] != []:
- env.set('SPACK_' + flag.upper(), ' '.join(f for f in flags[flag]))
+ if inject_flags[flag]:
+ # variables SPACK_<FLAG> inject flags through wrapper
+ var_name = 'SPACK_{0}'.format(flag.upper())
+ env.set(var_name, ' '.join(f for f in inject_flags[flag]))
+ if env_flags[flag]:
+ # implicit variables
+ env.set(flag.upper(), ' '.join(f for f in env_flags[flag]))
+ pkg.flags_to_build_system_args(build_system_flags)
env.set('SPACK_COMPILER_SPEC', str(pkg.spec.compiler))
@@ -559,19 +575,6 @@ def setup_package(pkg, dirty):
for s in pkg.spec.traverse():
assert s.package.spec is s
- # Trap spack-tracked compiler flags as appropriate.
- # Must be before set_compiler_environment_variables
- # Current implementation of default flag handler relies on this being
- # the first thing to affect the spack_env (so there is no appending), or
- # on no other build_environment methods trying to affect these variables
- # (CFLAGS, CXXFLAGS, etc). Currently both are true, either is sufficient.
- for flag in spack.spec.FlagMap.valid_compiler_flags():
- trap_func = getattr(pkg, flag + '_handler',
- getattr(pkg, 'default_flag_handler',
- lambda x, y: y[1]))
- flag_val = pkg.spec.compiler_flags[flag]
- pkg.spec.compiler_flags[flag] = trap_func(spack_env, (flag, flag_val))
-
set_compiler_environment_variables(pkg, spack_env)
set_build_environment_variables(pkg, spack_env, dirty)
pkg.architecture.platform.setup_platform_environment(pkg, spack_env)
diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py
index f39eed6997..5ef06754ca 100644
--- a/lib/spack/spack/build_systems/autotools.py
+++ b/lib/spack/spack/build_systems/autotools.py
@@ -182,15 +182,6 @@ class AutotoolsPackage(PackageBase):
"""Override to provide another place to build the package"""
return self.configure_directory
- def default_flag_handler(self, spack_env, flag_val):
- # Relies on being the first thing that can affect the spack_env
- # EnvironmentModification after it is instantiated or no other
- # method trying to affect these variables. Currently both are true
- # flag_val is a tuple (flag, value_list).
- spack_env.set(flag_val[0].upper(),
- ' '.join(flag_val[1]))
- return []
-
@run_before('autoreconf')
def delete_configure_to_force_update(self):
if self.force_autoreconf:
@@ -256,12 +247,24 @@ class AutotoolsPackage(PackageBase):
"""
return []
+ def flags_to_build_system_args(self, flags):
+ """Produces a list of all command line arguments to pass specified
+ compiler flags to configure."""
+ # Has to be dynamic attribute due to caching.
+ setattr(self, 'configure_flag_args', [])
+ for flag, values in flags.items():
+ if values:
+ values_str = '{0}={1}'.format(flag.upper(), ' '.join(values))
+ self.configure_flag_args.append(values_str)
+
def configure(self, spec, prefix):
"""Runs configure with the arguments specified in
:py:meth:`~.AutotoolsPackage.configure_args`
and an appropriately set prefix.
"""
- options = ['--prefix={0}'.format(prefix)] + self.configure_args()
+ options = getattr(self, 'configure_flag_args', [])
+ options += ['--prefix={0}'.format(prefix)]
+ options += self.configure_args()
with working_dir(self.build_directory, create=True):
inspect.getmodule(self).configure(*options)
diff --git a/lib/spack/spack/build_systems/cmake.py b/lib/spack/spack/build_systems/cmake.py
index 4f8162e655..e3acb2e845 100644
--- a/lib/spack/spack/build_systems/cmake.py
+++ b/lib/spack/spack/build_systems/cmake.py
@@ -109,7 +109,9 @@ class CMakePackage(PackageBase):
:return: standard cmake arguments
"""
# standard CMake arguments
- return CMakePackage._std_args(self)
+ std_cmake_args = CMakePackage._std_args(self)
+ std_cmake_args += getattr(self, 'cmake_flag_args', [])
+ return std_cmake_args
@staticmethod
def _std_args(pkg):
@@ -157,6 +159,44 @@ class CMakePackage(PackageBase):
args.append('-DCMAKE_PREFIX_PATH:STRING={0}'.format(';'.join(deps)))
return args
+ def flags_to_build_system_args(self, flags):
+ """Produces a list of all command line arguments to pass the specified
+ compiler flags to cmake. Note CMAKE does not have a cppflags option,
+ so cppflags will be added to cflags, cxxflags, and fflags to mimic the
+ behavior in other tools."""
+ # Has to be dynamic attribute due to caching
+ setattr(self, 'cmake_flag_args', [])
+
+ flag_string = '-DCMAKE_{0}_FLAGS={1}'
+ langs = {'C': 'c', 'CXX': 'cxx', 'Fortran': 'f'}
+
+ # Handle language compiler flags
+ for lang, pre in langs.items():
+ flag = pre + 'flags'
+ # cmake has no explicit cppflags support -> add it to all langs
+ lang_flags = ' '.join(flags.get(flag, []) + flags.get('cppflags',
+ []))
+ if lang_flags:
+ self.cmake_flag_args.append(flag_string.format(lang,
+ lang_flags))
+
+ # Cmake has different linker arguments for different build types.
+ # We specify for each of them.
+ if flags['ldflags']:
+ ldflags = ' '.join(flags['ldflags'])
+ ld_string = '-DCMAKE_{0}_LINKER_FLAGS={1}'
+ # cmake has separate linker arguments for types of builds.
+ for type in ['EXE', 'MODULE', 'SHARED', 'STATIC']:
+ self.cmake_flag_args.append(ld_string.format(type, ldflags))
+
+ # CMake has libs options separated by language. Apply ours to each.
+ if flags['ldlibs']:
+ libs_flags = ' '.join(flags['ldlibs'])
+ libs_string = '-DCMAKE_{0}_STANDARD_LIBRARIES={1}'
+ for lang in langs:
+ self.cmake_flag_args.append(libs_string.format(lang,
+ libs_flags))
+
@property
def build_directory(self):
"""Returns the directory to use when building the package
@@ -165,15 +205,6 @@ class CMakePackage(PackageBase):
"""
return join_path(self.stage.source_path, 'spack-build')
- def default_flag_handler(self, spack_env, flag_val):
- # Relies on being the first thing that can affect the spack_env
- # EnvironmentModification after it is instantiated or no other
- # method trying to affect these variables. Currently both are true
- # flag_val is a tuple (flag, value_list)
- spack_env.set(flag_val[0].upper(),
- ' '.join(flag_val[1]))
- return []
-
def cmake_args(self):
"""Produces a list containing all the arguments that must be passed to
cmake, except:
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 969daa469c..f0f092015f 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -1713,6 +1713,47 @@ class PackageBase(with_metaclass(PackageMeta, object)):
"""
pass
+ def inject_flags(self, name, flags):
+ """
+ flag_handler that injects all flags through the compiler wrapper.
+ """
+ return (flags, None, None)
+
+ def env_flags(self, name, flags):
+ """
+ flag_handler that adds all flags to canonical environment variables.
+ """
+ return (None, flags, None)
+
+ def build_system_flags(self, name, flags):
+ """
+ flag_handler that passes flags to the build system arguments. Any
+ package using `build_system_flags` must also implement
+ `flags_to_build_system_args`, or derive from a class that
+ implements it. Currently, AutotoolsPackage and CMakePackage
+ implement it.
+ """
+ return (None, None, flags)
+
+ flag_handler = inject_flags
+ # The flag handler method is called for each of the allowed compiler flags.
+ # It returns a triple of inject_flags, env_flags, build_system_flags.
+ # The flags returned as inject_flags are injected through the spack
+ # compiler wrappers.
+ # The flags returned as env_flags are passed to the build system through
+ # the environment variables of the same name.
+ # The flags returned as build_system_flags are passed to the build system
+ # package subclass to be turned into the appropriate part of the standard
+ # arguments. This is implemented for build system classes where
+ # appropriate and will otherwise raise a NotImplementedError.
+
+ def flags_to_build_system_args(self, flags):
+ # Takes flags as a dict name: list of values
+ if any(v for v in flags.values()):
+ msg = 'The {0} build system'.format(self.__class__.__name__)
+ msg += ' cannot take command line arguments for compiler flags'
+ raise NotImplementedError(msg)
+
@staticmethod
def uninstall_by_spec(spec, force=False):
if not os.path.isdir(spec.prefix):
diff --git a/lib/spack/spack/test/cc.py b/lib/spack/spack/test/cc.py
index a50af94ac2..671379c717 100644
--- a/lib/spack/spack/test/cc.py
+++ b/lib/spack/spack/test/cc.py
@@ -211,8 +211,12 @@ class CompilerWrapperTest(unittest.TestCase):
' '.join(test_command) + ' ' +
'-lfoo')
- os.environ['SPACK_LDFLAGS'] = ''
- os.environ['SPACK_LDLIBS'] = ''
+ del os.environ['SPACK_CFLAGS']
+ del os.environ['SPACK_CXXFLAGS']
+ del os.environ['SPACK_FFLAGS']
+ del os.environ['SPACK_CPPFLAGS']
+ del os.environ['SPACK_LDFLAGS']
+ del os.environ['SPACK_LDLIBS']
def test_dep_rpath(self):
"""Ensure RPATHs for root package are added."""
diff --git a/lib/spack/spack/test/flag_handlers.py b/lib/spack/spack/test/flag_handlers.py
new file mode 100644
index 0000000000..0b785a948f
--- /dev/null
+++ b/lib/spack/spack/test/flag_handlers.py
@@ -0,0 +1,176 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/spack/spack
+# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import pytest
+import os
+
+import spack.spec
+import spack.build_environment
+
+
+@pytest.fixture()
+def temp_env():
+ old_env = os.environ.copy()
+ yield
+ os.environ = old_env
+
+
+def add_O3_to_build_system_cflags(name, flags):
+ build_system_flags = []
+ if name == 'cflags':
+ build_system_flags.append('-O3')
+ return (flags, None, build_system_flags)
+
+
+@pytest.mark.usefixtures('config')
+class TestFlagHandlers(object):
+ def test_no_build_system_flags(self, temp_env):
+ # Test that both autotools and cmake work getting no build_system flags
+ s1 = spack.spec.Spec('callpath')
+ s1.concretize()
+ pkg1 = spack.repo.get(s1)
+ spack.build_environment.setup_package(pkg1, False)
+
+ s2 = spack.spec.Spec('libelf')
+ s2.concretize()
+ pkg2 = spack.repo.get(s2)
+ spack.build_environment.setup_package(pkg2, False)
+
+ # Use cppflags as a canary
+ assert 'SPACK_CPPFLAGS' not in os.environ
+ assert 'CPPFLAGS' not in os.environ
+
+ def test_inject_flags(self, temp_env):
+ s = spack.spec.Spec('mpileaks cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.inject_flags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert os.environ['SPACK_CPPFLAGS'] == '-g'
+ assert 'CPPFLAGS' not in os.environ
+
+ def test_env_flags(self, temp_env):
+ s = spack.spec.Spec('mpileaks cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.env_flags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert os.environ['CPPFLAGS'] == '-g'
+ assert 'SPACK_CPPFLAGS' not in os.environ
+
+ def test_build_system_flags_cmake(self, temp_env):
+ s = spack.spec.Spec('callpath cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.build_system_flags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert 'SPACK_CPPFLAGS' not in os.environ
+ assert 'CPPFLAGS' not in os.environ
+
+ expected = set(['-DCMAKE_C_FLAGS=-g', '-DCMAKE_CXX_FLAGS=-g',
+ '-DCMAKE_Fortran_FLAGS=-g'])
+ assert set(pkg.cmake_flag_args) == expected
+
+ def test_build_system_flags_autotools(self, temp_env):
+ s = spack.spec.Spec('libelf cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.build_system_flags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert 'SPACK_CPPFLAGS' not in os.environ
+ assert 'CPPFLAGS' not in os.environ
+
+ assert 'CPPFLAGS=-g' in pkg.configure_flag_args
+
+ def test_build_system_flags_not_implemented(self, temp_env):
+ s = spack.spec.Spec('mpileaks cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.build_system_flags
+
+ # Test the command line flags method raises a NotImplementedError
+ try:
+ spack.build_environment.setup_package(pkg, False)
+ assert False
+ except NotImplementedError:
+ assert True
+
+ def test_add_build_system_flags_autotools(self, temp_env):
+ s = spack.spec.Spec('libelf cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = add_O3_to_build_system_cflags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert '-g' in os.environ['SPACK_CPPFLAGS']
+ assert 'CPPFLAGS' not in os.environ
+
+ assert pkg.configure_flag_args == ['CFLAGS=-O3']
+
+ def test_add_build_system_flags_cmake(self, temp_env):
+ s = spack.spec.Spec('callpath cppflags=-g')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = add_O3_to_build_system_cflags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert '-g' in os.environ['SPACK_CPPFLAGS']
+ assert 'CPPFLAGS' not in os.environ
+
+ assert pkg.cmake_flag_args == ['-DCMAKE_C_FLAGS=-O3']
+
+ def test_ld_flags_cmake(self, temp_env):
+ s = spack.spec.Spec('callpath ldflags=-mthreads')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.build_system_flags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert 'SPACK_LDFLAGS' not in os.environ
+ assert 'LDFLAGS' not in os.environ
+
+ expected = set(['-DCMAKE_EXE_LINKER_FLAGS=-mthreads',
+ '-DCMAKE_MODULE_LINKER_FLAGS=-mthreads',
+ '-DCMAKE_SHARED_LINKER_FLAGS=-mthreads',
+ '-DCMAKE_STATIC_LINKER_FLAGS=-mthreads'])
+ assert set(pkg.cmake_flag_args) == expected
+
+ def test_ld_libs_cmake(self, temp_env):
+ s = spack.spec.Spec('callpath ldlibs=-lfoo')
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.flag_handler = pkg.build_system_flags
+ spack.build_environment.setup_package(pkg, False)
+
+ assert 'SPACK_LDLIBS' not in os.environ
+ assert 'LDLIBS' not in os.environ
+
+ expected = set(['-DCMAKE_C_STANDARD_LIBRARIES=-lfoo',
+ '-DCMAKE_CXX_STANDARD_LIBRARIES=-lfoo',
+ '-DCMAKE_Fortran_STANDARD_LIBRARIES=-lfoo'])
+ assert set(pkg.cmake_flag_args) == expected