Compiler flag handlers (#6415)

This adds the ability for packages to apply compiler flags in one of
three ways: by injecting them into the compiler wrapper calls (the
default in this PR and previously the only automated choice);
exporting environment variable definitions for variables with
corresponding names (e.g. CPPFLAGS=...); providing them as arguments
to the build system (e.g. configure).

When applying compiler flags using build system arguments, a package
must implement the 'flags_to_build_system_args" function. This is
provided for CMake and autotools packages, so for packages which
subclass those build systems, they need only update their flag
handler method specify which compiler flags should be specified as
arguments to the build system.

Convenience methods are provided to specify that all flags be applied
in one of the 3 available ways, so a custom implementation is only
required if more than one method of applying compiler flags is
needed.

This also removes redundant build system definitions from tutorial
examples
This commit is contained in:
becker33 2017-12-20 15:40:38 -08:00 committed by scheibelp
parent ef2e51571d
commit 28d8784ab9
12 changed files with 376 additions and 1324 deletions

View File

@ -2536,98 +2536,104 @@ build system.
Compiler flags Compiler flags
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
Compiler flags set by the user through the Spec object can be passed to Compiler flags set by the user through the Spec object can be passed
the build in one of two ways. For packages inheriting from the to the build in one of three ways. By default, the build environment
``CmakePackage`` or ``AutotoolsPackage`` classes, the build environment injects these flags directly into the compiler commands using Spack's
passes those flags to the relevant environment variables (``CFLAGS``, compiler wrappers. In cases where the build system requires knowledge
``CXXFLAGS``, etc) that are respected by the build system. For all other of the compiler flags, they can be registered with the build system by
packages, the default behavior is to inject the flags directly into the alternatively passing them through environment variables or as build
compiler commands using Spack's compiler wrappers. 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:: .. warning::
The flag handling methods described in this section are in beta. Passing compiler flags using build system arguments is only
The exact semantics are liable to change to improve usability. 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 Individual packages may also define their own ``flag_handler``
handling. Packages can define a ``default_flag_handler`` method that methods. The ``flag_handler`` method takes the package instance
applies to all sets of flags handled by Spack, or may define (``self``), the name of the flag, and a list of the values of the
individual methods ``cflags_handler``, ``cxxflags_handler``, flag. It will be called on each of the six compiler flags supported in
etc. Spack will apply the individual method for a flag set if it Spack. It should return a triple of ``(injf, envf, bsf)`` where
exists, otherwise the ``default_flag_handler`` method if it exists, ``injf`` is a list of flags to inject via the Spack compiler wrappers,
and fall back on the default for that package class if neither exists. ``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 .. warning::
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.
There are three primary idioms that can be combined to create whatever Passing a non-empty list of flags to ``bsf`` for a build system
behavior the package requires. that does not support build system arguments will result in an
error.
1. The default behavior for packages inheriting from Here are the definitions of the three built-in flag handlers:
``AutotoolsPackage`` or ``CmakePackage``.
.. code-block:: python .. code-block:: python
def default_flag_handler(self, env, flag_val): def inject_flags(self, name, flags):
env.append_flags(flag_val[0].upper(), ' '.join(flag_val[1])) return (flags, None, None)
return []
2. The default behavior for other packages def env_flags(self, name, flags):
return (None, flags, None)
def build_system_flags(self, name, flags):
return (None, None, flags)
.. note::
Returning ``[]`` and ``None`` are equivalent in a ``flag_handler``
method.
Packages can override the default behavior either by specifying one of
the built-in flag handlers,
.. code-block:: python .. code-block:: python
def default_flag_handler(self, env, flag_val): flag_handler = <PackageClass>.env_flags
return flag_val[1]
where ``<PackageClass>`` can be any of the subclasses of PackageBase
discussed in :ref:`installation_procedure`,
3. Packages may have additional flags to add to the build. These flags or by implementing the flag_handler method. Suppose for a package
can be added to either idiom above. For example: ``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 .. code-block:: python
def default_flag_handler(self, env, flag_val): def flag_handler(self, name, flags):
flags = flag_val[1] if name in ['cflags', 'cxxflags', 'cppflags']:
flags.append('-flag') return (None, flags, None)
return flags elif name == 'ldlibs':
flags.append('-lbar')
or return (flags, None, None)
.. code-block:: python
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 []
Packages may also opt for methods that include aspects of any of the
idioms above. E.g.
.. 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
Because these methods can pass values through environment variables, Because these methods can pass values through environment variables,
it is important not to override these variables unnecessarily in other it is important not to override these variables unnecessarily
package methods. In the ``setup_environment`` and (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`` ``setup_dependent_environment`` methods, use the ``append_flags``
method of the ``EnvironmentModifications`` class to append values to a method of the ``EnvironmentModifications`` class to append values to a
list of flags whenever there is no good reason to override the list of flags whenever the flag handler is ``env_flags``. If the
existing value. In the ``install`` method and other methods that can package passes flags through the environment or the build system
operate on the build environment directly through the ``env`` manually (in the install method, for example), we recommend using the
variable, test for environment variable existance before overriding default flag handler, or removind manual references and implementing a
values to add compiler flags. 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 In rare circumstances such as compiling and running small unit tests, a
package developer may need to know what are the appropriate compiler package developer may need to know what are the appropriate compiler

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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. 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 :language: python
:emphasize-lines: 42,45,62 :emphasize-lines: 42,45,62
:lines: 40-95,259-267 :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: Take note of the following:
.. literalinclude:: tutorial/examples/Makefile/makefile_class.py .. literalinclude:: ../../../lib/spack/spack/build_systems/makefile.py
:language: python :language: python
:lines: 33-79,89-107 :lines: 33-79,89-107
:emphasize-lines: 48,54,61 :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: 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 :language: python
:lines: 37-92, 94-155, 174-211 :lines: 37-92, 94-155, 174-211
:emphasize-lines: 57,68,86,94,96,99,100,101,102,111,117,135,136 :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: We see the following:
.. literalinclude:: tutorial/examples/PyPackage/python_package_class.py .. literalinclude:: ../../../lib/spack/spack/build_systems/python.py
:language: python :language: python
:lines: 35, 161-364 :lines: 35, 161-364
:linenos: :linenos:

View File

@ -128,7 +128,6 @@ def __call__(self, *args, **kwargs):
def set_compiler_environment_variables(pkg, env): def set_compiler_environment_variables(pkg, env):
assert(pkg.spec.concrete) assert(pkg.spec.concrete)
compiler = pkg.compiler compiler = pkg.compiler
flags = pkg.spec.compiler_flags
# Set compiler variables used by CMake and autotools # Set compiler variables used by CMake and autotools
assert all(key in compiler.link_paths for key in ( 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_F77_RPATH_ARG', compiler.f77_rpath_arg)
env.set('SPACK_FC_RPATH_ARG', compiler.fc_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(): for flag in spack.spec.FlagMap.valid_compiler_flags():
# Concreteness guarantees key safety here # Concreteness guarantees key safety here
if flags[flag] != []: if inject_flags[flag]:
env.set('SPACK_' + flag.upper(), ' '.join(f for f in 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)) env.set('SPACK_COMPILER_SPEC', str(pkg.spec.compiler))
@ -559,19 +575,6 @@ def setup_package(pkg, dirty):
for s in pkg.spec.traverse(): for s in pkg.spec.traverse():
assert s.package.spec is s 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_compiler_environment_variables(pkg, spack_env)
set_build_environment_variables(pkg, spack_env, dirty) set_build_environment_variables(pkg, spack_env, dirty)
pkg.architecture.platform.setup_platform_environment(pkg, spack_env) pkg.architecture.platform.setup_platform_environment(pkg, spack_env)

View File

@ -182,15 +182,6 @@ def build_directory(self):
"""Override to provide another place to build the package""" """Override to provide another place to build the package"""
return self.configure_directory 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') @run_before('autoreconf')
def delete_configure_to_force_update(self): def delete_configure_to_force_update(self):
if self.force_autoreconf: if self.force_autoreconf:
@ -256,12 +247,24 @@ def configure_args(self):
""" """
return [] 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): def configure(self, spec, prefix):
"""Runs configure with the arguments specified in """Runs configure with the arguments specified in
:py:meth:`~.AutotoolsPackage.configure_args` :py:meth:`~.AutotoolsPackage.configure_args`
and an appropriately set prefix. 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): with working_dir(self.build_directory, create=True):
inspect.getmodule(self).configure(*options) inspect.getmodule(self).configure(*options)

View File

@ -109,7 +109,9 @@ def std_cmake_args(self):
:return: standard cmake arguments :return: standard cmake arguments
""" """
# 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 @staticmethod
def _std_args(pkg): def _std_args(pkg):
@ -157,6 +159,44 @@ def _std_args(pkg):
args.append('-DCMAKE_PREFIX_PATH:STRING={0}'.format(';'.join(deps))) args.append('-DCMAKE_PREFIX_PATH:STRING={0}'.format(';'.join(deps)))
return args 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 @property
def build_directory(self): def build_directory(self):
"""Returns the directory to use when building the package """Returns the directory to use when building the package
@ -165,15 +205,6 @@ def build_directory(self):
""" """
return join_path(self.stage.source_path, 'spack-build') 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): def cmake_args(self):
"""Produces a list containing all the arguments that must be passed to """Produces a list containing all the arguments that must be passed to
cmake, except: cmake, except:

View File

@ -1713,6 +1713,47 @@ def setup_dependent_package(self, module, dependent_spec):
""" """
pass 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 @staticmethod
def uninstall_by_spec(spec, force=False): def uninstall_by_spec(spec, force=False):
if not os.path.isdir(spec.prefix): if not os.path.isdir(spec.prefix):

View File

@ -211,8 +211,12 @@ def test_flags(self):
' '.join(test_command) + ' ' + ' '.join(test_command) + ' ' +
'-lfoo') '-lfoo')
os.environ['SPACK_LDFLAGS'] = '' del os.environ['SPACK_CFLAGS']
os.environ['SPACK_LDLIBS'] = '' 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): def test_dep_rpath(self):
"""Ensure RPATHs for root package are added.""" """Ensure RPATHs for root package are added."""

View File

@ -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