diff options
155 files changed, 7465 insertions, 3573 deletions
diff --git a/lib/spack/docs/build_systems.rst b/lib/spack/docs/build_systems.rst index 12191d29f0..1ce8d6746e 100644 --- a/lib/spack/docs/build_systems.rst +++ b/lib/spack/docs/build_systems.rst @@ -65,7 +65,6 @@ on these ideas for each distinct build system that Spack supports: build_systems/custompackage build_systems/inteloneapipackage build_systems/intelpackage - build_systems/multiplepackage build_systems/rocmpackage build_systems/sourceforgepackage diff --git a/lib/spack/docs/build_systems/autotoolspackage.rst b/lib/spack/docs/build_systems/autotoolspackage.rst index d341d28d08..88fabc0c5d 100644 --- a/lib/spack/docs/build_systems/autotoolspackage.rst +++ b/lib/spack/docs/build_systems/autotoolspackage.rst @@ -5,9 +5,9 @@ .. _autotoolspackage: ----------------- -AutotoolsPackage ----------------- +--------- +Autotools +--------- Autotools is a GNU build system that provides a build-script generator. By running the platform-independent ``./configure`` script that comes @@ -17,7 +17,7 @@ with the package, you can generate a platform-dependent Makefile. Phases ^^^^^^ -The ``AutotoolsPackage`` base class comes with the following phases: +The ``AutotoolsBuilder`` and ``AutotoolsPackage`` base classes come with the following phases: #. ``autoreconf`` - generate the configure script #. ``configure`` - generate the Makefiles diff --git a/lib/spack/docs/build_systems/bundlepackage.rst b/lib/spack/docs/build_systems/bundlepackage.rst index 8787dce546..7a826f5e17 100644 --- a/lib/spack/docs/build_systems/bundlepackage.rst +++ b/lib/spack/docs/build_systems/bundlepackage.rst @@ -5,9 +5,9 @@ .. _bundlepackage: -------------- -BundlePackage -------------- +------ +Bundle +------ ``BundlePackage`` represents a set of packages that are expected to work well together, such as a collection of commonly used software libraries. The diff --git a/lib/spack/docs/build_systems/cmakepackage.rst b/lib/spack/docs/build_systems/cmakepackage.rst index 9544a7df73..7a1db842de 100644 --- a/lib/spack/docs/build_systems/cmakepackage.rst +++ b/lib/spack/docs/build_systems/cmakepackage.rst @@ -5,9 +5,9 @@ .. _cmakepackage: ------------- -CMakePackage ------------- +----- +CMake +----- Like Autotools, CMake is a widely-used build-script generator. Designed by Kitware, CMake is the most popular build system for new C, C++, and @@ -21,7 +21,7 @@ whereas Autotools is Unix-only. Phases ^^^^^^ -The ``CMakePackage`` base class comes with the following phases: +The ``CMakeBuilder`` and ``CMakePackage`` base classes come with the following phases: #. ``cmake`` - generate the Makefile #. ``build`` - build the package @@ -130,8 +130,8 @@ Adding flags to cmake To add additional flags to the ``cmake`` call, simply override the ``cmake_args`` function. The following example defines values for the flags ``WHATEVER``, ``ENABLE_BROKEN_FEATURE``, ``DETECT_HDF5``, and ``THREADS`` with -and without the :meth:`~spack.build_systems.cmake.CMakePackage.define` and -:meth:`~spack.build_systems.cmake.CMakePackage.define_from_variant` helper functions: +and without the :meth:`~spack.build_systems.cmake.CMakeBuilder.define` and +:meth:`~spack.build_systems.cmake.CMakeBuilder.define_from_variant` helper functions: .. code-block:: python diff --git a/lib/spack/docs/build_systems/luapackage.rst b/lib/spack/docs/build_systems/luapackage.rst index 6332edfc20..fd70f90c49 100644 --- a/lib/spack/docs/build_systems/luapackage.rst +++ b/lib/spack/docs/build_systems/luapackage.rst @@ -5,11 +5,11 @@ .. _luapackage: ------------- -LuaPackage ------------- +--- +Lua +--- -LuaPackage is a helper for the common case of Lua packages that provide +The ``Lua`` build-system is a helper for the common case of Lua packages that provide a rockspec file. This is not meant to take a rock archive, but to build a source archive or repository that provides a rockspec, which should cover most lua packages. In the case a Lua package builds by Make rather than @@ -19,7 +19,7 @@ luarocks, prefer MakefilePackage. Phases ^^^^^^ -The ``LuaPackage`` base class comes with the following phases: +The ``LuaBuilder`` and `LuaPackage`` base classes come with the following phases: #. ``unpack`` - if using a rock, unpacks the rock and moves into the source directory #. ``preprocess`` - adjust sources or rockspec to fix build diff --git a/lib/spack/docs/build_systems/makefilepackage.rst b/lib/spack/docs/build_systems/makefilepackage.rst index c092432037..5a83d612fa 100644 --- a/lib/spack/docs/build_systems/makefilepackage.rst +++ b/lib/spack/docs/build_systems/makefilepackage.rst @@ -5,9 +5,9 @@ .. _makefilepackage: ---------------- -MakefilePackage ---------------- +-------- +Makefile +-------- The most primitive build system a package can use is a plain Makefile. Makefiles are simple to write for small projects, but they usually @@ -18,7 +18,7 @@ variables. Phases ^^^^^^ -The ``MakefilePackage`` base class comes with 3 phases: +The ``MakefileBuilder`` and ``MakefilePackage`` base classes come with 3 phases: #. ``edit`` - edit the Makefile #. ``build`` - build the project diff --git a/lib/spack/docs/build_systems/mavenpackage.rst b/lib/spack/docs/build_systems/mavenpackage.rst index 94ce128d3a..d1237ce34c 100644 --- a/lib/spack/docs/build_systems/mavenpackage.rst +++ b/lib/spack/docs/build_systems/mavenpackage.rst @@ -5,9 +5,9 @@ .. _mavenpackage: ------------- -MavenPackage ------------- +----- +Maven +----- Apache Maven is a general-purpose build system that does not rely on Makefiles to build software. It is designed for building and @@ -17,7 +17,7 @@ managing and Java-based project. Phases ^^^^^^ -The ``MavenPackage`` base class comes with the following phases: +The ``MavenBuilder`` and ``MavenPackage`` base classes come with the following phases: #. ``build`` - compile code and package into a JAR file #. ``install`` - copy to installation prefix diff --git a/lib/spack/docs/build_systems/mesonpackage.rst b/lib/spack/docs/build_systems/mesonpackage.rst index 5ca444dcb1..c32b2241bc 100644 --- a/lib/spack/docs/build_systems/mesonpackage.rst +++ b/lib/spack/docs/build_systems/mesonpackage.rst @@ -5,9 +5,9 @@ .. _mesonpackage: ------------- -MesonPackage ------------- +----- +Meson +----- Much like Autotools and CMake, Meson is a build system. But it is meant to be both fast and as user friendly as possible. GNOME's goal @@ -17,7 +17,7 @@ is to port modules to use the Meson build system. Phases ^^^^^^ -The ``MesonPackage`` base class comes with the following phases: +The ``MesonBuilder`` and ``MesonPackage`` base classes come with the following phases: #. ``meson`` - generate ninja files #. ``build`` - build the project diff --git a/lib/spack/docs/build_systems/multiplepackage.rst b/lib/spack/docs/build_systems/multiplepackage.rst deleted file mode 100644 index 71751f0dbf..0000000000 --- a/lib/spack/docs/build_systems/multiplepackage.rst +++ /dev/null @@ -1,350 +0,0 @@ -.. Copyright 2013-2022 Lawrence Livermore National Security, LLC and other - Spack Project Developers. See the top-level COPYRIGHT file for details. - - SPDX-License-Identifier: (Apache-2.0 OR MIT) - -.. _multiplepackage: - ----------------------- -Multiple Build Systems ----------------------- - -Quite frequently, a package will change build systems from one version to the -next. For example, a small project that once used a single Makefile to build -may now require Autotools to handle the increased number of files that need to -be compiled. Or, a package that once used Autotools may switch to CMake for -Windows support. In this case, it becomes a bit more challenging to write a -single build recipe for this package in Spack. - -There are several ways that this can be handled in Spack: - -#. Subclass the new build system, and override phases as needed (preferred) -#. Subclass ``Package`` and implement ``install`` as needed -#. Create separate ``*-cmake``, ``*-autotools``, etc. packages for each build system -#. Rename the old package to ``*-legacy`` and create a new package -#. Move the old package to a ``legacy`` repository and create a new package -#. Drop older versions that only support the older build system - -Of these options, 1 is preferred, and will be demonstrated in this -documentation. Options 3-5 have issues with concretization, so shouldn't be -used. Options 4-5 also don't support more than two build systems. Option 6 only -works if the old versions are no longer needed. Option 1 is preferred over 2 -because it makes it easier to drop the old build system entirely. - -The exact syntax of the package depends on which build systems you need to -support. Below are a couple of common examples. - -^^^^^^^^^^^^^^^^^^^^^ -Makefile -> Autotools -^^^^^^^^^^^^^^^^^^^^^ - -Let's say we have the following package: - -.. code-block:: python - - class Foo(MakefilePackage): - version("1.2.0", sha256="...") - - def edit(self, spec, prefix): - filter_file("CC=", "CC=" + spack_cc, "Makefile") - - def install(self, spec, prefix): - install_tree(".", prefix) - - -The package subclasses from :ref:`makefilepackage`, which has three phases: - -#. ``edit`` (does nothing by default) -#. ``build`` (runs ``make`` by default) -#. ``install`` (runs ``make install`` by default) - -In this case, the ``install`` phase needed to be overridden because the -Makefile did not have an install target. We also modify the Makefile to use -Spack's compiler wrappers. The default ``build`` phase is not changed. - -Starting with version 1.3.0, we want to use Autotools to build instead. -:ref:`autotoolspackage` has four phases: - -#. ``autoreconf`` (does not if a configure script already exists) -#. ``configure`` (runs ``./configure --prefix=...`` by default) -#. ``build`` (runs ``make`` by default) -#. ``install`` (runs ``make install`` by default) - -If the only version we need to support is 1.3.0, the package would look as -simple as: - -.. code-block:: python - - class Foo(AutotoolsPackage): - version("1.3.0", sha256="...") - - def configure_args(self): - return ["--enable-shared"] - - -In this case, we use the default methods for each phase and only override -``configure_args`` to specify additional flags to pass to ``./configure``. - -If we wanted to write a single package that supports both versions 1.2.0 and -1.3.0, it would look something like: - -.. code-block:: python - - class Foo(AutotoolsPackage): - version("1.3.0", sha256="...") - version("1.2.0", sha256="...", deprecated=True) - - def configure_args(self): - return ["--enable-shared"] - - # Remove the following once version 1.2.0 is dropped - @when("@:1.2") - def patch(self): - filter_file("CC=", "CC=" + spack_cc, "Makefile") - - @when("@:1.2") - def autoreconf(self, spec, prefix): - pass - - @when("@:1.2") - def configure(self, spec, prefix): - pass - - @when("@:1.2") - def install(self, spec, prefix): - install_tree(".", prefix) - - -There are a few interesting things to note here: - -* We added ``deprecated=True`` to version 1.2.0. This signifies that version - 1.2.0 is deprecated and shouldn't be used. However, if a user still relies - on version 1.2.0, it's still there and builds just fine. -* We moved the contents of the ``edit`` phase to the ``patch`` function. Since - ``AutotoolsPackage`` doesn't have an ``edit`` phase, the only way for this - step to be executed is to move it to the ``patch`` function, which always - gets run. -* The ``autoreconf`` and ``configure`` phases become no-ops. Since the old - Makefile-based build system doesn't use these, we ignore these phases when - building ``foo@1.2.0``. -* The ``@when`` decorator is used to override these phases only for older - versions. The default methods are used for ``foo@1.3:``. - -Once a new Spack release comes out, version 1.2.0 and everything below the -comment can be safely deleted. The result is the same as if we had written a -package for version 1.3.0 from scratch. - -^^^^^^^^^^^^^^^^^^ -Autotools -> CMake -^^^^^^^^^^^^^^^^^^ - -Let's say we have the following package: - -.. code-block:: python - - class Bar(AutotoolsPackage): - version("1.2.0", sha256="...") - - def configure_args(self): - return ["--enable-shared"] - - -The package subclasses from :ref:`autotoolspackage`, which has four phases: - -#. ``autoreconf`` (does not if a configure script already exists) -#. ``configure`` (runs ``./configure --prefix=...`` by default) -#. ``build`` (runs ``make`` by default) -#. ``install`` (runs ``make install`` by default) - -In this case, we use the default methods for each phase and only override -``configure_args`` to specify additional flags to pass to ``./configure``. - -Starting with version 1.3.0, we want to use CMake to build instead. -:ref:`cmakepackage` has three phases: - -#. ``cmake`` (runs ``cmake ...`` by default) -#. ``build`` (runs ``make`` by default) -#. ``install`` (runs ``make install`` by default) - -If the only version we need to support is 1.3.0, the package would look as -simple as: - -.. code-block:: python - - class Bar(CMakePackage): - version("1.3.0", sha256="...") - - def cmake_args(self): - return [self.define("BUILD_SHARED_LIBS", True)] - - -In this case, we use the default methods for each phase and only override -``cmake_args`` to specify additional flags to pass to ``cmake``. - -If we wanted to write a single package that supports both versions 1.2.0 and -1.3.0, it would look something like: - -.. code-block:: python - - class Bar(CMakePackage): - version("1.3.0", sha256="...") - version("1.2.0", sha256="...", deprecated=True) - - def cmake_args(self): - return [self.define("BUILD_SHARED_LIBS", True)] - - # Remove the following once version 1.2.0 is dropped - def configure_args(self): - return ["--enable-shared"] - - @when("@:1.2") - def cmake(self, spec, prefix): - configure("--prefix=" + prefix, *self.configure_args()) - - -There are a few interesting things to note here: - -* We added ``deprecated=True`` to version 1.2.0. This signifies that version - 1.2.0 is deprecated and shouldn't be used. However, if a user still relies - on version 1.2.0, it's still there and builds just fine. -* Since CMake and Autotools are so similar, we only need to override the - ``cmake`` phase, we can use the default ``build`` and ``install`` phases. -* We override ``cmake`` to run ``./configure`` for older versions. - ``configure_args`` remains the same. -* The ``@when`` decorator is used to override these phases only for older - versions. The default methods are used for ``bar@1.3:``. - -Once a new Spack release comes out, version 1.2.0 and everything below the -comment can be safely deleted. The result is the same as if we had written a -package for version 1.3.0 from scratch. - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Multiple build systems for the same version -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -During the transition from one build system to another, developers often -support multiple build systems at the same time. Spack can only use a single -build system for a single version. To decide which build system to use for a -particular version, take the following things into account: - -1. If the developers explicitly state that one build system is preferred over - another, use that one. -2. If one build system is considered "experimental" while another is considered - "stable", use the stable build system. -3. Otherwise, use the newer build system. - -The developer preference for which build system to use can change over time as -a newer build system becomes stable/recommended. - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Dropping support for old build systems -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When older versions of a package don't support a newer build system, it can be -tempting to simply delete them from a package. This significantly reduces -package complexity and makes the build recipe much easier to maintain. However, -other packages or Spack users may rely on these older versions. The recommended -approach is to first support both build systems (as demonstrated above), -:ref:`deprecate <deprecate>` versions that rely on the old build system, and -remove those versions and any phases that needed to be overridden in the next -Spack release. - -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Three or more build systems -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In rare cases, a package may change build systems multiple times. For example, -a package may start with Makefiles, then switch to Autotools, then switch to -CMake. The same logic used above can be extended to any number of build systems. -For example: - -.. code-block:: python - - class Baz(CMakePackage): - version("1.4.0", sha256="...") # CMake - version("1.3.0", sha256="...") # Autotools - version("1.2.0", sha256="...") # Makefile - - def cmake_args(self): - return [self.define("BUILD_SHARED_LIBS", True)] - - # Remove the following once version 1.3.0 is dropped - def configure_args(self): - return ["--enable-shared"] - - @when("@1.3") - def cmake(self, spec, prefix): - configure("--prefix=" + prefix, *self.configure_args()) - - # Remove the following once version 1.2.0 is dropped - @when("@:1.2") - def patch(self): - filter_file("CC=", "CC=" + spack_cc, "Makefile") - - @when("@:1.2") - def cmake(self, spec, prefix): - pass - - @when("@:1.2") - def install(self, spec, prefix): - install_tree(".", prefix) - - -^^^^^^^^^^^^^^^^^^^ -Additional examples -^^^^^^^^^^^^^^^^^^^ - -When writing new packages, it often helps to see examples of existing packages. -Here is an incomplete list of existing Spack packages that have changed build -systems before: - -================ ===================== ================ -Package Previous Build System New Build System -================ ===================== ================ -amber custom CMake -arpack-ng Autotools CMake -atk Autotools Meson -blast None Autotools -dyninst Autotools CMake -evtgen Autotools CMake -fish Autotools CMake -gdk-pixbuf Autotools Meson -glib Autotools Meson -glog Autotools CMake -gmt Autotools CMake -gtkplus Autotools Meson -hpl Makefile Autotools -interproscan Perl Maven -jasper Autotools CMake -kahip SCons CMake -kokkos Makefile CMake -kokkos-kernels Makefile CMake -leveldb Makefile CMake -libdrm Autotools Meson -libjpeg-turbo Autotools CMake -mesa Autotools Meson -metis None CMake -mpifileutils Autotools CMake -muparser Autotools CMake -mxnet Makefile CMake -nest Autotools CMake -neuron Autotools CMake -nsimd CMake nsconfig -opennurbs Makefile CMake -optional-lite None CMake -plasma Makefile CMake -preseq Makefile Autotools -protobuf Autotools CMake -py-pygobject Autotools Python -singularity Autotools Makefile -span-lite None CMake -ssht Makefile CMake -string-view-lite None CMake -superlu Makefile CMake -superlu-dist Makefile CMake -uncrustify Autotools CMake -================ ===================== ================ - -Packages that support multiple build systems can be a bit confusing to write. -Don't hesitate to open an issue or draft pull request and ask for advice from -other Spack developers! diff --git a/lib/spack/docs/build_systems/octavepackage.rst b/lib/spack/docs/build_systems/octavepackage.rst index 9a81671db6..32e8cb61b3 100644 --- a/lib/spack/docs/build_systems/octavepackage.rst +++ b/lib/spack/docs/build_systems/octavepackage.rst @@ -5,9 +5,9 @@ .. _octavepackage: -------------- -OctavePackage -------------- +------ +Octave +------ Octave has its own build system for installing packages. @@ -15,7 +15,7 @@ Octave has its own build system for installing packages. Phases ^^^^^^ -The ``OctavePackage`` base class has a single phase: +The ``OctaveBuilder`` and ``OctavePackage`` base classes have a single phase: #. ``install`` - install the package diff --git a/lib/spack/docs/build_systems/perlpackage.rst b/lib/spack/docs/build_systems/perlpackage.rst index be81ca6ce9..4e1f613c3b 100644 --- a/lib/spack/docs/build_systems/perlpackage.rst +++ b/lib/spack/docs/build_systems/perlpackage.rst @@ -5,9 +5,9 @@ .. _perlpackage: ------------ -PerlPackage ------------ +---- +Perl +---- Much like Octave, Perl has its own language-specific build system. @@ -16,7 +16,7 @@ build system. Phases ^^^^^^ -The ``PerlPackage`` base class comes with 3 phases that can be overridden: +The ``PerlBuilder`` and ``PerlPackage`` base classes come with 3 phases that can be overridden: #. ``configure`` - configure the package #. ``build`` - build the package diff --git a/lib/spack/docs/build_systems/qmakepackage.rst b/lib/spack/docs/build_systems/qmakepackage.rst index 6e8bcef7cc..215d59536e 100644 --- a/lib/spack/docs/build_systems/qmakepackage.rst +++ b/lib/spack/docs/build_systems/qmakepackage.rst @@ -5,9 +5,9 @@ .. _qmakepackage: ------------- -QMakePackage ------------- +----- +QMake +----- Much like Autotools and CMake, QMake is a build-script generator designed by the developers of Qt. In its simplest form, Spack's @@ -29,7 +29,7 @@ variables or edit ``*.pro`` files to get things working properly. Phases ^^^^^^ -The ``QMakePackage`` base class comes with the following phases: +The ``QMakeBuilder`` and ``QMakePackage`` base classes come with the following phases: #. ``qmake`` - generate Makefiles #. ``build`` - build the project diff --git a/lib/spack/docs/build_systems/racketpackage.rst b/lib/spack/docs/build_systems/racketpackage.rst index 8ba37ceeba..5e09ffca4a 100644 --- a/lib/spack/docs/build_systems/racketpackage.rst +++ b/lib/spack/docs/build_systems/racketpackage.rst @@ -5,9 +5,9 @@ .. _racketpackage: -------------- -RacketPackage -------------- +------ +Racket +------ Much like Python, Racket packages and modules have their own special build system. To learn more about the specifics of Racket package system, please refer to the @@ -17,7 +17,7 @@ To learn more about the specifics of Racket package system, please refer to the Phases ^^^^^^ -The ``RacketPackage`` base class provides an ``install`` phase that +The ``RacketBuilder`` and ``RacketPackage`` base classes provides an ``install`` phase that can be overridden, corresponding to the use of: .. code-block:: console diff --git a/lib/spack/docs/build_systems/rpackage.rst b/lib/spack/docs/build_systems/rpackage.rst index 671af779b1..ebf2270e8e 100644 --- a/lib/spack/docs/build_systems/rpackage.rst +++ b/lib/spack/docs/build_systems/rpackage.rst @@ -19,7 +19,7 @@ new Spack packages for. Phases ^^^^^^ -The ``RPackage`` base class has a single phase: +The ``RBuilder`` and ``RPackage`` base classes have a single phase: #. ``install`` - install the package diff --git a/lib/spack/docs/build_systems/rubypackage.rst b/lib/spack/docs/build_systems/rubypackage.rst index b64ac60b2f..5b6ec462a6 100644 --- a/lib/spack/docs/build_systems/rubypackage.rst +++ b/lib/spack/docs/build_systems/rubypackage.rst @@ -5,9 +5,9 @@ .. _rubypackage: ------------ -RubyPackage ------------ +---- +Ruby +---- Like Perl, Python, and R, Ruby has its own build system for installing Ruby gems. @@ -16,7 +16,7 @@ installing Ruby gems. Phases ^^^^^^ -The ``RubyPackage`` base class provides the following phases that +The ``RubyBuilder`` and ``RubyPackage`` base classes provide the following phases that can be overridden: #. ``build`` - build everything needed to install diff --git a/lib/spack/docs/build_systems/sconspackage.rst b/lib/spack/docs/build_systems/sconspackage.rst index cea0408651..aea5dacfa7 100644 --- a/lib/spack/docs/build_systems/sconspackage.rst +++ b/lib/spack/docs/build_systems/sconspackage.rst @@ -5,9 +5,9 @@ .. _sconspackage: ------------- -SConsPackage ------------- +----- +SCons +----- SCons is a general-purpose build system that does not rely on Makefiles to build software. SCons is written in Python, and handles @@ -42,7 +42,7 @@ As previously mentioned, SCons allows developers to add subcommands like $ scons install -To facilitate this, the ``SConsPackage`` base class provides the +To facilitate this, the ``SConsBuilder`` and ``SconsPackage`` base classes provide the following phases: #. ``build`` - build the package diff --git a/lib/spack/docs/build_systems/sippackage.rst b/lib/spack/docs/build_systems/sippackage.rst index 5235015a92..3e77968e80 100644 --- a/lib/spack/docs/build_systems/sippackage.rst +++ b/lib/spack/docs/build_systems/sippackage.rst @@ -5,9 +5,9 @@ .. _sippackage: ----------- -SIPPackage ----------- +--- +SIP +--- SIP is a tool that makes it very easy to create Python bindings for C and C++ libraries. It was originally developed to create PyQt, the Python bindings for @@ -22,7 +22,7 @@ provides support functions to the automatically generated code. Phases ^^^^^^ -The ``SIPPackage`` base class comes with the following phases: +The ``SIPBuilder`` and ``SIPPackage`` base classes come with the following phases: #. ``configure`` - configure the package #. ``build`` - build the package diff --git a/lib/spack/docs/build_systems/wafpackage.rst b/lib/spack/docs/build_systems/wafpackage.rst index 54fcba98d0..f91479ce43 100644 --- a/lib/spack/docs/build_systems/wafpackage.rst +++ b/lib/spack/docs/build_systems/wafpackage.rst @@ -5,9 +5,9 @@ .. _wafpackage: ----------- -WafPackage ----------- +--- +Waf +--- Like SCons, Waf is a general-purpose build system that does not rely on Makefiles to build software. @@ -16,7 +16,7 @@ on Makefiles to build software. Phases ^^^^^^ -The ``WafPackage`` base class comes with the following phases: +The ``WafBuilder`` and ``WafPackage`` base classes come with the following phases: #. ``configure`` - configure the project #. ``build`` - build the project diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py index 1bdce87238..4fc321c72d 100644 --- a/lib/spack/docs/conf.py +++ b/lib/spack/docs/conf.py @@ -209,6 +209,7 @@ nitpick_ignore = [ # Spack classes that are private and we don't want to expose ("py:class", "spack.provider_index._IndexBase"), ("py:class", "spack.repo._PrependFileLoader"), + ("py:class", "spack.build_systems._checks.BaseBuilder"), # Spack classes that intersphinx is unable to resolve ("py:class", "spack.version.VersionBase"), ] diff --git a/lib/spack/docs/developer_guide.rst b/lib/spack/docs/developer_guide.rst index c4d04cb485..6b67ef9f77 100644 --- a/lib/spack/docs/developer_guide.rst +++ b/lib/spack/docs/developer_guide.rst @@ -149,11 +149,9 @@ grouped by functionality. Package-related modules ^^^^^^^^^^^^^^^^^^^^^^^ -:mod:`spack.package` - Contains the :class:`~spack.package_base.Package` class, which - is the superclass for all packages in Spack. Methods on ``Package`` - implement all phases of the :ref:`package lifecycle - <package-lifecycle>` and manage the build process. +:mod:`spack.package_base` + Contains the :class:`~spack.package_base.PackageBase` class, which + is the superclass for all packages in Spack. :mod:`spack.util.naming` Contains functions for mapping between Spack package names, diff --git a/lib/spack/docs/features.rst b/lib/spack/docs/features.rst index 985da967fd..1682616adb 100644 --- a/lib/spack/docs/features.rst +++ b/lib/spack/docs/features.rst @@ -98,40 +98,42 @@ For example, this command: .. code-block:: console - $ spack create http://www.mr511.de/software/libelf-0.8.13.tar.gz + $ spack create https://ftp.osuosl.org/pub/blfs/conglomeration/libelf/libelf-0.8.13.tar.gz creates a simple python file: .. code-block:: python - from spack import * + from spack.package import * - class Libelf(Package): + class Libelf(AutotoolsPackage): """FIXME: Put a proper description of your package here.""" # FIXME: Add a proper url for your package's homepage here. - homepage = "http://www.example.com" - url = "http://www.mr511.de/software/libelf-0.8.13.tar.gz" + homepage = "https://www.example.com" + url = "https://ftp.osuosl.org/pub/blfs/conglomeration/libelf/libelf-0.8.13.tar.gz" - version('0.8.13', '4136d7b4c04df68b686570afa26988ac') + # FIXME: Add a list of GitHub accounts to + # notify when the package is updated. + # maintainers = ["github_user1", "github_user2"] - # FIXME: Add dependencies if required. - # depends_on('foo') + version("0.8.13", sha256="591a9b4ec81c1f2042a97aa60564e0cb79d041c52faa7416acb38bc95bd2c76d") - def install(self, spec, prefix): - # FIXME: Modify the configure line to suit your build system here. - configure('--prefix={0}'.format(prefix)) + # FIXME: Add dependencies if required. + # depends_on("foo") - # FIXME: Add logic to build and install here. - make() - make('install') + def configure_args(self): + # FIXME: Add arguments other than --prefix + # FIXME: If not needed delete this function + args = [] + return args It doesn't take much python coding to get from there to a working package: .. literalinclude:: _spack_root/var/spack/repos/builtin/packages/libelf/package.py - :lines: 6- + :lines: 5- Spack also provides wrapper functions around common commands like ``configure``, ``make``, and ``cmake`` to make writing packages diff --git a/lib/spack/docs/images/adapter.png b/lib/spack/docs/images/adapter.png Binary files differnew file mode 100644 index 0000000000..aa889c2c32 --- /dev/null +++ b/lib/spack/docs/images/adapter.png diff --git a/lib/spack/docs/images/builder_package_architecture.png b/lib/spack/docs/images/builder_package_architecture.png Binary files differnew file mode 100644 index 0000000000..0c79b1ee7f --- /dev/null +++ b/lib/spack/docs/images/builder_package_architecture.png diff --git a/lib/spack/docs/images/builder_phases.png b/lib/spack/docs/images/builder_phases.png Binary files differnew file mode 100644 index 0000000000..e8141651c4 --- /dev/null +++ b/lib/spack/docs/images/builder_phases.png diff --git a/lib/spack/docs/images/installation_pipeline.png b/lib/spack/docs/images/installation_pipeline.png Binary files differnew file mode 100644 index 0000000000..c1d1e18f1b --- /dev/null +++ b/lib/spack/docs/images/installation_pipeline.png diff --git a/lib/spack/docs/images/original_package_architecture.png b/lib/spack/docs/images/original_package_architecture.png Binary files differnew file mode 100644 index 0000000000..9fc21efcc1 --- /dev/null +++ b/lib/spack/docs/images/original_package_architecture.png diff --git a/lib/spack/docs/images/packaging.excalidrawlib b/lib/spack/docs/images/packaging.excalidrawlib new file mode 100644 index 0000000000..1449c48579 --- /dev/null +++ b/lib/spack/docs/images/packaging.excalidrawlib @@ -0,0 +1,3092 @@ +{ + "type": "excalidrawlib", + "version": 2, + "source": "https://excalidraw.com", + "libraryItems": [ + { + "status": "unpublished", + "elements": [ + { + "type": "rectangle", + "version": 601, + "versionNonce": 158569138, + "isDeleted": false, + "id": "8MYJkzMoNEhDhGH1FB83g", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 445.75, + "y": 129, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 736, + "height": 651, + "seed": 448140078, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627195460, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 195, + "versionNonce": 1239338030, + "isDeleted": false, + "id": "2CKbNSYnk0z80hSe6axnR", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 470.25, + "y": 164, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 495, + "height": 455, + "seed": 566918834, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "IU_VoaKHNHswI8HaxNWt5", + "type": "arrow" + } + ], + "updated": 1664627105795, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 403, + "versionNonce": 56919410, + "isDeleted": false, + "id": "XUzv2kfpdxMahaSVVS42X", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 509.25, + "y": 407.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 354909550, + "groupIds": [ + "LYqioPcAzrIgJBDV3IaDA", + "SsaCg2uTI9sJjhD323wkh" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "71z_J7hoepiXas8Fk5x0B", + "type": "arrow" + }, + { + "id": "IU_VoaKHNHswI8HaxNWt5", + "type": "arrow" + } + ], + "updated": 1664627099901, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 300, + "versionNonce": 925254318, + "isDeleted": false, + "id": "lkCxvsSEn-AuBHtfj1N0d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 547.25, + "y": 441, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 321, + "height": 45, + "seed": 1361827954, + "groupIds": [ + "LYqioPcAzrIgJBDV3IaDA", + "SsaCg2uTI9sJjhD323wkh" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627099902, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "AutotoolsPackage", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AutotoolsPackage" + }, + { + "type": "rectangle", + "version": 377, + "versionNonce": 1733756722, + "isDeleted": false, + "id": "aCDb2PgRdoFKA8e-GqQzR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 509.25, + "y": 200, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 175218606, + "groupIds": [ + "WEeFev8dTdo9KgzR3hPki", + "SsaCg2uTI9sJjhD323wkh" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "71z_J7hoepiXas8Fk5x0B", + "type": "arrow" + } + ], + "updated": 1664627099902, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 161, + "versionNonce": 585481454, + "isDeleted": false, + "id": "fXYOlmw0CV0WFTNUDity0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 627.75, + "y": 233.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 160, + "height": 45, + "seed": 1186724402, + "groupIds": [ + "WEeFev8dTdo9KgzR3hPki", + "SsaCg2uTI9sJjhD323wkh" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627099902, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ArpackNg", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "ArpackNg" + }, + { + "type": "arrow", + "version": 290, + "versionNonce": 890458354, + "isDeleted": false, + "id": "71z_J7hoepiXas8Fk5x0B", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 707.8516807799414, + "y": 403, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 85, + "seed": 247298542, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627099902, + "link": null, + "locked": false, + "startBinding": { + "focus": 0.02318227093169459, + "gap": 3, + "elementId": "XUzv2kfpdxMahaSVVS42X" + }, + "endBinding": { + "focus": -0.02318227093169459, + "gap": 6, + "elementId": "aCDb2PgRdoFKA8e-GqQzR" + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -85 + ] + ] + }, + { + "type": "text", + "version": 673, + "versionNonce": 1429991214, + "isDeleted": false, + "id": "bsoYa0EVTdXYsTx5nsFJk", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 783.25, + "y": 518.3821170339361, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 164, + "height": 90, + "seed": 1633805298, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "IU_VoaKHNHswI8HaxNWt5", + "type": "arrow" + } + ], + "updated": 1664627099902, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Package \nHierarchy", + "baseline": 77, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Package \nHierarchy" + }, + { + "type": "rectangle", + "version": 903, + "versionNonce": 1712814318, + "isDeleted": false, + "id": "qRi5xNnAOqg-SFwtYBpoN", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 510.25, + "y": 657.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 1226050606, + "groupIds": [ + "-wCL8N0qNvseDw29hpA8g" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "IU_VoaKHNHswI8HaxNWt5", + "type": "arrow" + } + ], + "updated": 1664627118807, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 623, + "versionNonce": 492299954, + "isDeleted": false, + "id": "9h25d9NB-Q9Wc79boMEnC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 552.25, + "y": 691, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 313, + "height": 45, + "seed": 186946994, + "groupIds": [ + "-wCL8N0qNvseDw29hpA8g" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627118807, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Builder Forwarder", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Builder Forwarder" + }, + { + "type": "text", + "version": 1188, + "versionNonce": 351671150, + "isDeleted": false, + "id": "IlomIIocRvEmmYro4MZ68", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1002.75, + "y": 168.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 157, + "height": 90, + "seed": 1428885362, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627188273, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Package\n Wrapper", + "baseline": 77, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Package\n Wrapper" + }, + { + "type": "arrow", + "version": 832, + "versionNonce": 1121332014, + "isDeleted": false, + "id": "IU_VoaKHNHswI8HaxNWt5", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 707.7778281289579, + "y": 653.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 7.847537838213611, + "height": 130.23576593212783, + "seed": 1301783086, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664627118807, + "link": null, + "locked": false, + "startBinding": { + "elementId": "qRi5xNnAOqg-SFwtYBpoN", + "focus": 0.013062197564634722, + "gap": 4 + }, + "endBinding": { + "elementId": "XUzv2kfpdxMahaSVVS42X", + "focus": 0.056574233332975385, + "gap": 3.7642340678721666 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -7.847537838213611, + -130.23576593212783 + ] + ] + } + ], + "id": "mulubEO9Lw-HgC00sx7G-", + "created": 1664627205632 + }, + { + "status": "unpublished", + "elements": [ + { + "type": "rectangle", + "version": 360, + "versionNonce": 699609906, + "isDeleted": false, + "id": "ai3MIBTq8Rkokk4d2NJ_k", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 441.5, + "y": 56, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 479, + "height": 642, + "seed": 725687342, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926148, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 327, + "versionNonce": 1239118706, + "isDeleted": false, + "id": "7tuXfM91g28UGae9gJkis", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 993.25, + "y": 53, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 479, + "height": 642, + "seed": 860539570, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "F6E1EQxM-PyPeNjQXH6NZ", + "type": "arrow" + } + ], + "updated": 1664623054904, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 482, + "versionNonce": 616506034, + "isDeleted": false, + "id": "TmgDkNmbU86sH2Ssf1mL2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1030.75, + "y": 503.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 329380206, + "groupIds": [ + "rqi4zfKDNJjqgRyIIknBO" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "RQl1RtMzUcPE_zXHt8Ldm", + "type": "arrow" + }, + { + "id": "F6E1EQxM-PyPeNjQXH6NZ", + "type": "arrow" + }, + { + "id": "Iey2r9ev3NqXShFhDRa3t", + "type": "arrow" + } + ], + "updated": 1664623131360, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 377, + "versionNonce": 1649618094, + "isDeleted": false, + "id": "M6LF3AKrGIzDW8p00PLeg", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1068.75, + "y": 537, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 321, + "height": 45, + "seed": 1690477682, + "groupIds": [ + "rqi4zfKDNJjqgRyIIknBO" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926151, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "AutotoolsPackage", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AutotoolsPackage" + }, + { + "type": "rectangle", + "version": 466, + "versionNonce": 378147058, + "isDeleted": false, + "id": "-34MaUc1fQDbeqLTRUx91", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1030.625, + "y": 296, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 964531118, + "groupIds": [ + "TtAdfrQjw8FIlPZMGmWhX" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "RQl1RtMzUcPE_zXHt8Ldm", + "type": "arrow" + }, + { + "id": "7czUS_PAuM5hdRJoQRDRT", + "type": "arrow" + }, + { + "id": "Iey2r9ev3NqXShFhDRa3t", + "type": "arrow" + } + ], + "updated": 1664623131360, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 250, + "versionNonce": 1826973422, + "isDeleted": false, + "id": "85YHNomCStJoIV17Sp0A6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1093.625, + "y": 329.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 271, + "height": 45, + "seed": 1436108338, + "groupIds": [ + "TtAdfrQjw8FIlPZMGmWhX" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926151, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "builtin.ArpackNg", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "builtin.ArpackNg" + }, + { + "type": "arrow", + "version": 476, + "versionNonce": 1270564594, + "isDeleted": false, + "id": "RQl1RtMzUcPE_zXHt8Ldm", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1233.8516807799415, + "y": 499, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 85, + "seed": 1613426158, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926151, + "link": null, + "locked": false, + "startBinding": { + "elementId": "TmgDkNmbU86sH2Ssf1mL2", + "focus": 0.023182270931695163, + "gap": 4.5 + }, + "endBinding": { + "elementId": "-34MaUc1fQDbeqLTRUx91", + "focus": -0.02381199385360952, + "gap": 6 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -85 + ] + ] + }, + { + "type": "text", + "version": 693, + "versionNonce": 1438013742, + "isDeleted": false, + "id": "wSIdF9zegc69r2D38BVMs", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1061.75, + "y": 632.3821170339361, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 335, + "height": 45, + "seed": 1052094450, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926151, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Old-style packages", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Old-style packages" + }, + { + "type": "rectangle", + "version": 556, + "versionNonce": 1760787058, + "isDeleted": false, + "id": "lYxakYKLpAmo_DvzDJ27b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1030.625, + "y": 95, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 1302932978, + "groupIds": [ + "-WCCzMWoqGFfWxksMC6LG" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "RQl1RtMzUcPE_zXHt8Ldm", + "type": "arrow" + }, + { + "id": "8Z8HX6DlXqC-qL-63w1ol", + "type": "arrow" + }, + { + "id": "ia8wHuSmOVJLvGe5blR5g", + "type": "arrow" + }, + { + "id": "7czUS_PAuM5hdRJoQRDRT", + "type": "arrow" + } + ], + "updated": 1664623123836, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 341, + "versionNonce": 1412367214, + "isDeleted": false, + "id": "hF1874wuKYmbBjYAQwrVJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1088.125, + "y": 128.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 282, + "height": 45, + "seed": 524182062, + "groupIds": [ + "-WCCzMWoqGFfWxksMC6LG" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926152, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "myrepo.ArpackNg", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "myrepo.ArpackNg" + }, + { + "type": "arrow", + "version": 593, + "versionNonce": 214413938, + "isDeleted": false, + "id": "8Z8HX6DlXqC-qL-63w1ol", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1226.4453379157953, + "y": 297.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.434529927712447, + "height": 84, + "seed": 1326581486, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926152, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "lYxakYKLpAmo_DvzDJ27b", + "focus": -0.00782655608584947, + "gap": 6.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 2.434529927712447, + -84 + ] + ] + }, + { + "type": "rectangle", + "version": 733, + "versionNonce": 390297266, + "isDeleted": false, + "id": "G4--cV2YGQSrSijvYiNDB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 482.5, + "y": 507, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 85080878, + "groupIds": [ + "qZhg7KFANDHKWmTH71Lm0", + "FSKOW2oS76ubMa6DTOrDh" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "RQl1RtMzUcPE_zXHt8Ldm", + "type": "arrow" + }, + { + "id": "BkpnKUCjV1uqDGHPNuNZK", + "type": "arrow" + }, + { + "id": "aQdIO4VQx_J6SzCz-xt64", + "type": "arrow" + }, + { + "id": "F6E1EQxM-PyPeNjQXH6NZ", + "type": "arrow" + } + ], + "updated": 1664623061069, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 577, + "versionNonce": 2001681906, + "isDeleted": false, + "id": "MbNSUrN26Lx1aERuxunnt", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 541.5, + "y": 540.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 279, + "height": 45, + "seed": 950326962, + "groupIds": [ + "qZhg7KFANDHKWmTH71Lm0", + "FSKOW2oS76ubMa6DTOrDh" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926152, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Default Builder", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Default Builder" + }, + { + "type": "rectangle", + "version": 722, + "versionNonce": 1372930162, + "isDeleted": false, + "id": "WIS84sS48dCmi8q81Hh9F", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 482.5, + "y": 99, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 1977410350, + "groupIds": [ + "_CQwHz-xftDZzy8u9u4YO", + "FSKOW2oS76ubMa6DTOrDh" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "RQl1RtMzUcPE_zXHt8Ldm", + "type": "arrow" + }, + { + "id": "BkpnKUCjV1uqDGHPNuNZK", + "type": "arrow" + }, + { + "id": "aQdIO4VQx_J6SzCz-xt64", + "type": "arrow" + }, + { + "id": "ia8wHuSmOVJLvGe5blR5g", + "type": "arrow" + } + ], + "updated": 1664623105535, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 531, + "versionNonce": 1851174834, + "isDeleted": false, + "id": "qIbTXN1LbDYGZzSceYynz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 540.5, + "y": 132.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 281, + "height": 45, + "seed": 221818546, + "groupIds": [ + "_CQwHz-xftDZzy8u9u4YO", + "FSKOW2oS76ubMa6DTOrDh" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926152, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Adapter Builder", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Adapter Builder" + }, + { + "type": "arrow", + "version": 85, + "versionNonce": 50141422, + "isDeleted": false, + "id": "aQdIO4VQx_J6SzCz-xt64", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 670, + "y": 505, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 2, + "height": 291, + "seed": 417372974, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926152, + "link": null, + "locked": false, + "startBinding": { + "elementId": "G4--cV2YGQSrSijvYiNDB", + "focus": -0.05731267980406219, + "gap": 2 + }, + "endBinding": { + "elementId": "WIS84sS48dCmi8q81Hh9F", + "focus": 0.04321344955983103, + "gap": 3 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 2, + -291 + ] + ] + }, + { + "type": "arrow", + "version": 720, + "versionNonce": 1494556718, + "isDeleted": false, + "id": "ia8wHuSmOVJLvGe5blR5g", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.706831282597808, + "x": 932.4285606227004, + "y": 52.69401049592016, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 47.10077935537049, + "height": 145.9883132350331, + "seed": 314146734, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1664623039605, + "link": null, + "locked": false, + "startBinding": { + "elementId": "WIS84sS48dCmi8q81Hh9F", + "focus": 0.6597923311816741, + "gap": 2.0985583595166872 + }, + "endBinding": { + "elementId": "lYxakYKLpAmo_DvzDJ27b", + "focus": -0.6857137990945498, + "gap": 3.0336827015810286 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 45.89517648378751, + 72.7218231059162 + ], + [ + -1.2056028715829825, + 145.9883132350331 + ] + ] + }, + { + "type": "text", + "version": 727, + "versionNonce": 549636846, + "isDeleted": false, + "id": "JRrvIVZ9KAv56BYbRbCLA", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 527.5, + "y": 633.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 295, + "height": 45, + "seed": 2130028978, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664622926153, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Builder Hierarchy", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Builder Hierarchy" + }, + { + "type": "text", + "version": 281, + "versionNonce": 777063918, + "isDeleted": false, + "id": "BBj29IYUUwcEAk0aGGgEe", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 746, + "y": 2, + "strokeColor": "#c92a2a", + "backgroundColor": "#ced4da", + "width": 438, + "height": 35, + "seed": 344107566, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664623034966, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Defer to the old-style package", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Defer to the old-style package" + }, + { + "type": "arrow", + "version": 864, + "versionNonce": 353999662, + "isDeleted": false, + "id": "F6E1EQxM-PyPeNjQXH6NZ", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.5656833824867196, + "x": 932.5276780900645, + "y": 511.2079252998286, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 47.10077935537049, + "height": 145.9883132350331, + "seed": 2119154546, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1664623061069, + "link": null, + "locked": false, + "startBinding": { + "elementId": "TmgDkNmbU86sH2Ssf1mL2", + "focus": 0.700636908798286, + "gap": 3.7338363313426726 + }, + "endBinding": { + "elementId": "G4--cV2YGQSrSijvYiNDB", + "focus": -0.7137516210459195, + "gap": 1.5235945037890133 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 45.89517648378751, + 72.7218231059162 + ], + [ + -1.2056028715829825, + 145.9883132350331 + ] + ] + }, + { + "type": "text", + "version": 318, + "versionNonce": 1988243186, + "isDeleted": false, + "id": "VIOq-st9nvReenpiJkr7q", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 828, + "y": 724.5, + "strokeColor": "#c92a2a", + "backgroundColor": "#ced4da", + "width": 274, + "height": 70, + "seed": 2086072882, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664623095297, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Fall-back to the \nAdapter base class", + "baseline": 60, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Fall-back to the \nAdapter base class" + }, + { + "type": "arrow", + "version": 971, + "versionNonce": 1844256174, + "isDeleted": false, + "id": "7czUS_PAuM5hdRJoQRDRT", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 6.272294617229998, + "x": 1433.5276780900645, + "y": 163.20792529982862, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 47.10077935537049, + "height": 145.9883132350331, + "seed": 142056302, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1664623123836, + "link": null, + "locked": false, + "startBinding": { + "elementId": "lYxakYKLpAmo_DvzDJ27b", + "focus": -0.8331982906950285, + "gap": 5.098981289624589 + }, + "endBinding": { + "elementId": "-34MaUc1fQDbeqLTRUx91", + "focus": 0.7587321286266477, + "gap": 5.483331940596372 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 45.89517648378751, + 72.7218231059162 + ], + [ + -1.2056028715829825, + 145.9883132350331 + ] + ] + }, + { + "type": "arrow", + "version": 1075, + "versionNonce": 2073112366, + "isDeleted": false, + "id": "Iey2r9ev3NqXShFhDRa3t", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 6.272294617229998, + "x": 1434.451400933309, + "y": 387.7559332541056, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 47.10077935537049, + "height": 145.9883132350331, + "seed": 840513518, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1664623131360, + "link": null, + "locked": false, + "startBinding": { + "elementId": "-34MaUc1fQDbeqLTRUx91", + "focus": -0.7723329153292293, + "gap": 6.037577244264867 + }, + "endBinding": { + "elementId": "TmgDkNmbU86sH2Ssf1mL2", + "focus": 0.808011962769455, + "gap": 6.296927895236422 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 45.89517648378751, + 72.7218231059162 + ], + [ + -1.2056028715829825, + 145.9883132350331 + ] + ] + } + ], + "id": "sJP5ES4-kuhrqaBed7Feh", + "created": 1664623142493 + }, + { + "status": "unpublished", + "elements": [ + { + "type": "rectangle", + "version": 351, + "versionNonce": 94847218, + "isDeleted": false, + "id": "QfhQQY4Kvx8RLvCd6qXsx", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1011.5, + "y": 249, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 479, + "height": 438, + "seed": 1024685106, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347442, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 156, + "versionNonce": 2082406190, + "isDeleted": false, + "id": "rMQqqzkSZsBVWvOk137wO", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 511, + "y": 247, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 479, + "height": 438, + "seed": 250617778, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 392, + "versionNonce": 414601906, + "isDeleted": false, + "id": "h2lcAgJBn6WsPKAj3vWS8", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 545.5, + "y": 490.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 721668433, + "groupIds": [ + "ETPwHpdW1CXh0DtqZ_2na" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "r2Lq0kGXd6aTn5T-ki1aL", + "type": "arrow" + } + ], + "updated": 1664612347443, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 293, + "versionNonce": 848488814, + "isDeleted": false, + "id": "eaxk_MzyrjAjXKf0vmFuU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 583.5, + "y": 524, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 321, + "height": 45, + "seed": 1324675135, + "groupIds": [ + "ETPwHpdW1CXh0DtqZ_2na" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "AutotoolsPackage", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AutotoolsPackage" + }, + { + "type": "rectangle", + "version": 370, + "versionNonce": 595405938, + "isDeleted": false, + "id": "6TAhmS7GKN_ppUHjSVGLb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 545.5, + "y": 283, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 2083634783, + "groupIds": [ + "biKtN87UToAb_UBhyub5I" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "r2Lq0kGXd6aTn5T-ki1aL", + "type": "arrow" + } + ], + "updated": 1664612347443, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 155, + "versionNonce": 1066372014, + "isDeleted": false, + "id": "xyXchzGRLKRPuMVGo17mr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 664, + "y": 316.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 160, + "height": 45, + "seed": 2066951921, + "groupIds": [ + "biKtN87UToAb_UBhyub5I" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ArpackNg", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "ArpackNg" + }, + { + "type": "arrow", + "version": 285, + "versionNonce": 1807928882, + "isDeleted": false, + "id": "r2Lq0kGXd6aTn5T-ki1aL", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 748.6016807799414, + "y": 486, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 85, + "seed": 1479060383, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "startBinding": { + "elementId": "h2lcAgJBn6WsPKAj3vWS8", + "focus": 0.02318227093169459, + "gap": 3 + }, + "endBinding": { + "elementId": "6TAhmS7GKN_ppUHjSVGLb", + "focus": -0.02318227093169459, + "gap": 6 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -85 + ] + ] + }, + { + "type": "text", + "version": 572, + "versionNonce": 1094575598, + "isDeleted": false, + "id": "pUx1_v_UyKhu5zXISU4-f", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 653, + "y": 619.3821170339361, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 182, + "height": 45, + "seed": 1608256017, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Metadata", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Metadata" + }, + { + "type": "rectangle", + "version": 734, + "versionNonce": 1401317810, + "isDeleted": false, + "id": "4YBPHTc5sQiOKGM9NOZwg", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1045.5, + "y": 490.5, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 396.99999999999994, + "height": 112, + "seed": 1687989426, + "groupIds": [ + "lxE4hLtUAF2X7993lwk8q" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "M8cWqpsa0-iwN_cVJeXEQ", + "type": "arrow" + } + ], + "updated": 1664612347443, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 436, + "versionNonce": 1572061806, + "isDeleted": false, + "id": "P2U0ucf_QPvJcOWlMLp2K", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1183, + "y": 524, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 122, + "height": 45, + "seed": 276038958, + "groupIds": [ + "lxE4hLtUAF2X7993lwk8q" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Builder", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Builder" + }, + { + "type": "arrow", + "version": 489, + "versionNonce": 1663911086, + "isDeleted": false, + "id": "M8cWqpsa0-iwN_cVJeXEQ", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 942, + "y": 337, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 303, + "height": 143, + "seed": 1698960686, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "4YBPHTc5sQiOKGM9NOZwg", + "focus": 0.04820781382766574, + "gap": 10.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "dot", + "points": [ + [ + 0, + 0 + ], + [ + 295, + 0 + ], + [ + 303, + 143 + ] + ] + }, + { + "type": "text", + "version": 841, + "versionNonce": 2059173614, + "isDeleted": false, + "id": "QGyg9pXnTgByg9Lw9oZKC", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1043.5, + "y": 621.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 401, + "height": 45, + "seed": 1012078510, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664612347443, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Installation Procedure", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Installation Procedure" + } + ], + "id": "tezI4Q4gBH7mr-Q_us1KO", + "created": 1664612353293 + }, + { + "status": "unpublished", + "elements": [ + { + "type": "rectangle", + "version": 273, + "versionNonce": 1078330865, + "isDeleted": false, + "id": "h2lcAgJBn6WsPKAj3vWS8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 545.5, + "y": 489, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 396.99999999999994, + "height": 112, + "seed": 721668433, + "groupIds": [ + "ETPwHpdW1CXh0DtqZ_2na" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "r2Lq0kGXd6aTn5T-ki1aL", + "type": "arrow" + } + ], + "updated": 1664534889868, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 174, + "versionNonce": 1400524191, + "isDeleted": false, + "id": "eaxk_MzyrjAjXKf0vmFuU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 583.5, + "y": 522.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 321, + "height": 45, + "seed": 1324675135, + "groupIds": [ + "ETPwHpdW1CXh0DtqZ_2na" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664534889868, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "AutotoolsPackage", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AutotoolsPackage" + }, + { + "type": "text", + "version": 108, + "versionNonce": 438728849, + "isDeleted": false, + "id": "xyXchzGRLKRPuMVGo17mr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 664, + "y": 316.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160, + "height": 45, + "seed": 2066951921, + "groupIds": [ + "1wm7ikIN28k9zdVSKTLKQ" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664540120970, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ArpackNg", + "baseline": 32, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "ArpackNg" + }, + { + "type": "rectangle", + "version": 322, + "versionNonce": 1389146591, + "isDeleted": false, + "id": "6TAhmS7GKN_ppUHjSVGLb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 545.5, + "y": 283, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 396.99999999999994, + "height": 112, + "seed": 2083634783, + "groupIds": [ + "1wm7ikIN28k9zdVSKTLKQ" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "r2Lq0kGXd6aTn5T-ki1aL", + "type": "arrow" + } + ], + "updated": 1664534889868, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 94, + "versionNonce": 787416433, + "isDeleted": false, + "id": "r2Lq0kGXd6aTn5T-ki1aL", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 748.6016807799414, + "y": 486, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 85, + "seed": 1479060383, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664534889868, + "link": null, + "locked": false, + "startBinding": { + "elementId": "h2lcAgJBn6WsPKAj3vWS8", + "focus": 0.02318227093169459, + "gap": 3 + }, + "endBinding": { + "elementId": "6TAhmS7GKN_ppUHjSVGLb", + "focus": -0.02318227093169459, + "gap": 6 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -85 + ] + ] + }, + { + "type": "text", + "version": 227, + "versionNonce": 117980031, + "isDeleted": false, + "id": "pUx1_v_UyKhu5zXISU4-f", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 969, + "y": 386.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 442, + "height": 90, + "seed": 1608256017, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1664534908931, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Metadata \n+ Installation Procedure", + "baseline": 77, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Metadata \n+ Installation Procedure" + } + ], + "id": "_c7AOn60omrTlppZHlLQh", + "created": 1664540190548 + }, + { + "status": "unpublished", + "elements": [ + { + "type": "rectangle", + "version": 367, + "versionNonce": 963584621, + "isDeleted": false, + "id": "oAei2n-Ha1gpjnYdK7AwC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 240.5, + "y": 642.75, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 392, + "height": 80, + "seed": 701868237, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "slfbd0bbRqA8648kZ5fns", + "type": "text" + }, + { + "id": "slfbd0bbRqA8648kZ5fns", + "type": "text" + }, + { + "type": "text", + "id": "slfbd0bbRqA8648kZ5fns" + } + ], + "updated": 1663329462351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 373, + "versionNonce": 1698441027, + "isDeleted": false, + "id": "slfbd0bbRqA8648kZ5fns", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 245.5, + "y": 670.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 382, + "height": 25, + "seed": 1179637379, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663329462351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Execute the installation process", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oAei2n-Ha1gpjnYdK7AwC", + "originalText": "Execute the installation process" + }, + { + "type": "rectangle", + "version": 208, + "versionNonce": 844908259, + "isDeleted": false, + "id": "cLwg2WXUit_OTQmXLIdIW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 815.5, + "y": 517.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 557411811, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "SpG_8HxzMHjM2HYK6Fgwx", + "type": "arrow" + } + ], + "updated": 1663329462351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 274, + "versionNonce": 1704611021, + "isDeleted": false, + "id": "1r8FMl26VYSKpPKlHA_Oc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 916.5, + "y": 545, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 207, + "height": 25, + "seed": 961881101, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663329462351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "CMakeBuilder.cmake()", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CMakeBuilder.cmake()" + }, + { + "type": "rectangle", + "version": 264, + "versionNonce": 295137923, + "isDeleted": false, + "id": "CSwjuAw6Nl67sqQ6p21ty", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 815.5, + "y": 642.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 1011629069, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "SpG_8HxzMHjM2HYK6Fgwx", + "type": "arrow" + }, + { + "id": "zvmLoAH5oICRD5og-pBvu", + "type": "arrow" + } + ], + "updated": 1663329462351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 466, + "versionNonce": 196160301, + "isDeleted": false, + "id": "WX4axTU0IR7PJb0GkR-jq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 922.5, + "y": 670.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 193, + "height": 25, + "seed": 716117827, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663329462351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "CMakeBuilder.build()", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CMakeBuilder.build()" + }, + { + "type": "rectangle", + "version": 301, + "versionNonce": 1545420173, + "isDeleted": false, + "id": "coUXke3Fv_DpjqG9zgEjQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 815.5, + "y": 768, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 1934529891, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "yVIbU03yFYvpXnh9xIgET" + }, + { + "id": "zvmLoAH5oICRD5og-pBvu", + "type": "arrow" + } + ], + "updated": 1663329462351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 273, + "versionNonce": 1837690307, + "isDeleted": false, + "id": "yVIbU03yFYvpXnh9xIgET", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 820.5, + "y": 795.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 382, + "height": 25, + "seed": 1611291683, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663329462351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "CMakeBuilder.install()", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "coUXke3Fv_DpjqG9zgEjQ", + "originalText": "CMakeBuilder.install()" + }, + { + "type": "arrow", + "version": 564, + "versionNonce": 1041761261, + "isDeleted": false, + "id": "SpG_8HxzMHjM2HYK6Fgwx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1209, + "y": 558.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 96, + "height": 109, + "seed": 732445197, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1663329462351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "cLwg2WXUit_OTQmXLIdIW", + "focus": -0.7327371048252911, + "gap": 1.5 + }, + "endBinding": { + "elementId": "CSwjuAw6Nl67sqQ6p21ty", + "focus": 0.6494341563786008, + "gap": 2.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 96, + 54 + ], + [ + 1, + 109 + ] + ] + }, + { + "type": "arrow", + "version": 642, + "versionNonce": 1380728163, + "isDeleted": false, + "id": "zvmLoAH5oICRD5og-pBvu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1216, + "y": 680, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 98, + "height": 124.33745608356844, + "seed": 708861581, + "groupIds": [ + "D1SCf714tngJFHk8TFX8T" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1663329462351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "CSwjuAw6Nl67sqQ6p21ty", + "focus": -0.7839018302828619, + "gap": 8.5 + }, + "endBinding": { + "elementId": "coUXke3Fv_DpjqG9zgEjQ", + "focus": 0.7841576120638036, + "gap": 6.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 96, + 54 + ], + [ + -2, + 124.33745608356844 + ] + ] + }, + { + "type": "text", + "version": 613, + "versionNonce": 909390253, + "isDeleted": false, + "id": "fAHH1YdSlMq8ioLIj36Of", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 644, + "y": 353.7484662576685, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 166, + "height": 567.2515337423315, + "seed": 1455993539, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663329499644, + "link": null, + "locked": false, + "fontSize": 493.16790307261914, + "fontFamily": 2, + "text": "{", + "baseline": 454.2515337423315, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "{" + } + ], + "id": "KBV_I9pxrJD2zPuaP6vBc", + "created": 1663329511286 + }, + { + "status": "unpublished", + "elements": [ + { + "type": "rectangle", + "version": 93, + "versionNonce": 42296109, + "isDeleted": false, + "id": "cLwg2WXUit_OTQmXLIdIW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 625.5, + "y": 298, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 557411811, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "SpG_8HxzMHjM2HYK6Fgwx", + "type": "arrow" + } + ], + "updated": 1663324636434, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 99, + "versionNonce": 1537897869, + "isDeleted": false, + "id": "1r8FMl26VYSKpPKlHA_Oc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 726.5, + "y": 325.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 179, + "height": 25, + "seed": 961881101, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663324636434, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Fetch source files", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Fetch source files" + }, + { + "type": "rectangle", + "version": 149, + "versionNonce": 1653290435, + "isDeleted": false, + "id": "CSwjuAw6Nl67sqQ6p21ty", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 625.5, + "y": 423.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 1011629069, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "SpG_8HxzMHjM2HYK6Fgwx", + "type": "arrow" + }, + { + "id": "zvmLoAH5oICRD5og-pBvu", + "type": "arrow" + } + ], + "updated": 1663324636434, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 272, + "versionNonce": 1195260909, + "isDeleted": false, + "id": "WX4axTU0IR7PJb0GkR-jq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 645.5, + "y": 450.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 352, + "height": 25, + "seed": 716117827, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663324636434, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Expand them in the stage directory", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Expand them in the stage directory" + }, + { + "type": "rectangle", + "version": 185, + "versionNonce": 2143651171, + "isDeleted": false, + "id": "coUXke3Fv_DpjqG9zgEjQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 625.5, + "y": 548.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 1934529891, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "yVIbU03yFYvpXnh9xIgET" + }, + { + "id": "zvmLoAH5oICRD5og-pBvu", + "type": "arrow" + }, + { + "id": "5yqrFWV-hhJ4RoVewqAC0", + "type": "arrow" + } + ], + "updated": 1663324636434, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 135, + "versionNonce": 1833580109, + "isDeleted": false, + "id": "yVIbU03yFYvpXnh9xIgET", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 630.5, + "y": 563.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 382, + "height": 50, + "seed": 1611291683, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663324636434, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Set the stage directory as the \ncurrent working directory", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "coUXke3Fv_DpjqG9zgEjQ", + "originalText": "Set the stage directory as the current working directory" + }, + { + "type": "rectangle", + "version": 253, + "versionNonce": 1704770627, + "isDeleted": false, + "id": "tBTBRiEA6AJABK4wnKF_-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 625.5, + "y": 673.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 392, + "height": 80, + "seed": 1257829773, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "GoE9udjDQxUqdsYCUnbVI", + "type": "text" + }, + { + "type": "text", + "id": "GoE9udjDQxUqdsYCUnbVI" + }, + { + "id": "5yqrFWV-hhJ4RoVewqAC0", + "type": "arrow" + }, + { + "id": "v-9Voh5erXQ8iqoQ_9BVO", + "type": "arrow" + } + ], + "updated": 1663324636434, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 194, + "versionNonce": 1557028205, + "isDeleted": false, + "id": "GoE9udjDQxUqdsYCUnbVI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 630.5, + "y": 701.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 382, + "height": 25, + "seed": 895792579, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663324636434, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Fork a new build environment", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tBTBRiEA6AJABK4wnKF_-", + "originalText": "Fork a new build environment" + }, + { + "type": "rectangle", + "version": 321, + "versionNonce": 1675770851, + "isDeleted": false, + "id": "oAei2n-Ha1gpjnYdK7AwC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 625.5, + "y": 799, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 392, + "height": 80, + "seed": 701868237, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "slfbd0bbRqA8648kZ5fns", + "type": "text" + }, + { + "id": "slfbd0bbRqA8648kZ5fns", + "type": "text" + }, + { + "type": "text", + "id": "slfbd0bbRqA8648kZ5fns" + }, + { + "id": "v-9Voh5erXQ8iqoQ_9BVO", + "type": "arrow" + } + ], + "updated": 1663324636434, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 328, + "versionNonce": 1868179405, + "isDeleted": false, + "id": "slfbd0bbRqA8648kZ5fns", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 630.5, + "y": 826.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 382, + "height": 25, + "seed": 1179637379, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1663324636434, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Execute the installation process", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oAei2n-Ha1gpjnYdK7AwC", + "originalText": "Execute the installation process" + }, + { + "type": "arrow", + "version": 221, + "versionNonce": 1777917731, + "isDeleted": false, + "id": "SpG_8HxzMHjM2HYK6Fgwx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1019, + "y": 339, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 96, + "height": 109, + "seed": 732445197, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1663324636434, + "link": null, + "locked": false, + "startBinding": { + "elementId": "cLwg2WXUit_OTQmXLIdIW", + "focus": -0.7533277870216306, + "gap": 7 + }, + "endBinding": { + "elementId": "CSwjuAw6Nl67sqQ6p21ty", + "focus": 0.7554869684499315, + "gap": 6 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 96, + 54 + ], + [ + 1, + 109 + ] + ] + }, + { + "type": "arrow", + "version": 299, + "versionNonce": 309789379, + "isDeleted": false, + "id": "zvmLoAH5oICRD5og-pBvu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1026, + "y": 460.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 98, + "height": 124.33745608356844, + "seed": 708861581, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1663324636435, + "link": null, + "locked": false, + "startBinding": { + "elementId": "CSwjuAw6Nl67sqQ6p21ty", + "focus": -0.7021630615640598, + "gap": 12 + }, + "endBinding": { + "elementId": "coUXke3Fv_DpjqG9zgEjQ", + "focus": 0.8530521262002744, + "gap": 12 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 96, + 54 + ], + [ + -2, + 124.33745608356844 + ] + ] + }, + { + "type": "arrow", + "version": 301, + "versionNonce": 914472685, + "isDeleted": false, + "id": "5yqrFWV-hhJ4RoVewqAC0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1019, + "y": 586.6789496258876, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 99, + "height": 123.78306157234579, + "seed": 642378381, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1663324636435, + "link": null, + "locked": false, + "startBinding": { + "elementId": "coUXke3Fv_DpjqG9zgEjQ", + "focus": -0.6501663893510815, + "gap": 7 + }, + "endBinding": { + "elementId": "tBTBRiEA6AJABK4wnKF_-", + "focus": 0.8705418381344308, + "gap": 8 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 99, + 42.82105037411236 + ], + [ + 10.000000000000227, + 123.78306157234579 + ] + ] + }, + { + "type": "arrow", + "version": 351, + "versionNonce": 984592995, + "isDeleted": false, + "id": "v-9Voh5erXQ8iqoQ_9BVO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1031, + "y": 714.8662394200408, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 90, + "height": 137.7637151210173, + "seed": 698547757, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1663324636435, + "link": null, + "locked": false, + "startBinding": { + "elementId": "tBTBRiEA6AJABK4wnKF_-", + "focus": -0.6014975041597337, + "gap": 10 + }, + "endBinding": { + "elementId": "oAei2n-Ha1gpjnYdK7AwC", + "focus": 0.9573045267489712, + "gap": 12 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 90, + 33.633760579959244 + ], + [ + 4, + 137.7637151210173 + ] + ] + } + ], + "id": "RzNgncGu1938Ma5Teh6qZ", + "created": 1663324659550 + } + ] +}
\ No newline at end of file diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst index 556dde31e8..f7b8cfe35f 100644 --- a/lib/spack/docs/packaging_guide.rst +++ b/lib/spack/docs/packaging_guide.rst @@ -34,24 +34,155 @@ ubiquitous in the scientific software community. Second, it's a modern language and has many powerful features to help make package writing easy. ---------------------------- -Creating & editing packages ---------------------------- -.. _cmd-spack-create: +.. _installation_procedure: -^^^^^^^^^^^^^^^^ -``spack create`` -^^^^^^^^^^^^^^^^ +-------------------------------------- +Overview of the installation procedure +-------------------------------------- + +Whenever Spack installs software, it goes through a series of predefined steps: -The ``spack create`` command creates a directory with the package name and -generates a ``package.py`` file with a boilerplate package template. If given -a URL pointing to a tarball or other software archive, ``spack create`` is -smart enough to determine basic information about the package, including its name -and build system. In most cases, ``spack create`` plus a few modifications is -all you need to get a package working. +.. image:: images/installation_pipeline.png + :scale: 60 % + :align: center -Here's an example: +All these steps are influenced by the metadata in each ``package.py`` and +by the current Spack configuration. +Since build systems are different from one another, the execution of the +last block in the figure is further expanded in a build system specific way. +An example for ``CMake`` is, for instance: + +.. image:: images/builder_phases.png + :align: center + :scale: 60 % + +The predefined steps for each build system are called "phases". +In general, the name and order in which the phases will be executed can be +obtained by either reading the API docs at :py:mod:`~.spack.build_systems`, or +using the ``spack info`` command: + +.. code-block:: console + :emphasize-lines: 13,14 + + $ spack info --phases m4 + AutotoolsPackage: m4 + Homepage: https://www.gnu.org/software/m4/m4.html + + Safe versions: + 1.4.17 ftp://ftp.gnu.org/gnu/m4/m4-1.4.17.tar.gz + + Variants: + Name Default Description + + sigsegv on Build the libsigsegv dependency + + Installation Phases: + autoreconf configure build install + + Build Dependencies: + libsigsegv + + ... + +An extensive list of available build systems and phases is provided in :ref:`installation_process`. + + +------------------------ +Writing a package recipe +------------------------ + +Since v0.19, Spack supports two ways of writing a package recipe. The most commonly used is to encode both the metadata +(directives, etc.) and the build behavior in a single class, like shown in the following example: + +.. code-block:: python + + class Openjpeg(CMakePackage): + """OpenJPEG is an open-source JPEG 2000 codec written in C language""" + + homepage = "https://github.com/uclouvain/openjpeg" + url = "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz" + + version("2.4.0", sha256="8702ba68b442657f11aaeb2b338443ca8d5fb95b0d845757968a7be31ef7f16d") + + variant("codec", default=False, description="Build the CODEC executables") + depends_on("libpng", when="+codec") + + def url_for_version(self, version): + if version >= Version("2.1.1"): + return super(Openjpeg, self).url_for_version(version) + url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz" + return url_fmt.format(version) + + def cmake_args(self): + args = [ + self.define_from_variant("BUILD_CODEC", "codec"), + self.define("BUILD_MJ2", False), + self.define("BUILD_THIRDPARTY", False), + ] + return args + +A package encoded with a single class is backward compatible with versions of Spack +lower than v0.19, and so are custom repositories containing only recipes of this kind. +The downside is that *this format doesn't allow packagers to use more than one build system in a single recipe*. + +To do that, we have to resort to the second way Spack has of writing packages, which involves writing a +builder class explicitly. Using the same example as above, this reads: + +.. code-block:: python + + class Openjpeg(CMakePackage): + """OpenJPEG is an open-source JPEG 2000 codec written in C language""" + + homepage = "https://github.com/uclouvain/openjpeg" + url = "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz" + + version("2.4.0", sha256="8702ba68b442657f11aaeb2b338443ca8d5fb95b0d845757968a7be31ef7f16d") + + variant("codec", default=False, description="Build the CODEC executables") + depends_on("libpng", when="+codec") + + def url_for_version(self, version): + if version >= Version("2.1.1"): + return super(Openjpeg, self).url_for_version(version) + url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz" + return url_fmt.format(version) + + class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + args = [ + self.define_from_variant("BUILD_CODEC", "codec"), + self.define("BUILD_MJ2", False), + self.define("BUILD_THIRDPARTY", False), + ] + return args + +This way of writing packages allows extending the recipe to support multiple build systems, +see :ref:`multiple_build_systems` for more details. The downside is that recipes of this kind +are only understood by Spack since v0.19+. More information on the internal architecture of +Spack can be found at :ref:`package_class_structure`. + +.. note:: + + If a builder is implemented in ``package.py``, all build-specific methods must be moved + to the builder. This means that if you have a package like + + .. code-block:: python + + class Foo(CmakePackage): + def cmake_args(self): + ... + + and you add a builder to the ``package.py``, you must move ``cmake_args`` to the builder. + +.. _cmd-spack-create: + +--------------------- +Creating new packages +--------------------- + +To help creating a new package Spack provides a command that generates a ``package.py`` +file in an existing repository, with a boilerplate package template. Here's an example: .. code-block:: console @@ -87,23 +218,6 @@ You do not *have* to download all of the versions up front. You can always choose to download just one tarball initially, and run :ref:`cmd-spack-checksum` later if you need more versions. -Let's say you download 3 tarballs: - -.. code-block:: console - - How many would you like to checksum? (default is 1, q to abort) 3 - ==> Downloading... - ==> Fetching https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2 - ######################################################################## 100.0% - ==> Fetching https://gmplib.org/download/gmp/gmp-6.1.1.tar.bz2 - ######################################################################## 100.0% - ==> Fetching https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2 - ######################################################################## 100.0% - ==> Checksummed 3 versions of gmp: - ==> This package looks like it uses the autotools build system - ==> Created template for gmp package - ==> Created package file: /Users/Adam/spack/var/spack/repos/builtin/packages/gmp/package.py - Spack automatically creates a directory in the appropriate repository, generates a boilerplate template for your package, and opens up the new ``package.py`` in your favorite ``$EDITOR``: @@ -111,6 +225,14 @@ generates a boilerplate template for your package, and opens up the new .. code-block:: python :linenos: + # Copyright 2013-2022 Lawrence Livermore National Security, LLC and other + # Spack Project Developers. See the top-level COPYRIGHT file for details. + # + # SPDX-License-Identifier: (Apache-2.0 OR MIT) + + # ---------------------------------------------------------------------------- + # If you submit this package back to Spack as a pull request, + # please first remove this boilerplate and all FIXME comments. # # This is a template package file for Spack. We've put "FIXME" # next to all the things you'll want to change. Once you've handled @@ -123,9 +245,8 @@ generates a boilerplate template for your package, and opens up the new # spack edit gmp # # See the Spack documentation for more information on packaging. - # If you submit this package back to Spack as a pull request, - # please first remove this boilerplate and all FIXME comments. - # + # ---------------------------------------------------------------------------- + import spack.build_systems.autotools from spack.package import * @@ -133,19 +254,17 @@ generates a boilerplate template for your package, and opens up the new """FIXME: Put a proper description of your package here.""" # FIXME: Add a proper url for your package's homepage here. - homepage = "http://www.example.com" - url = "https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2" + homepage = "https://www.example.com" + url = "https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2" # FIXME: Add a list of GitHub accounts to # notify when the package is updated. - # maintainers = ['github_user1', 'github_user2'] + # maintainers = ["github_user1", "github_user2"] - version('6.1.2', '8ddbb26dc3bd4e2302984debba1406a5') - version('6.1.1', '4c175f86e11eb32d8bf9872ca3a8e11d') - version('6.1.0', '86ee6e54ebfc4a90b643a65e402c4048') + version("6.2.1", sha256="eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c") # FIXME: Add dependencies if required. - # depends_on('foo') + # depends_on("foo") def configure_args(self): # FIXME: Add arguments other than --prefix @@ -154,15 +273,16 @@ generates a boilerplate template for your package, and opens up the new return args The tedious stuff (creating the class, checksumming archives) has been -done for you. You'll notice that ``spack create`` correctly detected that -``gmp`` uses the Autotools build system. It created a new ``Gmp`` package -that subclasses the ``AutotoolsPackage`` base class. This base class -provides basic installation methods common to all Autotools packages: +done for you. Spack correctly detected that ``gmp`` uses the ``autotools`` +build system, so it created a new ``Gmp`` package that subclasses the +``AutotoolsPackage`` base class. + +The default installation procedure for a package subclassing the ``AutotoolsPackage`` +is to go through the typical process of: .. code-block:: bash ./configure --prefix=/path/to/installation/directory - make make check make install @@ -209,12 +329,14 @@ The rest of the tasks you need to do are as follows: Your new package may require specific flags during ``configure``. These can be added via ``configure_args``. Specifics will differ depending on the package and its build system. - :ref:`Implementing the install method <install-method>` is + :ref:`installation_process` is covered in detail later. -Passing a URL to ``spack create`` is a convenient and easy way to get -a basic package template, but what if your software is licensed and -cannot be downloaded from a URL? You can still create a boilerplate +^^^^^^^^^^^^^^^^^^^^^^^^^ +Non-downloadable software +^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your software cannot be downloaded from a URL you can still create a boilerplate ``package.py`` by telling ``spack create`` what name you want to use: .. code-block:: console @@ -223,40 +345,23 @@ cannot be downloaded from a URL? You can still create a boilerplate This will create a simple ``intel`` package with an ``install()`` method that you can craft to install your package. - -What if ``spack create <url>`` guessed the wrong name or build system? -For example, if your package uses the Autotools build system but does -not come with a ``configure`` script, Spack won't realize it uses -Autotools. You can overwrite the old package with ``--force`` and specify -a name with ``--name`` or a build system template to use with ``--template``: +Likewise, you can force the build system to be used with ``--template`` and, +in case it's needed, you can overwrite a package already in the repository +with ``--force``: .. code-block:: console $ spack create --name gmp https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2 $ spack create --force --template autotools https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2 -.. note:: - - If you are creating a package that uses the Autotools build system - but does not come with a ``configure`` script, you'll need to add an - ``autoreconf`` method to your package that explains how to generate - the ``configure`` script. You may also need the following dependencies: - - .. code-block:: python - - depends_on('autoconf', type='build') - depends_on('automake', type='build') - depends_on('libtool', type='build') - depends_on('m4', type='build') - A complete list of available build system templates can be found by running ``spack create --help``. .. _cmd-spack-edit: -^^^^^^^^^^^^^^ -``spack edit`` -^^^^^^^^^^^^^^ +------------------------- +Editing existing packages +------------------------- One of the easiest ways to learn how to write packages is to look at existing ones. You can edit a package file by name with the ``spack @@ -266,10 +371,15 @@ edit`` command: $ spack edit gmp -So, if you used ``spack create`` to create a package, then saved and -closed the resulting file, you can get back to it with ``spack edit``. -The ``gmp`` package actually lives in -``$SPACK_ROOT/var/spack/repos/builtin/packages/gmp/package.py``, +If you used ``spack create`` to create a package, you can get back to +it later with ``spack edit``. For instance, the ``gmp`` package actually +lives in: + +.. code-block:: console + + $ spack location -p gmp + ${SPACK_ROOT}/var/spack/repos/builtin/packages/gmp/package.py + but ``spack edit`` provides a much simpler shortcut and saves you the trouble of typing the full path. @@ -2422,7 +2532,7 @@ Spack provides a mechanism for dependencies to influence the environment of their dependents by overriding the :meth:`setup_dependent_run_environment <spack.package_base.PackageBase.setup_dependent_run_environment>` or the -:meth:`setup_dependent_build_environment <spack.package_base.PackageBase.setup_dependent_build_environment>` +:meth:`setup_dependent_build_environment <spack.builder.Builder.setup_dependent_build_environment>` methods. The Qt package, for instance, uses this call: @@ -3280,67 +3390,91 @@ the Python extensions provided by them: once for ``+python`` and once for ``~python``. Other than using a little extra disk space, that solution has no serious problems. -.. _installation_procedure: +.. _installation_process: + +-------------------------------- +Overriding build system defaults +-------------------------------- ---------------------------------------- -Implementing the installation procedure ---------------------------------------- +.. note:: -The last element of a package is its **installation procedure**. This is -where the real work of installation happens, and it's the main part of -the package you'll need to customize for each piece of software. + If you code a single class in ``package.py`` all the functions shown in the table below + can be implemented with the same signature on the ``*Package`` instead of the corresponding builder. -Defining an installation procedure means overriding a set of methods or attributes -that will be called at some point during the installation of the package. -The package base class, usually specialized for a given build system, determines the -actual set of entities available for overriding. -The classes that are currently provided by Spack are: + +Most of the time the default implementation of methods or attributes in build system base classes +is what a packager needs, and just a very few entities need to be overwritten. Typically we just +need to override methods like ``configure_args``: + +.. code-block:: python + + def configure_args(self): + args = ["--enable-cxx"] + self.enable_or_disable("libs") + if "libs=static" in self.spec: + args.append("--with-pic") + return args + +The actual set of entities available for overriding in ``package.py`` depend on +the build system. The build systems currently supported by Spack are: +----------------------------------------------------------+----------------------------------+ -| **Base Class** | **Purpose** | +| **API docs** | **Description** | +==========================================================+==================================+ -| :class:`~spack.package_base.Package` | General base class not | -| | specialized for any build system | +| :class:`~spack.build_systems.generic` | Generic build system without any | +| | base implementation | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.makefile.MakefilePackage` | Specialized class for packages | -| | built invoking | +| :class:`~spack.build_systems.makefile` | Specialized build system for | +| | software built invoking | | | hand-written Makefiles | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.autotools.AutotoolsPackage` | Specialized class for packages | -| | built using GNU Autotools | +| :class:`~spack.build_systems.autotools` | Specialized build system for | +| | software built using | +| | GNU Autotools | ++----------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.cmake` | Specialized build system for | +| | software built using CMake | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.cmake.CMakePackage` | Specialized class for packages | -| | built using CMake | +| :class:`~spack.build_systems.maven` | Specialized build system for | +| | software built using Maven | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.cuda.CudaPackage` | A helper class for packages that | -| | use CUDA | +| :class:`~spack.build_systems.meson` | Specialized build system for | +| | software built using Meson | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.qmake.QMakePackage` | Specialized class for packages | -| | built using QMake | +| :class:`~spack.build_systems.nmake` | Specialized build system for | +| | software built using NMake | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.rocm.ROCmPackage` | A helper class for packages that | -| | use ROCm | +| :class:`~spack.build_systems.qmake` | Specialized build system for | +| | software built using QMake | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.scons.SConsPackage` | Specialized class for packages | -| | built using SCons | +| :class:`~spack.build_systems.scons` | Specialized build system for | +| | software built using SCons | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.waf.WafPackage` | Specialized class for packages | -| | built using Waf | +| :class:`~spack.build_systems.waf` | Specialized build system for | +| | software built using Waf | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.r.RPackage` | Specialized class for | +| :class:`~spack.build_systems.r` | Specialized build system for | | | R extensions | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.octave.OctavePackage` | Specialized class for | +| :class:`~spack.build_systems.octave` | Specialized build system for | | | Octave packages | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.python.PythonPackage` | Specialized class for | +| :class:`~spack.build_systems.python` | Specialized build system for | | | Python extensions | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.perl.PerlPackage` | Specialized class for | +| :class:`~spack.build_systems.perl` | Specialized build system for | | | Perl extensions | +----------------------------------------------------------+----------------------------------+ -| :class:`~spack.build_systems.intel.IntelPackage` | Specialized class for licensed | -| | Intel software | +| :class:`~spack.build_systems.ruby` | Specialized build system for | +| | Ruby extensions | ++----------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.intel` | Specialized build system for | +| | licensed Intel software | ++----------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.oneapi` | Specialized build system for | +| | Intel onaAPI software | ++----------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.aspell_dict` | Specialized build system for | +| | Aspell dictionaries | +----------------------------------------------------------+----------------------------------+ @@ -3353,69 +3487,17 @@ The classes that are currently provided by Spack are: For example, a Python extension installed with CMake would ``extends('python')`` and subclass from :class:`~spack.build_systems.cmake.CMakePackage`. -^^^^^^^^^^^^^^^^^^^^^ -Installation pipeline -^^^^^^^^^^^^^^^^^^^^^ - -When a user runs ``spack install``, Spack: - -1. Fetches an archive for the correct version of the software. -2. Expands the archive. -3. Sets the current working directory to the root directory of the expanded archive. - -Then, depending on the base class of the package under consideration, it will execute -a certain number of **phases** that reflect the way a package of that type is usually built. -The name and order in which the phases will be executed can be obtained either reading the API -docs at :py:mod:`~.spack.build_systems`, or using the ``spack info`` command: - -.. code-block:: console - :emphasize-lines: 26-27 - - $ spack info --phases m4 - AutotoolsPackage: m4 - - Description: - GNU M4 is an implementation of the traditional Unix macro processor. - - Homepage: https://www.gnu.org/software/m4/m4.html - - Preferred version: - 1.4.19 https://ftpmirror.gnu.org/m4/m4-1.4.19.tar.gz - - Safe versions: - 1.4.19 https://ftpmirror.gnu.org/m4/m4-1.4.19.tar.gz - 1.4.18 https://ftpmirror.gnu.org/m4/m4-1.4.18.tar.gz - 1.4.17 https://ftpmirror.gnu.org/m4/m4-1.4.17.tar.gz - - Deprecated versions: - None - - Variants: - Name [Default] When Allowed values Description - ============== ==== ============== =============================== - - sigsegv [on] -- on, off Build the libsigsegv dependency - - Installation Phases: - autoreconf configure build install - - Build Dependencies: - diffutils gnuconfig libsigsegv - - Link Dependencies: - libsigsegv - - Run Dependencies: - None - +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Overriding builder methods +^^^^^^^^^^^^^^^^^^^^^^^^^^ -Typically, phases have default implementations that fit most of the common cases: +Build-system "phases" have default implementations that fit most of the common cases: .. literalinclude:: _spack_root/lib/spack/spack/build_systems/autotools.py - :pyobject: AutotoolsPackage.configure + :pyobject: AutotoolsBuilder.configure :linenos: -It is thus just sufficient for a packager to override a few +It is usually sufficient for a packager to override a few build system specific helper methods or attributes to provide, for instance, configure arguments: @@ -3423,31 +3505,31 @@ configure arguments: :pyobject: M4.configure_args :linenos: -.. note:: - Each specific build system has a list of attributes that can be overridden to - fine-tune the installation of a package without overriding an entire phase. To - have more information on them the place to go is the API docs of the :py:mod:`~.spack.build_systems` - module. +Each specific build system has a list of attributes and methods that can be overridden to +fine-tune the installation of a package without overriding an entire phase. To +have more information on them the place to go is the API docs of the :py:mod:`~.spack.build_systems` +module. ^^^^^^^^^^^^^^^^^^^^^^^^^^ Overriding an entire phase ^^^^^^^^^^^^^^^^^^^^^^^^^^ -In extreme cases it may be necessary to override an entire phase. Regardless -of the build system, the signature is the same. For example, the signature -for the install phase is: +Sometimes it is necessary to override an entire phase. If the ``package.py`` contains +a single class recipe, see :ref:`package_class_structure`, then the signature for a +phase is: .. code-block:: python - class Foo(Package): + class Openjpeg(CMakePackage): def install(self, spec, prefix): ... +regardless of the build system. The arguments for the phase are: + ``self`` - For those not used to Python instance methods, this is the - package itself. In this case it's an instance of ``Foo``, which - extends ``Package``. For API docs on Package objects, see - :py:class:`Package <spack.package_base.Package>`. + This is the package object, which extends ``CMakePackage``. + For API docs on Package objects, see + :py:class:`Package <spack.package_base.PackageBase>`. ``spec`` This is the concrete spec object created by Spack from an @@ -3462,12 +3544,111 @@ for the install phase is: The arguments ``spec`` and ``prefix`` are passed only for convenience, as they always correspond to ``self.spec`` and ``self.spec.prefix`` respectively. -As mentioned in :ref:`install-environment`, you will usually not need to refer -to dependencies explicitly in your package file, as the compiler wrappers take care of most of -the heavy lifting here. There will be times, though, when you need to refer to -the install locations of dependencies, or when you need to do something different -depending on the version, compiler, dependencies, etc. that your package is -built with. These parameters give you access to this type of information. +If the ``package.py`` encodes builders explicitly, the signature for a phase changes slightly: + +.. code-block:: python + + class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def install(self, pkg, spec, prefix): + ... + +In this case the package is passed as the second argument, and ``self`` is the builder instance. + +.. _multiple_build_systems: + +^^^^^^^^^^^^^^^^^^^^^^ +Multiple build systems +^^^^^^^^^^^^^^^^^^^^^^ + +There are cases where a software actively supports two build systems, or changes build systems +as it evolves, or needs different build systems on different platforms. Spack allows dealing with +these cases natively, if a recipe is written using builders explicitly. + +For instance, software that supports two build systems unconditionally should derive from +both ``*Package`` base classes, and declare the possible use of multiple build systems using +a directive: + +.. code-block:: python + + class ArpackNg(CMakePackage, AutotoolsPackage): + + build_system("cmake", "autotools", default="cmake") + +In this case the software can be built with both ``autotools`` and ``cmake``. Since the package +supports multiple build systems, it is necessary to declare which one is the default. The ``package.py`` +will likely contain some overriding of default builder methods: + +.. code-block:: python + + class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + pass + + class Autotoolsbuilder(spack.build_systems.autotools.AutotoolsBuilder): + def configure_args(self): + pass + +In more complex cases it might happen that the build system changes according to certain conditions, +for instance across versions. That can be expressed with conditional variant values: + +.. code-block:: python + + class ArpackNg(CMakePackage, AutotoolsPackage): + + build_system( + conditional("cmake", when="@0.64:"), + conditional("autotools", when="@:0.63"), + default="cmake", + ) + +In the example the directive impose a change from ``Autotools`` to ``CMake`` going +from ``v0.63`` to ``v0.64``. + +^^^^^^^^^^^^^^^^^^ +Mixin base classes +^^^^^^^^^^^^^^^^^^ + +Besides build systems, there are other cases where common metadata and behavior can be extracted +and reused by many packages. For instance, packages that depend on ``Cuda`` or ``Rocm``, share +common dependencies and constraints. To factor these attributes into a single place, Spack provides +a few mixin classes in the ``spack.build_systems`` module: + ++---------------------------------------------------------------+----------------------------------+ +| **API docs** | **Description** | ++===============================================================+==================================+ +| :class:`~spack.build_systems.cuda.CudaPackage` | A helper class for packages that | +| | use CUDA | ++---------------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.rocm.ROCmPackage` | A helper class for packages that | +| | use ROCm | ++---------------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.gnu.GNUMirrorPackage` | A helper class for GNU packages | ++---------------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.python.PythonExtension` | A helper class for Python | +| | extensions | ++---------------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.sourceforge.SourceforgePackage` | A helper class for packages | +| | from sourceforge.org | ++---------------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.sourceware.SourcewarePackage` | A helper class for packages | +| | from sourceware.org | ++---------------------------------------------------------------+----------------------------------+ +| :class:`~spack.build_systems.xorg.XorgPackage` | A helper class for x.org | +| | packages | ++---------------------------------------------------------------+----------------------------------+ + +These classes should be used by adding them to the inheritance tree of the package that needs them, +for instance: + +.. code-block:: python + + class Cp2k(MakefilePackage, CudaPackage): + """CP2K is a quantum chemistry and solid state physics software package + that can perform atomistic simulations of solid state, liquid, molecular, + periodic, material, crystal, and biological systems + """ + +In the example above ``Cp2k`` inherits all the conflicts and variants that ``CudaPackage`` defines. .. _install-environment: @@ -6116,3 +6297,82 @@ might write: DWARF_PREFIX = $(spack location --install-dir libdwarf) CXXFLAGS += -I$DWARF_PREFIX/include CXXFLAGS += -L$DWARF_PREFIX/lib + + +.. _package_class_structure: + +-------------------------- +Package class architecture +-------------------------- + +.. note:: + + This section aims to provide a high-level knowledge of how the package class architecture evolved + in Spack, and provides some insights on the current design. + +Packages in Spack were originally designed to support only a single build system. The overall +class structure for a package looked like: + +.. image:: images/original_package_architecture.png + :scale: 60 % + :align: center + +In this architecture the base class ``AutotoolsPackage`` was responsible for both the metadata +related to the ``autotools`` build system (e.g. dependencies or variants common to all packages +using it), and for encoding the default installation procedure. + +In reality, a non-negligible number of packages are either changing their build system during the evolution of the +project, or using different build systems for different platforms. An architecture based on a single class +requires hacks or other workarounds to deal with these cases. + +To support a model more adherent to reality, Spack v0.19 changed its internal design by extracting +the attributes and methods related to building a software into a separate hierarchy: + +.. image:: images/builder_package_architecture.png + :scale: 60 % + :align: center + +In this new format each ``package.py`` contains one ``*Package`` class that gathers all the metadata, +and one or more ``*Builder`` classes that encode the installation procedure. A specific builder object +is created just before the software is built, so at a time where Spack knows which build system needs +to be used for the current installation, and receives a ``package`` object during initialization. + +^^^^^^^^^^^^^^^^^^^^^^^^ +``build_system`` variant +^^^^^^^^^^^^^^^^^^^^^^^^ + +To allow imposing conditions based on the build system, each package must a have ``build_system`` variant, +which is usually inherited from base classes. This variant allows for writing metadata that is conditional +on the build system: + +.. code-block:: python + + with when("build_system=cmake"): + depends_on("cmake", type="build") + +and also for selecting a specific build system from a spec literal, like in the following command: + +.. code-block:: console + + $ spack install arpack-ng build_system=autotools + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Compatibility with single-class format +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Internally, Spack always uses builders to perform operations related to the installation of a specific software. +The builders are created in the ``spack.builder.create`` function + +.. literalinclude:: _spack_root/lib/spack/spack/builder.py + :pyobject: create + +To achieve backward compatibility with the single-class format Spack creates in this function a special +"adapter builder", if no custom builder is detected in the recipe: + +.. image:: images/adapter.png + :scale: 60 % + :align: center + +Overall the role of the adapter is to route access to attributes of methods first through the ``*Package`` +hierarchy, and then back to the base class builder. This is schematically shown in the diagram above, where +the adapter role is to "emulate" a method resolution order like the one represented by the red arrows.
\ No newline at end of file diff --git a/lib/spack/spack/audit.py b/lib/spack/spack/audit.py index ee974a19b1..de9fc1a05b 100644 --- a/lib/spack/spack/audit.py +++ b/lib/spack/spack/audit.py @@ -503,6 +503,33 @@ def _ensure_all_packages_use_sha256_checksums(pkgs, error_cls): return errors +@package_properties +def _ensure_env_methods_are_ported_to_builders(pkgs, error_cls): + """Ensure that methods modifying the build environment are ported to builder classes.""" + errors = [] + for pkg_name in pkgs: + pkg_cls = spack.repo.path.get_pkg_class(pkg_name) + buildsystem_variant, _ = pkg_cls.variants["build_system"] + buildsystem_names = [getattr(x, "value", x) for x in buildsystem_variant.values] + builder_cls_names = [spack.builder.BUILDER_CLS[x].__name__ for x in buildsystem_names] + module = pkg_cls.module + has_builders_in_package_py = any( + getattr(module, name, False) for name in builder_cls_names + ) + if not has_builders_in_package_py: + continue + + for method_name in ("setup_build_environment", "setup_dependent_build_environment"): + if hasattr(pkg_cls, method_name): + msg = ( + "Package '{}' need to move the '{}' method from the package class to the" + " appropriate builder class".format(pkg_name, method_name) + ) + errors.append(error_cls(msg, [])) + + return errors + + @package_https_directives def _linting_package_file(pkgs, error_cls): """Check for correctness of links""" @@ -660,7 +687,13 @@ def _ensure_variant_defaults_are_parsable(pkgs, error_cls): errors.append(error_cls(error_msg.format(variant_name, pkg_name), [])) continue - vspec = variant.make_default() + try: + vspec = variant.make_default() + except spack.variant.MultipleValuesInExclusiveVariantError: + error_msg = "Cannot create a default value for the variant '{}' in package '{}'" + errors.append(error_cls(error_msg.format(variant_name, pkg_name), [])) + continue + try: variant.validate_or_raise(vspec, pkg_cls=pkg_cls) except spack.variant.InvalidVariantValueError: diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py index f8fda997d3..9fe7c1fbb7 100644 --- a/lib/spack/spack/build_environment.py +++ b/lib/spack/spack/build_environment.py @@ -52,6 +52,7 @@ from llnl.util.tty.log import MultiProcessFd import spack.build_systems.cmake import spack.build_systems.meson +import spack.builder import spack.config import spack.install_test import spack.main @@ -558,9 +559,9 @@ def _set_variables_for_single_module(pkg, module): if sys.platform == "win32": m.nmake = Executable("nmake") # Standard CMake arguments - m.std_cmake_args = spack.build_systems.cmake.CMakePackage._std_args(pkg) - m.std_meson_args = spack.build_systems.meson.MesonPackage._std_args(pkg) - m.std_pip_args = spack.build_systems.python.PythonPackage._std_args(pkg) + m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg) + m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg) + m.std_pip_args = spack.build_systems.python.PythonPipBuilder.std_args(pkg) # Put spack compiler paths in module scope. link_dir = spack.paths.build_env_path @@ -727,38 +728,6 @@ def get_rpaths(pkg): return list(dedupe(filter_system_paths(rpaths))) -def get_std_cmake_args(pkg): - """List of standard arguments used if a package is a CMakePackage. - - Returns: - list: standard arguments that would be used if this - package were a CMakePackage instance. - - Args: - pkg (spack.package_base.PackageBase): package under consideration - - Returns: - list: arguments for cmake - """ - return spack.build_systems.cmake.CMakePackage._std_args(pkg) - - -def get_std_meson_args(pkg): - """List of standard arguments used if a package is a MesonPackage. - - Returns: - list: standard arguments that would be used if this - package were a MesonPackage instance. - - Args: - pkg (spack.package_base.PackageBase): package under consideration - - Returns: - list: arguments for meson - """ - return spack.build_systems.meson.MesonPackage._std_args(pkg) - - def parent_class_modules(cls): """ Get list of superclass modules that descend from spack.package_base.PackageBase @@ -819,7 +788,8 @@ def setup_package(pkg, dirty, context="build"): platform.setup_platform_environment(pkg, env_mods) if context == "build": - pkg.setup_build_environment(env_mods) + builder = spack.builder.create(pkg) + builder.setup_build_environment(env_mods) if (not dirty) and (not env_mods.is_unset("CPATH")): tty.debug( @@ -1015,7 +985,8 @@ def modifications_from_dependencies( module.__dict__.update(changes.__dict__) if context == "build": - dpkg.setup_dependent_build_environment(env, spec) + builder = spack.builder.create(dpkg) + builder.setup_dependent_build_environment(env, spec) else: dpkg.setup_dependent_run_environment(env, spec) @@ -1117,8 +1088,20 @@ def _setup_pkg_and_run( pkg.test_suite.stage, spack.install_test.TestSuite.test_log_name(pkg.spec) ) + error_msg = str(exc) + if isinstance(exc, (spack.multimethod.NoSuchMethodError, AttributeError)): + error_msg = ( + "The '{}' package cannot find an attribute while trying to build " + "from sources. This might be due to a change in Spack's package format " + "to support multiple build-systems for a single package. You can fix this " + "by updating the build recipe, and you can also report the issue as a bug. " + "More information at https://spack.readthedocs.io/en/latest/packaging_guide.html#installation-procedure" + ).format(pkg.name) + error_msg = colorize("@*R{{{}}}".format(error_msg)) + error_msg = "{}\n\n{}".format(str(exc), error_msg) + # make a pickleable exception to send to parent. - msg = "%s: %s" % (exc_type.__name__, str(exc)) + msg = "%s: %s" % (exc_type.__name__, error_msg) ce = ChildError( msg, diff --git a/lib/spack/spack/build_systems/_checks.py b/lib/spack/spack/build_systems/_checks.py new file mode 100644 index 0000000000..73d5bbdb93 --- /dev/null +++ b/lib/spack/spack/build_systems/_checks.py @@ -0,0 +1,124 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +import six + +import llnl.util.lang + +import spack.builder +import spack.installer +import spack.relocate +import spack.store + + +def sanity_check_prefix(builder): + """Check that specific directories and files are created after installation. + + The files to be checked are in the ``sanity_check_is_file`` attribute of the + package object, while the directories are in the ``sanity_check_is_dir``. + + Args: + builder (spack.builder.Builder): builder that installed the package + """ + pkg = builder.pkg + + def check_paths(path_list, filetype, predicate): + if isinstance(path_list, six.string_types): + path_list = [path_list] + + for path in path_list: + abs_path = os.path.join(pkg.prefix, path) + if not predicate(abs_path): + msg = "Install failed for {0}. No such {1} in prefix: {2}" + msg = msg.format(pkg.name, filetype, path) + raise spack.installer.InstallError(msg) + + check_paths(pkg.sanity_check_is_file, "file", os.path.isfile) + check_paths(pkg.sanity_check_is_dir, "directory", os.path.isdir) + + ignore_file = llnl.util.lang.match_predicate(spack.store.layout.hidden_file_regexes) + if all(map(ignore_file, os.listdir(pkg.prefix))): + msg = "Install failed for {0}. Nothing was installed!" + raise spack.installer.InstallError(msg.format(pkg.name)) + + +def apply_macos_rpath_fixups(builder): + """On Darwin, make installed libraries more easily relocatable. + + Some build systems (handrolled, autotools, makefiles) can set their own + rpaths that are duplicated by spack's compiler wrapper. This fixup + interrogates, and postprocesses if necessary, all libraries installed + by the code. + + It should be added as a @run_after to packaging systems (or individual + packages) that do not install relocatable libraries by default. + + Args: + builder (spack.builder.Builder): builder that installed the package + """ + spack.relocate.fixup_macos_rpaths(builder.spec) + + +def ensure_build_dependencies_or_raise(spec, dependencies, error_msg): + """Ensure that some build dependencies are present in the concrete spec. + + If not, raise a RuntimeError with a helpful error message. + + Args: + spec (spack.spec.Spec): concrete spec to be checked. + dependencies (list of spack.spec.Spec): list of abstract specs to be satisfied + error_msg (str): brief error message to be prepended to a longer description + + Raises: + RuntimeError: when the required build dependencies are not found + """ + assert spec.concrete, "Can ensure build dependencies only on concrete specs" + build_deps = [d.name for d in spec.dependencies(deptype="build")] + missing_deps = [x for x in dependencies if x not in build_deps] + + if not missing_deps: + return + + # Raise an exception on missing deps. + msg = ( + "{0}: missing dependencies: {1}.\n\nPlease add " + "the following lines to the package:\n\n".format(error_msg, ", ".join(missing_deps)) + ) + + for dep in missing_deps: + msg += " depends_on('{0}', type='build', when='@{1} {2}')\n".format( + dep, spec.version, "build_system=autotools" + ) + + msg += "\nUpdate the version (when='@{0}') as needed.".format(spec.version) + raise RuntimeError(msg) + + +def execute_build_time_tests(builder): + """Execute the build-time tests prescribed by builder. + + Args: + builder (Builder): builder prescribing the test callbacks. The name of the callbacks is + stored as a list of strings in the ``build_time_test_callbacks`` attribute. + """ + builder.pkg.run_test_callbacks(builder, builder.build_time_test_callbacks, "build") + + +def execute_install_time_tests(builder): + """Execute the install-time tests prescribed by builder. + + Args: + builder (Builder): builder prescribing the test callbacks. The name of the callbacks is + stored as a list of strings in the ``install_time_test_callbacks`` attribute. + """ + builder.pkg.run_test_callbacks(builder, builder.install_time_test_callbacks, "install") + + +class BaseBuilder(spack.builder.Builder): + """Base class for builders to register common checks""" + + # Check that self.prefix is there after installation + spack.builder.run_after("install")(sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/aspell_dict.py b/lib/spack/spack/build_systems/aspell_dict.py index f1e41cc3df..9de8255e68 100644 --- a/lib/spack/spack/build_systems/aspell_dict.py +++ b/lib/spack/spack/build_systems/aspell_dict.py @@ -2,18 +2,36 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +import llnl.util.filesystem as fs -# Why doesn't this work for me? -# from spack import * -from llnl.util.filesystem import filter_file +import spack.directives +import spack.package_base +import spack.util.executable -from spack.build_systems.autotools import AutotoolsPackage -from spack.directives import extends -from spack.package_base import ExtensionError -from spack.util.executable import which +from .autotools import AutotoolsBuilder, AutotoolsPackage + + +class AspellBuilder(AutotoolsBuilder): + """The Aspell builder is close enough to an autotools builder to allow + specializing the builder class, so to use variables that are specific + to the Aspell extensions. + """ + + def configure(self, pkg, spec, prefix): + aspell = spec["aspell"].prefix.bin.aspell + prezip = spec["aspell"].prefix.bin.prezip + destdir = prefix + + sh = spack.util.executable.which("sh") + sh( + "./configure", + "--vars", + "ASPELL={0}".format(aspell), + "PREZIP={0}".format(prezip), + "DESTDIR={0}".format(destdir), + ) -# # Aspell dictionaries install their bits into their prefix.lib # and when activated they'll get symlinked into the appropriate aspell's # dict dir (see aspell's {de,}activate methods). @@ -23,12 +41,17 @@ from spack.util.executable import which class AspellDictPackage(AutotoolsPackage): """Specialized class for building aspell dictionairies.""" - extends("aspell") + spack.directives.extends("aspell", when="build_system=autotools") + + #: Override the default autotools builder + AutotoolsBuilder = AspellBuilder def view_destination(self, view): aspell_spec = self.spec["aspell"] if view.get_projection_for_spec(aspell_spec) != aspell_spec.prefix: - raise ExtensionError("aspell does not support non-global extensions") + raise spack.package_base.ExtensionError( + "aspell does not support non-global extensions" + ) aspell = aspell_spec.command return aspell("dump", "config", "dict-dir", output=str).strip() @@ -36,19 +59,5 @@ class AspellDictPackage(AutotoolsPackage): return self.prefix.lib def patch(self): - filter_file(r"^dictdir=.*$", "dictdir=/lib", "configure") - filter_file(r"^datadir=.*$", "datadir=/lib", "configure") - - def configure(self, spec, prefix): - aspell = spec["aspell"].prefix.bin.aspell - prezip = spec["aspell"].prefix.bin.prezip - destdir = prefix - - sh = which("sh") - sh( - "./configure", - "--vars", - "ASPELL={0}".format(aspell), - "PREZIP={0}".format(prezip), - "DESTDIR={0}".format(destdir), - ) + fs.filter_file(r"^dictdir=.*$", "dictdir=/lib", "configure") + fs.filter_file(r"^datadir=.*$", "datadir=/lib", "configure") diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py index 81a572de52..76e6bb27ce 100644 --- a/lib/spack/spack/build_systems/autotools.py +++ b/lib/spack/spack/build_systems/autotools.py @@ -6,87 +6,140 @@ import inspect import os import os.path import stat -from subprocess import PIPE, check_call +import subprocess from typing import List # novm import llnl.util.filesystem as fs import llnl.util.tty as tty -from llnl.util.filesystem import force_remove, working_dir -from spack.build_environment import InstallError -from spack.directives import conflicts, depends_on +import spack.build_environment +import spack.builder +import spack.package_base +from spack.directives import build_system, conflicts, depends_on +from spack.multimethod import when from spack.operating_systems.mac_os import macos_version -from spack.package_base import PackageBase, run_after, run_before from spack.util.executable import Executable from spack.version import Version +from ._checks import ( + BaseBuilder, + apply_macos_rpath_fixups, + ensure_build_dependencies_or_raise, + execute_build_time_tests, + execute_install_time_tests, +) -class AutotoolsPackage(PackageBase): - """Specialized class for packages built using GNU Autotools. - This class provides four phases that can be overridden: +class AutotoolsPackage(spack.package_base.PackageBase): + """Specialized class for packages built using GNU Autotools.""" - 1. :py:meth:`~.AutotoolsPackage.autoreconf` - 2. :py:meth:`~.AutotoolsPackage.configure` - 3. :py:meth:`~.AutotoolsPackage.build` - 4. :py:meth:`~.AutotoolsPackage.install` + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "AutotoolsPackage" + + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "autotools" + + build_system("autotools") + + with when("build_system=autotools"): + depends_on("gnuconfig", type="build", when="target=ppc64le:") + depends_on("gnuconfig", type="build", when="target=aarch64:") + depends_on("gnuconfig", type="build", when="target=riscv64:") + conflicts("platform=windows") + + 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) + # Spack's fflags are meant for both F77 and FC, therefore we + # additionaly set FCFLAGS if required. + values = flags.get("fflags", None) + if values: + values_str = "FCFLAGS={0}".format(" ".join(values)) + self.configure_flag_args.append(values_str) + + # Legacy methods (used by too many packages to change them, + # need to forward to the builder) + def enable_or_disable(self, *args, **kwargs): + return self.builder.enable_or_disable(*args, **kwargs) + + def with_or_without(self, *args, **kwargs): + return self.builder.with_or_without(*args, **kwargs) + + +@spack.builder.builder("autotools") +class AutotoolsBuilder(BaseBuilder): + """The autotools builder encodes the default way of installing software built + with autotools. It has four phases that can be overridden, if need be: + + 1. :py:meth:`~.AutotoolsBuilder.autoreconf` + 2. :py:meth:`~.AutotoolsBuilder.configure` + 3. :py:meth:`~.AutotoolsBuilder.build` + 4. :py:meth:`~.AutotoolsBuilder.install` + + They all have sensible defaults and for many packages the only thing necessary + is to override the helper method + :meth:`~spack.build_systems.autotools.AutotoolsBuilder.configure_args`. - They all have sensible defaults and for many packages the only thing - necessary will be to override the helper method - :meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args`. For a finer tuning you may also override: +-----------------------------------------------+--------------------+ | **Method** | **Purpose** | +===============================================+====================+ - | :py:attr:`~.AutotoolsPackage.build_targets` | Specify ``make`` | + | :py:attr:`~.AutotoolsBuilder.build_targets` | Specify ``make`` | | | targets for the | | | build phase | +-----------------------------------------------+--------------------+ - | :py:attr:`~.AutotoolsPackage.install_targets` | Specify ``make`` | + | :py:attr:`~.AutotoolsBuilder.install_targets` | Specify ``make`` | | | targets for the | | | install phase | +-----------------------------------------------+--------------------+ - | :py:meth:`~.AutotoolsPackage.check` | Run build time | + | :py:meth:`~.AutotoolsBuilder.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" - - @property - def patch_config_files(self): - """ - Whether or not to update old ``config.guess`` and ``config.sub`` files - distributed with the tarball. This currently only applies to - ``ppc64le:``, ``aarch64:``, and ``riscv64`` target architectures. The - substitutes are taken from the ``gnuconfig`` package, which is - automatically added as a build dependency for these architectures. In - case system versions of these config files are required, the - ``gnuconfig`` package can be marked external with a prefix pointing to - the directory containing the system ``config.guess`` and ``config.sub`` - files. - """ - return ( - self.spec.satisfies("target=ppc64le:") - or self.spec.satisfies("target=aarch64:") - or self.spec.satisfies("target=riscv64:") - ) - - #: Whether or not to update ``libtool`` - #: (currently only for Arm/Clang/Fujitsu/NVHPC compilers) + phases = ("autoreconf", "configure", "build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ( + "configure_args", + "check", + "installcheck", + ) + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "archive_files", + "patch_libtool", + "build_targets", + "install_targets", + "build_time_test_callbacks", + "install_time_test_callbacks", + "force_autoreconf", + "autoreconf_extra_args", + "install_libtool_archives", + "patch_config_files", + "configure_directory", + "configure_abs_path", + "build_directory", + "autoreconf_search_path_args", + ) + + #: Whether to update ``libtool`` (e.g. for Arm/Clang/Fujitsu/NVHPC compilers) patch_libtool = True - #: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.build` - #: phase + #: Targets for ``make`` during the :py:meth:`~.AutotoolsBuilder.build` phase build_targets = [] # type: List[str] - #: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.install` - #: phase + #: Targets for ``make`` during the :py:meth:`~.AutotoolsBuilder.install` phase install_targets = ["install"] #: Callback names for build-time test @@ -97,24 +150,40 @@ class AutotoolsPackage(PackageBase): #: 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 = [] # type: List[str] - #: If False deletes all the .la files in the prefix folder - #: after the installation. If True instead it installs them. + #: If False deletes all the .la files in the prefix folder after the installation. + #: If True instead it installs them. install_libtool_archives = False - depends_on("gnuconfig", type="build", when="target=ppc64le:") - depends_on("gnuconfig", type="build", when="target=aarch64:") - depends_on("gnuconfig", type="build", when="target=riscv64:") - conflicts("platform=windows") + @property + def patch_config_files(self): + """Whether to update old ``config.guess`` and ``config.sub`` files + distributed with the tarball. + + This currently only applies to ``ppc64le:``, ``aarch64:``, and + ``riscv64`` target architectures. + + The substitutes are taken from the ``gnuconfig`` package, which is + automatically added as a build dependency for these architectures. In case + system versions of these config files are required, the ``gnuconfig`` package + can be marked external, with a prefix pointing to the directory containing the + system ``config.guess`` and ``config.sub`` files. + """ + return ( + self.pkg.spec.satisfies("target=ppc64le:") + or self.pkg.spec.satisfies("target=aarch64:") + or self.pkg.spec.satisfies("target=riscv64:") + ) @property def _removed_la_files_log(self): - """File containing the list of remove libtool archives""" + """File containing the list of removed libtool archives""" build_dir = self.build_directory if not os.path.isabs(self.build_directory): - build_dir = os.path.join(self.stage.path, build_dir) + build_dir = os.path.join(self.pkg.stage.path, build_dir) return os.path.join(build_dir, "removed_la_files.txt") @property @@ -125,13 +194,13 @@ class AutotoolsPackage(PackageBase): files.append(self._removed_la_files_log) return files - @run_after("autoreconf") + @spack.builder.run_after("autoreconf") def _do_patch_config_files(self): - """Some packages ship with older config.guess/config.sub files and - need to have these 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) and for ARM (aarch64) and - RISC-V (riscv64). + """Some packages ship with older config.guess/config.sub files and need to + have these 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) and for AArch64 and RISC-V. """ if not self.patch_config_files: return @@ -139,11 +208,11 @@ class AutotoolsPackage(PackageBase): # TODO: Expand this to select the 'config.sub'-compatible architecture # for each platform (e.g. 'config.sub' doesn't accept 'power9le', but # does accept 'ppc64le'). - if self.spec.satisfies("target=ppc64le:"): + if self.pkg.spec.satisfies("target=ppc64le:"): config_arch = "ppc64le" - elif self.spec.satisfies("target=aarch64:"): + elif self.pkg.spec.satisfies("target=aarch64:"): config_arch = "aarch64" - elif self.spec.satisfies("target=riscv64:"): + elif self.pkg.spec.satisfies("target=riscv64:"): config_arch = "riscv64" else: config_arch = "local" @@ -155,7 +224,7 @@ class AutotoolsPackage(PackageBase): args = [script_abs_path] + additional_args.get(script_name, []) try: - check_call(args, stdout=PIPE, stderr=PIPE) + subprocess.check_call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except Exception as e: tty.debug(e) return False @@ -163,7 +232,7 @@ class AutotoolsPackage(PackageBase): return True # Get the list of files that needs to be patched - to_be_patched = fs.find(self.stage.path, files=["config.sub", "config.guess"]) + to_be_patched = fs.find(self.pkg.stage.path, files=["config.sub", "config.guess"]) to_be_patched = [f for f in to_be_patched if not runs_ok(f)] # If there are no files to be patched, return early @@ -171,22 +240,21 @@ class AutotoolsPackage(PackageBase): return # Otherwise, require `gnuconfig` to be a build dependency - self._require_build_deps( - pkgs=["gnuconfig"], spec=self.spec, err="Cannot patch config files" + ensure_build_dependencies_or_raise( + spec=self.pkg.spec, dependencies=["gnuconfig"], error_msg="Cannot patch config files" ) # Get the config files we need to patch (config.sub / config.guess). to_be_found = list(set(os.path.basename(f) for f in to_be_patched)) - gnuconfig = self.spec["gnuconfig"] + gnuconfig = self.pkg.spec["gnuconfig"] gnuconfig_dir = gnuconfig.prefix # An external gnuconfig may not not have a prefix. if gnuconfig_dir is None: - raise InstallError( - "Spack could not find substitutes for GNU config " - "files because no prefix is available for the " - "`gnuconfig` package. Make sure you set a prefix " - "path instead of modules for external `gnuconfig`." + raise spack.build_environment.InstallError( + "Spack could not find substitutes for GNU config files because no " + "prefix is available for the `gnuconfig` package. Make sure you set a " + "prefix path instead of modules for external `gnuconfig`." ) candidates = fs.find(gnuconfig_dir, files=to_be_found, recursive=False) @@ -203,7 +271,7 @@ class AutotoolsPackage(PackageBase): msg += ( " or the `gnuconfig` package prefix is misconfigured as" " an external package" ) - raise InstallError(msg) + raise spack.build_environment.InstallError(msg) # Filter working substitutes candidates = [f for f in candidates if runs_ok(f)] @@ -228,7 +296,9 @@ To resolve this problem, please try the following: and set the prefix to the directory containing the `config.guess` and `config.sub` files. """ - raise InstallError(msg.format(", ".join(to_be_found), self.name)) + raise spack.build_environment.InstallError( + msg.format(", ".join(to_be_found), self.name) + ) # Copy the good files over the bad ones for abs_path in to_be_patched: @@ -238,7 +308,7 @@ To resolve this problem, please try the following: fs.copy(substitutes[name], abs_path) os.chmod(abs_path, mode) - @run_before("configure") + @spack.builder.run_before("configure") def _patch_usr_bin_file(self): """On NixOS file is not available in /usr/bin/file. Patch configure scripts to use file from path.""" @@ -250,7 +320,7 @@ To resolve this problem, please try the following: with fs.keep_modification_time(*x.filenames): x.filter(regex="/usr/bin/file", repl="file", string=True) - @run_before("configure") + @spack.builder.run_before("configure") def _set_autotools_environment_variables(self): """Many autotools builds use a version of mknod.m4 that fails when running as root unless FORCE_UNSAFE_CONFIGURE is set to 1. @@ -261,11 +331,10 @@ To resolve this problem, please try the following: Without it, configure just fails halfway through, but it can still run things *before* this check. Forcing this just removes a nuisance -- this is not circumventing any real protection. - """ os.environ["FORCE_UNSAFE_CONFIGURE"] = "1" - @run_before("configure") + @spack.builder.run_before("configure") def _do_patch_libtool_configure(self): """Patch bugs that propagate from libtool macros into "configure" and further into "libtool". Note that patches that can be fixed by patching @@ -293,7 +362,7 @@ To resolve this problem, please try the following: # Support Libtool 2.4.2 and older: x.filter(regex=r'^(\s*test \$p = "-R")(; then\s*)$', repl=r'\1 || test x-l = x"$p"\2') - @run_after("configure") + @spack.builder.run_after("configure") def _do_patch_libtool(self): """If configure generates a "libtool" script that does not correctly detect the compiler (and patch_libtool is set), patch in the correct @@ -328,31 +397,33 @@ To resolve this problem, please try the following: markers[tag] = "LIBTOOL TAG CONFIG: {0}".format(tag.upper()) # Replace empty linker flag prefixes: - if self.compiler.name == "nag": + if self.pkg.compiler.name == "nag": # Nag is mixed with gcc and g++, which are recognized correctly. # Therefore, we change only Fortran values: for tag in ["fc", "f77"]: marker = markers[tag] x.filter( regex='^wl=""$', - repl='wl="{0}"'.format(self.compiler.linker_arg), + repl='wl="{0}"'.format(self.pkg.compiler.linker_arg), start_at="# ### BEGIN {0}".format(marker), stop_at="# ### END {0}".format(marker), ) else: - x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.compiler.linker_arg)) + x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.pkg.compiler.linker_arg)) # Replace empty PIC flag values: for cc, marker in markers.items(): x.filter( regex='^pic_flag=""$', - repl='pic_flag="{0}"'.format(getattr(self.compiler, "{0}_pic_flag".format(cc))), + repl='pic_flag="{0}"'.format( + getattr(self.pkg.compiler, "{0}_pic_flag".format(cc)) + ), start_at="# ### BEGIN {0}".format(marker), stop_at="# ### END {0}".format(marker), ) # Other compiler-specific patches: - if self.compiler.name == "fj": + if self.pkg.compiler.name == "fj": x.filter(regex="-nostdlib", repl="", string=True) rehead = r"/\S*/" for o in [ @@ -365,12 +436,12 @@ To resolve this problem, please try the following: "crtendS.o", ]: x.filter(regex=(rehead + o), repl="", string=True) - elif self.compiler.name == "dpcpp": + elif self.pkg.compiler.name == "dpcpp": # Hack to filter out spurious predep_objects when building with Intel dpcpp # (see https://github.com/spack/spack/issues/32863): x.filter(regex=r"^(predep_objects=.*)/tmp/conftest-[0-9A-Fa-f]+\.o", repl=r"\1") x.filter(regex=r"^(predep_objects=.*)/tmp/a-[0-9A-Fa-f]+\.o", repl=r"\1") - elif self.compiler.name == "nag": + elif self.pkg.compiler.name == "nag": for tag in ["fc", "f77"]: marker = markers[tag] start_at = "# ### BEGIN {0}".format(marker) @@ -446,11 +517,8 @@ To resolve this problem, please try the following: @property def configure_directory(self): - """Returns the directory where 'configure' resides. - - :return: directory where to find configure - """ - return self.stage.source_path + """Return the directory where 'configure' resides.""" + return self.pkg.stage.source_path @property def configure_abs_path(self): @@ -463,34 +531,12 @@ To resolve this problem, please try the following: """Override to provide another place to build the package""" return self.configure_directory - @run_before("autoreconf") + @spack.builder.run_before("autoreconf") def delete_configure_to_force_update(self): if self.force_autoreconf: - force_remove(self.configure_abs_path) - - def _require_build_deps(self, pkgs, spec, err): - """Require `pkgs` to be direct build dependencies of `spec`. Raises a - RuntimeError with a helpful error messages when any dep is missing.""" - - build_deps = [d.name for d in spec.dependencies(deptype="build")] - missing_deps = [x for x in pkgs if x not in build_deps] - - if not missing_deps: - return - - # Raise an exception on missing deps. - msg = ( - "{0}: missing dependencies: {1}.\n\nPlease add " - "the following lines to the package:\n\n".format(err, ", ".join(missing_deps)) - ) - - for dep in missing_deps: - msg += " depends_on('{0}', type='build', when='@{1}')\n".format(dep, spec.version) - - msg += "\nUpdate the version (when='@{0}') as needed.".format(spec.version) - raise RuntimeError(msg) + fs.force_remove(self.configure_abs_path) - def autoreconf(self, spec, prefix): + def autoreconf(self, pkg, spec, prefix): """Not needed usually, configure should be already there""" # If configure exists nothing needs to be done @@ -498,8 +544,10 @@ To resolve this problem, please try the following: return # Else try to regenerate it, which reuquires a few build dependencies - self._require_build_deps( - pkgs=["autoconf", "automake", "libtool"], spec=spec, err="Cannot generate configure" + ensure_build_dependencies_or_raise( + spec=spec, + dependencies=["autoconf", "automake", "libtool"], + error_msg="Cannot generate configure", ) tty.msg("Configure script not found: trying to generate it") @@ -507,8 +555,8 @@ To resolve this problem, please try the following: 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) + with fs.working_dir(self.configure_directory): + m = inspect.getmodule(self.pkg) # This line is what is needed most of the time # --install, --verbose, --force autoreconf_args = ["-ivf"] @@ -524,98 +572,66 @@ To resolve this problem, please try the following: spack dependencies.""" return _autoreconf_search_path_args(self.spec) - @run_after("autoreconf") + @spack.builder.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. + """Ensure the presence of a "configure" script, or raise. If the "configure" + is found, a module level attribute is set. - :raises RuntimeError: if a configure script is not found in - :py:meth:`~AutotoolsPackage.configure_directory` + Raises: + RuntimeError: if the "configure" script is not found """ - # Check if a configure script is there. If not raise a RuntimeError. + # Check if the "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) + inspect.getmodule(self.pkg).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 the list of all the arguments that must be passed to configure, + except ``--prefix`` which will be pre-pended to the list. """ 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) - # Spack's fflags are meant for both F77 and FC, therefore we - # additionaly set FCFLAGS if required. - values = flags.get("fflags", None) - if values: - values_str = "FCFLAGS={0}".format(" ".join(values)) - self.configure_flag_args.append(values_str) - - def configure(self, spec, prefix): - """Runs configure with the arguments specified in - :meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args` - and an appropriately set prefix. + def configure(self, pkg, spec, prefix): + """Run "configure", with the arguments specified by the builder and an + appropriately set prefix. """ - options = getattr(self, "configure_flag_args", []) + options = getattr(self.pkg, "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) - - def setup_build_environment(self, env): - if self.spec.platform == "darwin" and macos_version() >= Version("11"): - # Many configure files rely on matching '10.*' for macOS version - # detection and fail to add flags if it shows as version 11. - env.set("MACOSX_DEPLOYMENT_TARGET", "10.16") + with fs.working_dir(self.build_directory, create=True): + inspect.getmodule(self.pkg).configure(*options) - def build(self, spec, prefix): - """Makes the build targets specified by - :py:attr:``~.AutotoolsPackage.build_targets`` - """ + def build(self, pkg, spec, prefix): + """Run "make" on the build targets specified by the builder.""" # See https://autotools.io/automake/silent.html params = ["V=1"] params += self.build_targets - with working_dir(self.build_directory): - inspect.getmodule(self).make(*params) + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).make(*params) - 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) + def install(self, pkg, spec, prefix): + """Run "make" on the install targets specified by the builder.""" + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).make(*self.install_targets) - run_after("build")(PackageBase._run_default_build_time_test_callbacks) + spack.builder.run_after("build")(execute_build_time_tests) 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 "make" on the ``test`` and ``check`` targets, if found.""" + with fs.working_dir(self.build_directory): + self.pkg._if_make_target_execute("test") + self.pkg._if_make_target_execute("check") def _activate_or_not( self, name, activation_word, deactivation_word, activation_value=None, variant=None ): - """This function contains the current implementation details of - :meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without` and - :meth:`~spack.build_systems.autotools.AutotoolsPackage.enable_or_disable`. + """This function contain the current implementation details of + :meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without` and + :meth:`~spack.build_systems.autotools.AutotoolsBuilder.enable_or_disable`. Args: name (str): name of the option that is being activated or not @@ -671,7 +687,7 @@ To resolve this problem, please try the following: Raises: KeyError: if name is not among known variants """ - spec = self.spec + spec = self.pkg.spec args = [] if activation_value == "prefix": @@ -681,16 +697,16 @@ To resolve this problem, please try the following: # Defensively look that the name passed as argument is among # variants - if variant not in self.variants: + if variant not in self.pkg.variants: msg = '"{0}" is not a variant of "{1}"' - raise KeyError(msg.format(variant, self.name)) + raise KeyError(msg.format(variant, self.pkg.name)) if variant not in spec.variants: return [] # Create a list of pairs. Each pair includes a configuration # option and whether or not that option is activated - variant_desc, _ = self.variants[variant] + variant_desc, _ = self.pkg.variants[variant] if set(variant_desc.values) == set((True, False)): # BoolValuedVariant carry information about a single option. # Nonetheless, for uniformity of treatment we'll package them @@ -718,14 +734,18 @@ To resolve this problem, please try the following: override_name = "{0}_or_{1}_{2}".format( activation_word, deactivation_word, option_value ) - line_generator = getattr(self, override_name, None) + line_generator = getattr(self, override_name, None) or getattr( + self.pkg, 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): + 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) @@ -764,7 +784,7 @@ To resolve this problem, please try the following: def enable_or_disable(self, name, activation_value=None, variant=None): """Same as - :meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without` + :meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without` but substitute ``with`` with ``enable`` and ``without`` with ``disable``. Args: @@ -781,19 +801,14 @@ To resolve this problem, please try the following: """ return self._activate_or_not(name, "enable", "disable", activation_value, variant) - run_after("install")(PackageBase._run_default_install_time_test_callbacks) + spack.builder.run_after("install")(execute_install_time_tests) 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) + """Run "make" on the ``installcheck`` target, if found.""" + with fs.working_dir(self.build_directory): + self.pkg._if_make_target_execute("installcheck") - @run_after("install") + @spack.builder.run_after("install") def remove_libtool_archives(self): """Remove all .la files in prefix sub-folders if the package sets ``install_libtool_archives`` to be False. @@ -803,14 +818,20 @@ To resolve this problem, please try the following: return # Remove the files and create a log of what was removed - libtool_files = fs.find(str(self.prefix), "*.la", recursive=True) + libtool_files = fs.find(str(self.pkg.prefix), "*.la", recursive=True) with fs.safe_remove(*libtool_files): fs.mkdirp(os.path.dirname(self._removed_la_files_log)) with open(self._removed_la_files_log, mode="w") as f: f.write("\n".join(libtool_files)) + def setup_build_environment(self, env): + if self.spec.platform == "darwin" and macos_version() >= Version("11"): + # Many configure files rely on matching '10.*' for macOS version + # detection and fail to add flags if it shows as version 11. + env.set("MACOSX_DEPLOYMENT_TARGET", "10.16") + # On macOS, force rpaths for shared library IDs and remove duplicate rpaths - run_after("install")(PackageBase.apply_macos_rpath_fixups) + spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) def _autoreconf_search_path_args(spec): diff --git a/lib/spack/spack/build_systems/bundle.py b/lib/spack/spack/build_systems/bundle.py new file mode 100644 index 0000000000..fad0ba4e14 --- /dev/null +++ b/lib/spack/spack/build_systems/bundle.py @@ -0,0 +1,31 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import spack.builder +import spack.directives +import spack.package_base + + +class BundlePackage(spack.package_base.PackageBase): + """General purpose bundle, or no-code, package class.""" + + #: This attribute is used in UI queries that require to know which + #: build-system class we are using + build_system_class = "BundlePackage" + + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "bundle" + + #: Bundle packages do not have associated source or binary code. + has_code = False + + spack.directives.build_system("bundle") + + +@spack.builder.builder("bundle") +class BundleBuilder(spack.builder.Builder): + phases = ("install",) + + def install(self, pkg, spec, prefix): + pass diff --git a/lib/spack/spack/build_systems/cached_cmake.py b/lib/spack/spack/build_systems/cached_cmake.py index 9ffd2a82ab..7caf2a1539 100644 --- a/lib/spack/spack/build_systems/cached_cmake.py +++ b/lib/spack/spack/build_systems/cached_cmake.py @@ -3,12 +3,14 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) import os +from typing import Tuple +import llnl.util.filesystem as fs import llnl.util.tty as tty -from llnl.util.filesystem import install, mkdirp -from spack.build_systems.cmake import CMakePackage -from spack.package_base import run_after +import spack.builder + +from .cmake import CMakeBuilder, CMakePackage def cmake_cache_path(name, value, comment=""): @@ -28,44 +30,46 @@ def cmake_cache_option(name, boolean_value, comment=""): return 'set({0} {1} CACHE BOOL "{2}")\n'.format(name, value, comment) -class CachedCMakePackage(CMakePackage): - """Specialized class for packages built using CMake initial cache. +class CachedCMakeBuilder(CMakeBuilder): - This feature of CMake allows packages to increase reproducibility, - especially between Spack- and manual builds. It also allows packages to - sidestep certain parsing bugs in extremely long ``cmake`` commands, and to - avoid system limits on the length of the command line.""" + #: Names associated with package methods in the old build-system format + legacy_methods = CMakeBuilder.legacy_methods + ( + "initconfig_compiler_entries", + "initconfig_mpi_entries", + "initconfig_hardware_entries", + "std_initconfig_entries", + "initconfig_package_entries", + ) # type: Tuple[str, ...] - phases = ["initconfig", "cmake", "build", "install"] + #: Names associated with package attributes in the old build-system format + legacy_attributes = CMakeBuilder.legacy_attributes + ( + "cache_name", + "cache_path", + ) # type: Tuple[str, ...] @property def cache_name(self): return "{0}-{1}-{2}@{3}.cmake".format( - self.name, - self.spec.architecture, - self.spec.compiler.name, - self.spec.compiler.version, + self.pkg.name, + self.pkg.spec.architecture, + self.pkg.spec.compiler.name, + self.pkg.spec.compiler.version, ) @property def cache_path(self): - return os.path.join(self.stage.source_path, self.cache_name) - - def flag_handler(self, name, flags): - if name in ("cflags", "cxxflags", "cppflags", "fflags"): - return (None, None, None) # handled in the cmake cache - return (flags, None, None) + return os.path.join(self.pkg.stage.source_path, self.cache_name) def initconfig_compiler_entries(self): # This will tell cmake to use the Spack compiler wrappers when run # through Spack, but use the underlying compiler when run outside of # Spack - spec = self.spec + spec = self.pkg.spec # Fortran compiler is optional if "FC" in os.environ: spack_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", os.environ["FC"]) - system_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", self.compiler.fc) + system_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", self.pkg.compiler.fc) else: spack_fc_entry = "# No Fortran compiler defined in spec" system_fc_entry = "# No Fortran compiler defined in spec" @@ -81,8 +85,8 @@ class CachedCMakePackage(CMakePackage): " " + cmake_cache_path("CMAKE_CXX_COMPILER", os.environ["CXX"]), " " + spack_fc_entry, "else()\n", - " " + cmake_cache_path("CMAKE_C_COMPILER", self.compiler.cc), - " " + cmake_cache_path("CMAKE_CXX_COMPILER", self.compiler.cxx), + " " + cmake_cache_path("CMAKE_C_COMPILER", self.pkg.compiler.cc), + " " + cmake_cache_path("CMAKE_CXX_COMPILER", self.pkg.compiler.cxx), " " + system_fc_entry, "endif()\n", ] @@ -126,7 +130,7 @@ class CachedCMakePackage(CMakePackage): return entries def initconfig_mpi_entries(self): - spec = self.spec + spec = self.pkg.spec if not spec.satisfies("^mpi"): return [] @@ -160,13 +164,13 @@ class CachedCMakePackage(CMakePackage): mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpiexec") if not os.path.exists(mpiexec): - msg = "Unable to determine MPIEXEC, %s tests may fail" % self.name + msg = "Unable to determine MPIEXEC, %s tests may fail" % self.pkg.name entries.append("# {0}\n".format(msg)) tty.warn(msg) else: # starting with cmake 3.10, FindMPI expects MPIEXEC_EXECUTABLE # vs the older versions which expect MPIEXEC - if self.spec["cmake"].satisfies("@3.10:"): + if self.pkg.spec["cmake"].satisfies("@3.10:"): entries.append(cmake_cache_path("MPIEXEC_EXECUTABLE", mpiexec)) else: entries.append(cmake_cache_path("MPIEXEC", mpiexec)) @@ -180,7 +184,7 @@ class CachedCMakePackage(CMakePackage): return entries def initconfig_hardware_entries(self): - spec = self.spec + spec = self.pkg.spec entries = [ "#------------------{0}".format("-" * 60), @@ -212,7 +216,7 @@ class CachedCMakePackage(CMakePackage): "#------------------{0}".format("-" * 60), "# !!!! This is a generated file, edit at own risk !!!!", "#------------------{0}".format("-" * 60), - "# CMake executable path: {0}".format(self.spec["cmake"].command.path), + "# CMake executable path: {0}".format(self.pkg.spec["cmake"].command.path), "#------------------{0}\n".format("-" * 60), ] @@ -220,7 +224,8 @@ class CachedCMakePackage(CMakePackage): """This method is to be overwritten by the package""" return [] - def initconfig(self, spec, prefix): + @spack.builder.run_before("cmake") + def initconfig(self): cache_entries = ( self.std_initconfig_entries() + self.initconfig_compiler_entries() @@ -236,11 +241,28 @@ class CachedCMakePackage(CMakePackage): @property def std_cmake_args(self): - args = super(CachedCMakePackage, self).std_cmake_args + args = super(CachedCMakeBuilder, self).std_cmake_args args.extend(["-C", self.cache_path]) return args - @run_after("install") + @spack.builder.run_after("install") def install_cmake_cache(self): - mkdirp(self.spec.prefix.share.cmake) - install(self.cache_path, self.spec.prefix.share.cmake) + fs.mkdirp(self.pkg.spec.prefix.share.cmake) + fs.install(self.cache_path, self.pkg.spec.prefix.share.cmake) + + +class CachedCMakePackage(CMakePackage): + """Specialized class for packages built using CMake initial cache. + + This feature of CMake allows packages to increase reproducibility, + especially between Spack- and manual builds. It also allows packages to + sidestep certain parsing bugs in extremely long ``cmake`` commands, and to + avoid system limits on the length of the command line. + """ + + CMakeBuilder = CachedCMakeBuilder + + def flag_handler(self, name, flags): + if name in ("cflags", "cxxflags", "cppflags", "fflags"): + return None, None, None # handled in the cmake cache + return flags, None, None diff --git a/lib/spack/spack/build_systems/cmake.py b/lib/spack/spack/build_systems/cmake.py index 6fb75b6747..8d8bba4778 100644 --- a/lib/spack/spack/build_systems/cmake.py +++ b/lib/spack/spack/build_systems/cmake.py @@ -2,23 +2,26 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect import os import platform import re import sys -from typing import List +from typing import List, Tuple import six +import llnl.util.filesystem as fs from llnl.util.compat import Sequence -from llnl.util.filesystem import working_dir import spack.build_environment -from spack.directives import conflicts, depends_on, variant -from spack.package_base import InstallError, PackageBase, run_after +import spack.builder +import spack.package_base +import spack.util.path +from spack.directives import build_system, depends_on, variant +from spack.multimethod import when + +from ._checks import BaseBuilder, execute_build_time_tests # Regex to extract the primary generator from the CMake generator # string. @@ -34,56 +37,141 @@ def _extract_primary_generator(generator): return primary_generator -class CMakePackage(PackageBase): +class CMakePackage(spack.package_base.PackageBase): """Specialized class for packages built using CMake For more information on the CMake build system, see: https://cmake.org/cmake/help/latest/ + """ + + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "CMakePackage" - This class provides three phases that can be overridden: + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "cmake" - 1. :py:meth:`~.CMakePackage.cmake` - 2. :py:meth:`~.CMakePackage.build` - 3. :py:meth:`~.CMakePackage.install` + build_system("cmake") + + with when("build_system=cmake"): + # 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"), + ) + # CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9 + # https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html + variant( + "ipo", + default=False, + when="^cmake@3.9:", + description="CMake interprocedural optimization", + ) + depends_on("cmake", type="build") + depends_on("ninja", type="build", when="platform=windows") + + def flags_to_build_system_args(self, flags): + """Return 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)) + + # Legacy methods (used by too many packages to change them, + # need to forward to the builder) + def define(self, *args, **kwargs): + return self.builder.define(*args, **kwargs) + + def define_from_variant(self, *args, **kwargs): + return self.builder.define_from_variant(*args, **kwargs) + + +@spack.builder.builder("cmake") +class CMakeBuilder(BaseBuilder): + """The cmake builder encodes the default way of building software with CMake. IT + has three phases that can be overridden: + + 1. :py:meth:`~.CMakeBuilder.cmake` + 2. :py:meth:`~.CMakeBuilder.build` + 3. :py:meth:`~.CMakeBuilder.install` They all have sensible defaults and for many packages the only thing - necessary will be to override :py:meth:`~.CMakePackage.cmake_args`. + necessary will be to override :py:meth:`~.CMakeBuilder.cmake_args`. + For a finer tuning you may also override: +-----------------------------------------------+--------------------+ | **Method** | **Purpose** | +===============================================+====================+ - | :py:meth:`~.CMakePackage.root_cmakelists_dir` | Location of the | + | :py:meth:`~.CMakeBuilder.root_cmakelists_dir` | Location of the | | | root CMakeLists.txt| +-----------------------------------------------+--------------------+ - | :py:meth:`~.CMakePackage.build_directory` | Directory where to | + | :py:meth:`~.CMakeBuilder.build_directory` | Directory where to | | | build the package | +-----------------------------------------------+--------------------+ - - The generator used by CMake can be specified by providing the - generator attribute. Per + The generator used by CMake can be specified by providing the ``generator`` + attribute. Per https://cmake.org/cmake/help/git-master/manual/cmake-generators.7.html, - the format is: [<secondary-generator> - ]<primary_generator>. The - full list of primary and secondary generators supported by CMake may - be found in the documentation for the version of CMake used; - however, at this time Spack supports only the primary generators - "Unix Makefiles" and "Ninja." Spack's CMake support is agnostic with - respect to primary generators. Spack will generate a runtime error - if the generator string does not follow the prescribed format, or if + the format is: [<secondary-generator> - ]<primary_generator>. + + The full list of primary and secondary generators supported by CMake may be found + in the documentation for the version of CMake used; however, at this time Spack + supports only the primary generators "Unix Makefiles" and "Ninja." Spack's CMake + support is agnostic with respect to primary generators. Spack will generate a + runtime error if the generator string does not follow the prescribed format, or if the primary generator is not supported. """ #: 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 = [] # type: List[str] - install_targets = ["install"] - - build_time_test_callbacks = ["check"] + phases = ("cmake", "build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("cmake_args", "check") # type: Tuple[str, ...] + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "generator", + "build_targets", + "install_targets", + "build_time_test_callbacks", + "archive_files", + "root_cmakelists_dir", + "std_cmake_args", + "build_dirname", + "build_directory", + ) # type: Tuple[str, ...] #: The build system generator to use. #: @@ -93,27 +181,14 @@ class CMakePackage(PackageBase): #: #: See https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html #: for more information. + generator = "Ninja" if sys.platform == "win32" else "Unix Makefiles" - generator = "Unix Makefiles" - - if sys.platform == "win32": - generator = "Ninja" - depends_on("ninja") - - # 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"), - ) - - # https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html - variant("ipo", default=False, description="CMake interprocedural optimization") - # CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9 - conflicts("+ipo", when="^cmake@:3.8", msg="+ipo is not supported by CMake < 3.9") - - depends_on("cmake", type="build") + #: Targets to be used during the build phase + build_targets = [] # type: List[str] + #: Targets to be used during the install phase + install_targets = ["install"] + #: Callback names for build-time test + build_time_test_callbacks = ["check"] @property def archive_files(self): @@ -126,40 +201,30 @@ class CMakePackage(PackageBase): 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 + return self.pkg.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 - std_cmake_args = CMakePackage._std_args(self) - std_cmake_args += getattr(self, "cmake_flag_args", []) + std_cmake_args = CMakeBuilder.std_args(self.pkg, generator=self.generator) + std_cmake_args += getattr(self.pkg, "cmake_flag_args", []) return std_cmake_args @staticmethod - def _std_args(pkg): + def std_args(pkg, generator=None): """Computes the standard cmake arguments for a generic package""" - - try: - generator = pkg.generator - except AttributeError: - generator = CMakePackage.generator - - # Make sure a valid generator was chosen + generator = generator or "Unix Makefiles" valid_primary_generators = ["Unix Makefiles", "Ninja"] primary_generator = _extract_primary_generator(generator) if primary_generator not in valid_primary_generators: msg = "Invalid CMake generator: '{0}'\n".format(generator) msg += "CMakePackage currently supports the following " msg += "primary generators: '{0}'".format("', '".join(valid_primary_generators)) - raise InstallError(msg) + raise spack.package_base.InstallError(msg) try: build_type = pkg.spec.variants["build_type"].value @@ -171,7 +236,7 @@ class CMakePackage(PackageBase): except KeyError: ipo = False - define = CMakePackage.define + define = CMakeBuilder.define args = [ "-G", generator, @@ -251,7 +316,7 @@ class CMakePackage(PackageBase): of ``cmake_var``. This utility function is similar to - :meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without`. + :meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without`. Examples: @@ -291,122 +356,75 @@ class CMakePackage(PackageBase): if variant is None: variant = cmake_var.lower() - if variant not in self.variants: - raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.name)) + if variant not in self.pkg.variants: + raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.pkg.name)) - if variant not in self.spec.variants: + if variant not in self.pkg.spec.variants: return "" - value = self.spec.variants[variant].value + value = self.pkg.spec.variants[variant].value if isinstance(value, (tuple, list)): # Sort multi-valued variants for reproducibility value = sorted(value) return self.define(cmake_var, value) - 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_dirname(self): - """Returns the directory name to use when building the package - - :return: name of the subdirectory for building the package - """ - return "spack-build-%s" % self.spec.dag_hash(7) + """Directory name to use when building the package.""" + return "spack-build-%s" % self.pkg.spec.dag_hash(7) @property def build_directory(self): - """Returns the directory to use when building the package - - :return: directory where to build the package - """ - return os.path.join(self.stage.path, self.build_dirname) + """Full-path to the directory to use when building the package.""" + return os.path.join(self.pkg.stage.path, self.build_dirname) def cmake_args(self): - """Produces a list containing all the arguments that must be passed to - cmake, except: + """List of all the arguments that must be passed to cmake, except: * CMAKE_INSTALL_PREFIX * CMAKE_BUILD_TYPE * BUILD_TESTING which will be set automatically. - - :return: list of arguments for cmake """ return [] - def cmake(self, spec, prefix): + def cmake(self, pkg, spec, prefix): """Runs ``cmake`` in the build directory""" options = self.std_cmake_args options += self.cmake_args() options.append(os.path.abspath(self.root_cmakelists_dir)) - with working_dir(self.build_directory, create=True): - inspect.getmodule(self).cmake(*options) + with fs.working_dir(self.build_directory, create=True): + inspect.getmodule(self.pkg).cmake(*options) - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Make the build targets""" - with working_dir(self.build_directory): + with fs.working_dir(self.build_directory): if self.generator == "Unix Makefiles": - inspect.getmodule(self).make(*self.build_targets) + inspect.getmodule(self.pkg).make(*self.build_targets) elif self.generator == "Ninja": self.build_targets.append("-v") - inspect.getmodule(self).ninja(*self.build_targets) + inspect.getmodule(self.pkg).ninja(*self.build_targets) - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Make the install targets""" - with working_dir(self.build_directory): + with fs.working_dir(self.build_directory): if self.generator == "Unix Makefiles": - inspect.getmodule(self).make(*self.install_targets) + inspect.getmodule(self.pkg).make(*self.install_targets) elif self.generator == "Ninja": - inspect.getmodule(self).ninja(*self.install_targets) + inspect.getmodule(self.pkg).ninja(*self.install_targets) - run_after("build")(PackageBase._run_default_build_time_test_callbacks) + spack.builder.run_after("build")(execute_build_time_tests) def check(self): - """Searches the CMake-generated Makefile for the target ``test`` - and runs it if found. + """Search the CMake-generated files for the targets ``test`` and ``check``, + and runs them if found. """ - with working_dir(self.build_directory): + with fs.working_dir(self.build_directory): if self.generator == "Unix Makefiles": - self._if_make_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL") - self._if_make_target_execute("check") + self.pkg._if_make_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL") + self.pkg._if_make_target_execute("check") elif self.generator == "Ninja": - self._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL") - self._if_ninja_target_execute("check") - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) + self.pkg._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL") + self.pkg._if_ninja_target_execute("check") diff --git a/lib/spack/spack/build_systems/generic.py b/lib/spack/spack/build_systems/generic.py new file mode 100644 index 0000000000..628af6f2d4 --- /dev/null +++ b/lib/spack/spack/build_systems/generic.py @@ -0,0 +1,44 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +from typing import Tuple + +import spack.builder +import spack.directives +import spack.package_base + +from ._checks import BaseBuilder, apply_macos_rpath_fixups + + +class Package(spack.package_base.PackageBase): + """General purpose class with a single ``install`` phase that needs to be + coded by packagers. + """ + + #: This attribute is used in UI queries that require to know which + #: build-system class we are using + build_system_class = "Package" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "generic" + + spack.directives.build_system("generic") + + +@spack.builder.builder("generic") +class GenericBuilder(BaseBuilder): + """A builder for a generic build system, that require packagers + to implement an "install" phase. + """ + + #: A generic package has only the "install" phase + phases = ("install",) + + #: Names associated with package methods in the old build-system format + legacy_methods = () # type: Tuple[str, ...] + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ("archive_files",) # type: Tuple[str, ...] + + # On macOS, force rpaths for shared library IDs and remove duplicate rpaths + spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) diff --git a/lib/spack/spack/build_systems/intel.py b/lib/spack/spack/build_systems/intel.py index 133b5030de..4abed70f21 100644 --- a/lib/spack/spack/build_systems/intel.py +++ b/lib/spack/spack/build_systems/intel.py @@ -2,8 +2,6 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import glob import inspect import os @@ -26,12 +24,14 @@ from llnl.util.filesystem import ( import spack.error from spack.build_environment import dso_suffix -from spack.package_base import InstallError, PackageBase, run_after +from spack.package_base import InstallError from spack.util.environment import EnvironmentModifications from spack.util.executable import Executable from spack.util.prefix import Prefix from spack.version import Version, ver +from .generic import Package + # A couple of utility functions that might be useful in general. If so, they # should really be defined elsewhere, unless deemed heretical. # (Or na"ive on my part). @@ -86,7 +86,7 @@ def _expand_fields(s): return s -class IntelPackage(PackageBase): +class IntelPackage(Package): """Specialized class for licensed Intel software. This class provides two phases that can be overridden: @@ -99,9 +99,6 @@ class IntelPackage(PackageBase): to set the appropriate environment variables. """ - #: Phases of an Intel package - phases = ["configure", "install"] - #: This attribute is used in UI queries that need to know the build #: system base class build_system_class = "IntelPackage" @@ -1184,12 +1181,13 @@ class IntelPackage(PackageBase): debug_print(license_type) return license_type - def configure(self, spec, prefix): + @spack.builder.run_before("install") + def configure(self): """Generates the silent.cfg file to pass to installer.sh. See https://software.intel.com/en-us/articles/configuration-file-format """ - + prefix = self.prefix # Both tokens AND values of the configuration file are validated during # the run of the underlying binary installer. Any unknown token or # unacceptable value will cause that installer to fail. Notably, this @@ -1270,7 +1268,7 @@ class IntelPackage(PackageBase): for f in glob.glob("%s/intel*log" % tmpdir): install(f, dst) - @run_after("install") + @spack.builder.run_after("install") def validate_install(self): # Sometimes the installer exits with an error but doesn't pass a # non-zero exit code to spack. Check for the existence of a 'bin' @@ -1278,7 +1276,7 @@ class IntelPackage(PackageBase): if not os.path.exists(self.prefix.bin): raise InstallError("The installer has failed to install anything.") - @run_after("install") + @spack.builder.run_after("install") def configure_rpath(self): if "+rpath" not in self.spec: return @@ -1296,7 +1294,7 @@ class IntelPackage(PackageBase): with open(compiler_cfg, "w") as fh: fh.write("-Xlinker -rpath={0}\n".format(compilers_lib_dir)) - @run_after("install") + @spack.builder.run_after("install") def configure_auto_dispatch(self): if self._has_compilers: if "auto_dispatch=none" in self.spec: @@ -1320,7 +1318,7 @@ class IntelPackage(PackageBase): with open(compiler_cfg, "a") as fh: fh.write("-ax{0}\n".format(",".join(ad))) - @run_after("install") + @spack.builder.run_after("install") def filter_compiler_wrappers(self): if ("+mpi" in self.spec or self.provides("mpi")) and "~newdtags" in self.spec: bin_dir = self.component_bin_dir("mpi") @@ -1328,7 +1326,7 @@ class IntelPackage(PackageBase): f = os.path.join(bin_dir, f) filter_file("-Xlinker --enable-new-dtags", " ", f, string=True) - @run_after("install") + @spack.builder.run_after("install") def uninstall_ism(self): # The "Intel(R) Software Improvement Program" [ahem] gets installed, # apparently regardless of PHONEHOME_SEND_USAGE_DATA. @@ -1360,7 +1358,7 @@ class IntelPackage(PackageBase): debug_print(d) return d - @run_after("install") + @spack.builder.run_after("install") def modify_LLVMgold_rpath(self): """Add libimf.so and other required libraries to the RUNPATH of LLVMgold.so. @@ -1391,6 +1389,3 @@ class IntelPackage(PackageBase): ] ) patchelf("--set-rpath", rpath, lib) - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/lua.py b/lib/spack/spack/build_systems/lua.py index c0d4321097..48fa106881 100644 --- a/lib/spack/spack/build_systems/lua.py +++ b/lib/spack/spack/build_systems/lua.py @@ -2,59 +2,79 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import os from llnl.util.filesystem import find -from spack.directives import depends_on, extends +import spack.builder +import spack.package_base +import spack.util.executable +from spack.directives import build_system, depends_on, extends from spack.multimethod import when -from spack.package_base import PackageBase -from spack.util.executable import Executable -class LuaPackage(PackageBase): +class LuaPackage(spack.package_base.PackageBase): """Specialized class for lua packages""" - phases = ["unpack", "generate_luarocks_config", "preprocess", "install"] #: This attribute is used in UI queries that need to know the build #: system base class build_system_class = "LuaPackage" list_depth = 1 # LuaRocks requires at least one level of spidering to find versions - depends_on("lua-lang") - extends("lua", when="^lua") - with when("^lua-luajit"): - extends("lua-luajit") - depends_on("luajit") - depends_on("lua-luajit+lualinks") - with when("^lua-luajit-openresty"): - extends("lua-luajit-openresty") - depends_on("luajit") - depends_on("lua-luajit-openresty+lualinks") - - def unpack(self, spec, prefix): - if os.path.splitext(self.stage.archive_file)[1] == ".rock": - directory = self.luarocks("unpack", self.stage.archive_file, output=str) + + build_system("lua") + + with when("build_system=lua"): + depends_on("lua-lang") + extends("lua", when="^lua") + with when("^lua-luajit"): + extends("lua-luajit") + depends_on("luajit") + depends_on("lua-luajit+lualinks") + with when("^lua-luajit-openresty"): + extends("lua-luajit-openresty") + depends_on("luajit") + depends_on("lua-luajit-openresty+lualinks") + + @property + def lua(self): + return spack.util.executable.Executable(self.spec["lua-lang"].prefix.bin.lua) + + @property + def luarocks(self): + lr = spack.util.executable.Executable(self.spec["lua-lang"].prefix.bin.luarocks) + return lr + + +@spack.builder.builder("lua") +class LuaBuilder(spack.builder.Builder): + phases = ("unpack", "generate_luarocks_config", "preprocess", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("luarocks_args",) + + #: Names associated with package attributes in the old build-system format + legacy_attributes = () + + def unpack(self, pkg, spec, prefix): + if os.path.splitext(pkg.stage.archive_file)[1] == ".rock": + directory = pkg.luarocks("unpack", pkg.stage.archive_file, output=str) dirlines = directory.split("\n") # TODO: figure out how to scope this better os.chdir(dirlines[2]) - def _generate_tree_line(self, name, prefix): + @staticmethod + def _generate_tree_line(name, prefix): return """{{ name = "{name}", root = "{prefix}" }};""".format( name=name, prefix=prefix, ) - def _luarocks_config_path(self): - return os.path.join(self.stage.source_path, "spack_luarocks.lua") - - def generate_luarocks_config(self, spec, prefix): - spec = self.spec + def generate_luarocks_config(self, pkg, spec, prefix): + spec = self.pkg.spec table_entries = [] for d in spec.traverse(deptypes=("build", "run"), deptype_query="run"): - if d.package.extends(self.extendee_spec): + if d.package.extends(self.pkg.extendee_spec): table_entries.append(self._generate_tree_line(d.name, d.prefix)) path = self._luarocks_config_path() @@ -71,30 +91,24 @@ class LuaPackage(PackageBase): ) return path - def setup_build_environment(self, env): - env.set("LUAROCKS_CONFIG", self._luarocks_config_path()) - - def preprocess(self, spec, prefix): + def preprocess(self, pkg, spec, prefix): """Override this to preprocess source before building with luarocks""" pass - @property - def lua(self): - return Executable(self.spec["lua-lang"].prefix.bin.lua) - - @property - def luarocks(self): - lr = Executable(self.spec["lua-lang"].prefix.bin.luarocks) - return lr - def luarocks_args(self): return [] - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): rock = "." specs = find(".", "*.rockspec", recursive=False) if specs: rock = specs[0] rocks_args = self.luarocks_args() rocks_args.append(rock) - self.luarocks("--tree=" + prefix, "make", *rocks_args) + self.pkg.luarocks("--tree=" + prefix, "make", *rocks_args) + + def _luarocks_config_path(self): + return os.path.join(self.pkg.stage.source_path, "spack_luarocks.lua") + + def setup_build_environment(self, env): + env.set("LUAROCKS_CONFIG", self._luarocks_config_path()) diff --git a/lib/spack/spack/build_systems/makefile.py b/lib/spack/spack/build_systems/makefile.py index e2bb8c0c26..b826144258 100644 --- a/lib/spack/spack/build_systems/makefile.py +++ b/lib/spack/spack/build_systems/makefile.py @@ -2,62 +2,85 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect from typing import List # novm -import llnl.util.tty as tty -from llnl.util.filesystem import working_dir +import llnl.util.filesystem as fs + +import spack.builder +import spack.package_base +from spack.directives import build_system, conflicts -from spack.directives import conflicts -from spack.package_base import PackageBase, run_after +from ._checks import ( + BaseBuilder, + apply_macos_rpath_fixups, + execute_build_time_tests, + execute_install_time_tests, +) -class MakefilePackage(PackageBase): - """Specialized class for packages that are built using editable Makefiles +class MakefilePackage(spack.package_base.PackageBase): + """Specialized class for packages built using a Makefiles.""" - This class provides three phases that can be overridden: + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "MakefilePackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "makefile" - 1. :py:meth:`~.MakefilePackage.edit` - 2. :py:meth:`~.MakefilePackage.build` - 3. :py:meth:`~.MakefilePackage.install` + build_system("makefile") + conflicts("platform=windows", when="build_system=makefile") + + +@spack.builder.builder("makefile") +class MakefileBuilder(BaseBuilder): + """The Makefile builder encodes the most common way of building software with + Makefiles. It has three phases that can be overridden, if need be: + + 1. :py:meth:`~.MakefileBuilder.edit` + 2. :py:meth:`~.MakefileBuilder.build` + 3. :py:meth:`~.MakefileBuilder.install` + + It is usually necessary to override the :py:meth:`~.MakefileBuilder.edit` + phase (which is by default a no-op), while the other two have sensible defaults. - 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`` | + | :py:attr:`~.MakefileBuilder.build_targets` | Specify ``make`` | | | targets for the | | | build phase | +-----------------------------------------------+--------------------+ - | :py:attr:`~.MakefilePackage.install_targets` | Specify ``make`` | + | :py:attr:`~.MakefileBuilder.install_targets` | Specify ``make`` | | | targets for the | | | install phase | +-----------------------------------------------+--------------------+ - | :py:meth:`~.MakefilePackage.build_directory` | Directory where the| + | :py:meth:`~.MakefileBuilder.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" + phases = ("edit", "build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("check", "installcheck") - #: Targets for ``make`` during the :py:meth:`~.MakefilePackage.build` - #: phase + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "build_targets", + "install_targets", + "build_time_test_callbacks", + "install_time_test_callbacks", + "build_directory", + ) + + #: Targets for ``make`` during the :py:meth:`~.MakefileBuilder.build` phase build_targets = [] # type: List[str] - #: Targets for ``make`` during the :py:meth:`~.MakefilePackage.install` - #: phase + #: Targets for ``make`` during the :py:meth:`~.MakefileBuilder.install` phase install_targets = ["install"] - conflicts("platform=windows") #: Callback names for build-time test build_time_test_callbacks = ["check"] @@ -66,53 +89,39 @@ class MakefilePackage(PackageBase): @property def build_directory(self): - """Returns the directory containing the main Makefile + """Return the directory containing the main Makefile.""" + return self.pkg.stage.source_path - :return: build directory - """ - return self.stage.source_path + def edit(self, pkg, spec, prefix): + """Edit the Makefile before calling make. The default is a no-op.""" + pass - 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, pkg, spec, prefix): + """Run "make" on the build targets specified by the builder.""" + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).make(*self.build_targets) - 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) + def install(self, pkg, spec, prefix): + """Run "make" on the install targets specified by the builder.""" + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).make(*self.install_targets) - run_after("build")(PackageBase._run_default_build_time_test_callbacks) + spack.builder.run_after("build")(execute_build_time_tests) 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 "make" on the ``test`` and ``check`` targets, if found.""" + with fs.working_dir(self.build_directory): + self.pkg._if_make_target_execute("test") + self.pkg._if_make_target_execute("check") - run_after("install")(PackageBase._run_default_install_time_test_callbacks) + spack.builder.run_after("install")(execute_install_time_tests) 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) + with fs.working_dir(self.build_directory): + self.pkg._if_make_target_execute("installcheck") # On macOS, force rpaths for shared library IDs and remove duplicate rpaths - run_after("install")(PackageBase.apply_macos_rpath_fixups) + spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) diff --git a/lib/spack/spack/build_systems/maven.py b/lib/spack/spack/build_systems/maven.py index 1ff1882e13..66680b5b6c 100644 --- a/lib/spack/spack/build_systems/maven.py +++ b/lib/spack/spack/build_systems/maven.py @@ -2,60 +2,73 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +import llnl.util.filesystem as fs - -from llnl.util.filesystem import install_tree, working_dir - -from spack.directives import depends_on -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, depends_on +from spack.multimethod import when from spack.util.executable import which +from ._checks import BaseBuilder + -class MavenPackage(PackageBase): +class MavenPackage(spack.package_base.PackageBase): """Specialized class for packages that are built using the Maven build system. See https://maven.apache.org/index.html for more information. - - This class provides the following phases that can be overridden: - - * build - * install """ - # Default phases - phases = ["build", "install"] - # To be used in UI queries that require to know which # build-system class we are using build_system_class = "MavenPackage" - depends_on("java", type=("build", "run")) - depends_on("maven", type="build") + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "maven" + + build_system("maven") + + with when("build_system=maven"): + depends_on("java", type=("build", "run")) + depends_on("maven", type="build") + + +@spack.builder.builder("maven") +class MavenBuilder(BaseBuilder): + """The Maven builder encodes the default way to build software with Maven. + It has two phases that can be overridden, if need be: + + 1. :py:meth:`~.MavenBuilder.build` + 2. :py:meth:`~.MavenBuilder.install` + """ + + phases = ("build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("build_args",) + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ("build_directory",) @property def build_directory(self): """The directory containing the ``pom.xml`` file.""" - return self.stage.source_path + return self.pkg.stage.source_path def build_args(self): """List of args to pass to build phase.""" return [] - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Compile code and package into a JAR file.""" - - with working_dir(self.build_directory): + with fs.working_dir(self.build_directory): mvn = which("mvn") - if self.run_tests: + if self.pkg.run_tests: mvn("verify", *self.build_args()) else: mvn("package", "-DskipTests", *self.build_args()) - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Copy to installation prefix.""" - - with working_dir(self.build_directory): - install_tree(".", prefix) - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) + with fs.working_dir(self.build_directory): + fs.install_tree(".", prefix) diff --git a/lib/spack/spack/build_systems/meson.py b/lib/spack/spack/build_systems/meson.py index cafd613808..6ab760cfb9 100644 --- a/lib/spack/spack/build_systems/meson.py +++ b/lib/spack/spack/build_systems/meson.py @@ -2,76 +2,104 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect import os from typing import List # novm -from llnl.util.filesystem import working_dir +import llnl.util.filesystem as fs -from spack.directives import depends_on, variant -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, depends_on, variant +from spack.multimethod import when +from ._checks import BaseBuilder, execute_build_time_tests -class MesonPackage(PackageBase): - """Specialized class for packages built using Meson - For more information on the Meson build system, see: - https://mesonbuild.com/ +class MesonPackage(spack.package_base.PackageBase): + """Specialized class for packages built using Meson. For more information + on the Meson build system, see https://mesonbuild.com/ + """ - This class provides three phases that can be overridden: + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "MesonPackage" - 1. :py:meth:`~.MesonPackage.meson` - 2. :py:meth:`~.MesonPackage.build` - 3. :py:meth:`~.MesonPackage.install` + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "meson" + + build_system("meson") + + with when("build_system=meson"): + variant( + "buildtype", + default="debugoptimized", + description="Meson build type", + values=("plain", "debug", "debugoptimized", "release", "minsize"), + ) + variant( + "default_library", + default="shared", + values=("shared", "static"), + multi=True, + description="Build shared libs, static libs or both", + ) + variant("strip", default=False, description="Strip targets on install") + depends_on("meson", type="build") + depends_on("ninja", type="build") + + def flags_to_build_system_args(self, flags): + """Produces a list of all command line arguments to pass the specified + compiler flags to meson.""" + # Has to be dynamic attribute due to caching + setattr(self, "meson_flag_args", []) + + +@spack.builder.builder("meson") +class MesonBuilder(BaseBuilder): + """The Meson builder encodes the default way to build software with Meson. + The builder has three phases that can be overridden, if need be: + + 1. :py:meth:`~.MesonBuilder.meson` + 2. :py:meth:`~.MesonBuilder.build` + 3. :py:meth:`~.MesonBuilder.install` They all have sensible defaults and for many packages the only thing - necessary will be to override :py:meth:`~.MesonPackage.meson_args`. + necessary will be to override :py:meth:`~.MesonBuilder.meson_args`. + For a finer tuning you may also override: +-----------------------------------------------+--------------------+ | **Method** | **Purpose** | +===============================================+====================+ - | :py:meth:`~.MesonPackage.root_mesonlists_dir` | Location of the | + | :py:meth:`~.MesonBuilder.root_mesonlists_dir` | Location of the | | | root MesonLists.txt| +-----------------------------------------------+--------------------+ - | :py:meth:`~.MesonPackage.build_directory` | Directory where to | + | :py:meth:`~.MesonBuilder.build_directory` | Directory where to | | | build the package | +-----------------------------------------------+--------------------+ + """ + phases = ("meson", "build", "install") - """ + #: Names associated with package methods in the old build-system format + legacy_methods = ("meson_args", "check") - #: Phases of a Meson package - phases = ["meson", "build", "install"] - #: This attribute is used in UI queries that need to know the build - #: system base class - build_system_class = "MesonPackage" + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "build_targets", + "install_targets", + "build_time_test_callbacks", + "root_mesonlists_dir", + "std_meson_args", + "build_directory", + ) build_targets = [] # type: List[str] install_targets = ["install"] build_time_test_callbacks = ["check"] - variant( - "buildtype", - default="debugoptimized", - description="Meson build type", - values=("plain", "debug", "debugoptimized", "release", "minsize"), - ) - variant( - "default_library", - default="shared", - values=("shared", "static"), - multi=True, - description="Build shared libs, static libs or both", - ) - variant("strip", default=False, description="Strip targets on install") - - depends_on("meson", type="build") - depends_on("ninja", type="build") - @property def archive_files(self): """Files to archive for packages based on Meson""" @@ -79,31 +107,26 @@ class MesonPackage(PackageBase): @property def root_mesonlists_dir(self): - """The relative path to the directory containing meson.build + """Relative path to the directory containing meson.build This path is relative to the root of the extracted tarball, not to the ``build_directory``. Defaults to the current directory. - - :return: directory containing meson.build """ - return self.stage.source_path + return self.pkg.stage.source_path @property def std_meson_args(self): - """Standard meson arguments provided as a property for - convenience of package writers - - :return: standard meson arguments + """Standard meson arguments provided as a property for convenience + of package writers. """ # standard Meson arguments - std_meson_args = MesonPackage._std_args(self) + std_meson_args = MesonBuilder.std_args(self.pkg) std_meson_args += getattr(self, "meson_flag_args", []) return std_meson_args @staticmethod - def _std_args(pkg): - """Computes the standard meson arguments for a generic package""" - + def std_args(pkg): + """Standard meson arguments for a generic package.""" try: build_type = pkg.spec.variants["buildtype"].value except KeyError: @@ -132,31 +155,18 @@ class MesonPackage(PackageBase): 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 meson.""" - # Has to be dynamic attribute due to caching - setattr(self, "meson_flag_args", []) - @property def build_dirname(self): - """Returns the directory name to use when building the package - - :return: name of the subdirectory for building the package - """ - return "spack-build-%s" % self.spec.dag_hash(7) + """Returns the directory name to use when building the package.""" + return "spack-build-{}".format(self.spec.dag_hash(7)) @property def build_directory(self): - """Returns the directory to use when building the package - - :return: directory where to build the package - """ - return os.path.join(self.stage.path, self.build_dirname) + """Directory to use when building the package.""" + return os.path.join(self.pkg.stage.path, self.build_dirname) def meson_args(self): - """Produces a list containing all the arguments that must be passed to - meson, except: + """List of arguments that must be passed to meson, except: * ``--prefix`` * ``--libdir`` @@ -165,40 +175,33 @@ class MesonPackage(PackageBase): * ``--default_library`` which will be set automatically. - - :return: list of arguments for meson """ return [] - def meson(self, spec, prefix): - """Runs ``meson`` in the build directory""" + def meson(self, pkg, spec, prefix): + """Run ``meson`` in the build directory""" options = [os.path.abspath(self.root_mesonlists_dir)] options += self.std_meson_args options += self.meson_args() - with working_dir(self.build_directory, create=True): - inspect.getmodule(self).meson(*options) + with fs.working_dir(self.build_directory, create=True): + inspect.getmodule(self.pkg).meson(*options) - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Make the build targets""" options = ["-v"] options += self.build_targets - with working_dir(self.build_directory): - inspect.getmodule(self).ninja(*options) + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).ninja(*options) - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Make the install targets""" - with working_dir(self.build_directory): - inspect.getmodule(self).ninja(*self.install_targets) + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).ninja(*self.install_targets) - run_after("build")(PackageBase._run_default_build_time_test_callbacks) + spack.builder.run_after("build")(execute_build_time_tests) def check(self): - """Searches the Meson-generated file for the target ``test`` - and runs it if found. - """ - with working_dir(self.build_directory): + """Search Meson-generated files for the target ``test`` and run it if found.""" + with fs.working_dir(self.build_directory): self._if_ninja_target_execute("test") self._if_ninja_target_execute("check") - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/nmake.py b/lib/spack/spack/build_systems/nmake.py new file mode 100644 index 0000000000..bf36895160 --- /dev/null +++ b/lib/spack/spack/build_systems/nmake.py @@ -0,0 +1,102 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import inspect +from typing import List # novm + +import llnl.util.filesystem as fs + +import spack.builder +import spack.package_base +from spack.directives import build_system, conflicts + +from ._checks import BaseBuilder + + +class NMakePackage(spack.package_base.PackageBase): + """Specialized class for packages built using a Makefiles.""" + + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "NmakePackage" + + build_system("nmake") + conflicts("platform=linux", when="build_system=nmake") + conflicts("platform=darwin", when="build_system=nmake") + conflicts("platform=cray", when="build_system=nmake") + + +@spack.builder.builder("nmake") +class NMakeBuilder(BaseBuilder): + """The NMake builder encodes the most common way of building software with + NMake on Windows. It has three phases that can be overridden, if need be: + + 1. :py:meth:`~.NMakeBuilder.edit` + 2. :py:meth:`~.NMakeBuilder.build` + 3. :py:meth:`~.NMakeBuilder.install` + + It is usually necessary to override the :py:meth:`~.NMakeBuilder.edit` + phase (which is by default a no-op), while the other two have sensible defaults. + + For a finer tuning you may override: + + +--------------------------------------------+--------------------+ + | **Method** | **Purpose** | + +============================================+====================+ + | :py:attr:`~.NMakeBuilder.build_targets` | Specify ``nmake`` | + | | targets for the | + | | build phase | + +--------------------------------------------+--------------------+ + | :py:attr:`~.NMakeBuilder.install_targets` | Specify ``nmake`` | + | | targets for the | + | | install phase | + +--------------------------------------------+--------------------+ + | :py:meth:`~.NMakeBuilder.build_directory` | Directory where the| + | | Makefile is located| + +--------------------------------------------+--------------------+ + """ + + phases = ("edit", "build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("check", "installcheck") + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "build_targets", + "install_targets", + "build_time_test_callbacks", + "install_time_test_callbacks", + "build_directory", + ) + + #: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.build` phase + build_targets = [] # type: List[str] + #: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.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): + """Return the directory containing the main Makefile.""" + return self.pkg.stage.source_path + + def edit(self, pkg, spec, prefix): + """Edit the Makefile before calling make. The default is a no-op.""" + pass + + def build(self, pkg, spec, prefix): + """Run "make" on the build targets specified by the builder.""" + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).nmake(*self.build_targets) + + def install(self, pkg, spec, prefix): + """Run "make" on the install targets specified by the builder.""" + with fs.working_dir(self.build_directory): + inspect.getmodule(self.pkg).nmake(*self.install_targets) diff --git a/lib/spack/spack/build_systems/octave.py b/lib/spack/spack/build_systems/octave.py index 9916c319b0..5b2456f098 100644 --- a/lib/spack/spack/build_systems/octave.py +++ b/lib/spack/spack/build_systems/octave.py @@ -2,51 +2,62 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import inspect -from spack.directives import extends -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, extends +from spack.multimethod import when + +from ._checks import BaseBuilder -class OctavePackage(PackageBase): +class OctavePackage(spack.package_base.PackageBase): """Specialized class for Octave packages. See https://www.gnu.org/software/octave/doc/v4.2.0/Installing-and-Removing-Packages.html for more information. - - This class provides the following phases that can be overridden: - - 1. :py:meth:`~.OctavePackage.install` - """ - # Default phases - phases = ["install"] - # To be used in UI queries that require to know which # build-system class we are using build_system_class = "OctavePackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "octave" - extends("octave") + build_system("octave") + + with when("build_system=octave"): + extends("octave") - def setup_build_environment(self, env): - # octave does not like those environment variables to be set: - env.unset("CC") - env.unset("CXX") - env.unset("FC") - def install(self, spec, prefix): +@spack.builder.builder("octave") +class OctaveBuilder(BaseBuilder): + """The octave builder provides the following phases that can be overridden: + + 1. :py:meth:`~.OctaveBuilder.install` + """ + + phases = ("install",) + + #: Names associated with package methods in the old build-system format + legacy_methods = () + + #: Names associated with package attributes in the old build-system format + legacy_attributes = () + + def install(self, pkg, spec, prefix): """Install the package from the archive file""" - inspect.getmodule(self).octave( + inspect.getmodule(self.pkg).octave( "--quiet", "--norc", "--built-in-docstrings-file=/dev/null", "--texi-macros-file=/dev/null", "--eval", - "pkg prefix %s; pkg install %s" % (prefix, self.stage.archive_file), + "pkg prefix %s; pkg install %s" % (prefix, self.pkg.stage.archive_file), ) - # Testing - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) + def setup_build_environment(self, env): + # octave does not like those environment variables to be set: + env.unset("CC") + env.unset("CXX") + env.unset("FC") diff --git a/lib/spack/spack/build_systems/oneapi.py b/lib/spack/spack/build_systems/oneapi.py index 669c66fe8f..1cb79b9901 100644 --- a/lib/spack/spack/build_systems/oneapi.py +++ b/lib/spack/spack/build_systems/oneapi.py @@ -2,11 +2,7 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - -"""Common utilities for managing intel oneapi packages. - -""" - +"""Common utilities for managing intel oneapi packages.""" import getpass import platform import shutil @@ -14,18 +10,17 @@ from os.path import basename, dirname, isdir from llnl.util.filesystem import find_headers, find_libraries, join_path -from spack.package_base import Package from spack.util.environment import EnvironmentModifications from spack.util.executable import Executable +from .generic import Package + class IntelOneApiPackage(Package): """Base class for Intel oneAPI packages.""" homepage = "https://software.intel.com/oneapi" - phases = ["install"] - # oneAPI license does not allow mirroring outside of the # organization (e.g. University/Company). redistribute_source = False diff --git a/lib/spack/spack/build_systems/perl.py b/lib/spack/spack/build_systems/perl.py index 1f354beece..a100b89bfd 100644 --- a/lib/spack/spack/build_systems/perl.py +++ b/lib/spack/spack/build_systems/perl.py @@ -2,73 +2,87 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect import os from llnl.util.filesystem import filter_file -from spack.directives import extends -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, extends +from spack.package_base import PackageBase from spack.util.executable import Executable +from ._checks import BaseBuilder, execute_build_time_tests + class PerlPackage(PackageBase): - """Specialized class for packages that are built using Perl. + """Specialized class for packages that are built using Perl.""" + + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "PerlPackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "perl" + + build_system("perl") + + extends("perl", when="build_system=perl") - This class provides four phases that can be overridden if required: - 1. :py:meth:`~.PerlPackage.configure` - 2. :py:meth:`~.PerlPackage.build` - 3. :py:meth:`~.PerlPackage.check` - 4. :py:meth:`~.PerlPackage.install` +@spack.builder.builder("perl") +class PerlBuilder(BaseBuilder): + """The perl builder provides four phases that can be overridden, if required: + + 1. :py:meth:`~.PerlBuilder.configure` + 2. :py:meth:`~.PerlBuilder.build` + 3. :py:meth:`~.PerlBuilder.check` + 4. :py:meth:`~.PerlBuilder.install` The default methods use, in order of preference: (1) Makefile.PL, (2) Build.PL. - Some packages may need to override - :py:meth:`~.PerlPackage.configure_args`, - which produces a list of arguments for - :py:meth:`~.PerlPackage.configure`. + Some packages may need to override :py:meth:`~.PerlBuilder.configure_args`, + which produces a list of arguments for :py:meth:`~.PerlBuilder.configure`. + Arguments should not include the installation base directory. """ #: Phases of a Perl package - phases = ["configure", "build", "install"] + phases = ("configure", "build", "install") - #: This attribute is used in UI queries that need to know the build - #: system base class - build_system_class = "PerlPackage" + #: Names associated with package methods in the old build-system format + legacy_methods = ("configure_args", "check") + + #: Names associated with package attributes in the old build-system format + legacy_attributes = () #: Callback names for build-time test build_time_test_callbacks = ["check"] - extends("perl") - def configure_args(self): - """Produces a list containing the arguments that must be passed to - :py:meth:`~.PerlPackage.configure`. Arguments should not include - the installation base directory, which is prepended automatically. + """List of arguments passed to :py:meth:`~.PerlBuilder.configure`. - :return: list of arguments for Makefile.PL or Build.PL + Arguments should not include the installation base directory, which + is prepended automatically. """ return [] - def configure(self, spec, prefix): - """Runs Makefile.PL or Build.PL with arguments consisting of + def configure(self, pkg, spec, prefix): + """Run Makefile.PL or Build.PL with arguments consisting of an appropriate installation base directory followed by the - list returned by :py:meth:`~.PerlPackage.configure_args`. + list returned by :py:meth:`~.PerlBuilder.configure_args`. - :raise RuntimeError: if neither Makefile.PL or Build.PL exist + Raises: + RuntimeError: if neither Makefile.PL nor Build.PL exist """ if os.path.isfile("Makefile.PL"): self.build_method = "Makefile.PL" - self.build_executable = inspect.getmodule(self).make + self.build_executable = inspect.getmodule(self.pkg).make elif os.path.isfile("Build.PL"): self.build_method = "Build.PL" - self.build_executable = Executable(os.path.join(self.stage.source_path, "Build")) + self.build_executable = Executable(os.path.join(self.pkg.stage.source_path, "Build")) else: raise RuntimeError("Unknown build_method for perl package") @@ -78,33 +92,30 @@ class PerlPackage(PackageBase): options = ["Build.PL", "--install_base", prefix] options += self.configure_args() - inspect.getmodule(self).perl(*options) + inspect.getmodule(self.pkg).perl(*options) # It is possible that the shebang in the Build script that is created from # Build.PL may be too long causing the build to fail. Patching the shebang # does not happen until after install so set '/usr/bin/env perl' here in # the Build script. - @run_after("configure") + @spack.builder.run_after("configure") def fix_shebang(self): if self.build_method == "Build.PL": pattern = "#!{0}".format(self.spec["perl"].command.path) repl = "#!/usr/bin/env perl" filter_file(pattern, repl, "Build", backup=False) - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Builds a Perl package.""" self.build_executable() # Ensure that tests run after build (if requested): - run_after("build")(PackageBase._run_default_build_time_test_callbacks) + spack.builder.run_after("build")(execute_build_time_tests) def check(self): """Runs built-in tests of a Perl package.""" self.build_executable("test") - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Installs a Perl package.""" self.build_executable("install") - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py index d1cecdac63..0f84ac7f8a 100644 --- a/lib/spack/spack/build_systems/python.py +++ b/lib/spack/spack/build_systems/python.py @@ -8,93 +8,22 @@ import re import shutil from typing import Optional +import llnl.util.filesystem as fs +import llnl.util.lang as lang import llnl.util.tty as tty -from llnl.util.filesystem import ( - filter_file, - find, - find_all_headers, - find_libraries, - is_nonsymlink_exe_with_shebang, - path_contains_subdirectory, - same_path, - working_dir, -) -from llnl.util.lang import classproperty, match_predicate - -from spack.directives import depends_on, extends + +import spack.builder +import spack.multimethod +import spack.package_base +from spack.directives import build_system, depends_on, extends from spack.error import NoHeadersError, NoLibrariesError, SpecError -from spack.package_base import PackageBase, run_after from spack.version import Version +from ._checks import BaseBuilder, execute_install_time_tests -class PythonPackage(PackageBase): - """Specialized class for packages that are built using pip.""" - - #: Package name, version, and extension on PyPI - pypi = None # type: Optional[str] - - maintainers = ["adamjstewart", "pradyunsg"] - - # Default phases - phases = ["install"] - - # To be used in UI queries that require to know which - # build-system class we are using - build_system_class = "PythonPackage" - - #: Callback names for install-time test - install_time_test_callbacks = ["test"] - - extends("python") - depends_on("py-pip", type="build") - # FIXME: technically wheel is only needed when building from source, not when - # installing a downloaded wheel, but I don't want to add wheel as a dep to every - # package manually - depends_on("py-wheel", type="build") - - py_namespace = None # type: Optional[str] - - @staticmethod - def _std_args(cls): - return [ - # Verbose - "-vvv", - # Disable prompting for input - "--no-input", - # Disable the cache - "--no-cache-dir", - # Don't check to see if pip is up-to-date - "--disable-pip-version-check", - # Install packages - "install", - # Don't install package dependencies - "--no-deps", - # Overwrite existing packages - "--ignore-installed", - # Use env vars like PYTHONPATH - "--no-build-isolation", - # Don't warn that prefix.bin is not in PATH - "--no-warn-script-location", - # Ignore the PyPI package index - "--no-index", - ] - - @classproperty - def homepage(cls): - if cls.pypi: - name = cls.pypi.split("/")[0] - return "https://pypi.org/project/" + name + "/" - - @classproperty - def url(cls): - if cls.pypi: - return "https://files.pythonhosted.org/packages/source/" + cls.pypi[0] + "/" + cls.pypi - @classproperty - def list_url(cls): - if cls.pypi: - name = cls.pypi.split("/")[0] - return "https://pypi.org/simple/" + name + "/" +class PythonExtension(spack.package_base.PackageBase): + maintainers = ["adamjstewart"] @property def import_modules(self): @@ -124,7 +53,7 @@ class PythonPackage(PackageBase): # Some Python libraries are packages: collections of modules # distributed in directories containing __init__.py files - for path in find(root, "__init__.py", recursive=True): + for path in fs.find(root, "__init__.py", recursive=True): modules.append( path.replace(root + os.sep, "", 1) .replace(os.sep + "__init__.py", "") @@ -133,7 +62,7 @@ class PythonPackage(PackageBase): # Some Python libraries are modules: individual *.py files # found in the site-packages directory - for path in find(root, "*.py", recursive=False): + for path in fs.find(root, "*.py", recursive=False): modules.append( path.replace(root + os.sep, "", 1).replace(".py", "").replace("/", ".") ) @@ -160,6 +89,208 @@ class PythonPackage(PackageBase): """ return [] + def view_file_conflicts(self, view, merge_map): + """Report all file conflicts, excepting special cases for python. + Specifically, this does not report errors for duplicate + __init__.py files for packages in the same namespace. + """ + conflicts = list(dst for src, dst in merge_map.items() if os.path.exists(dst)) + + if conflicts and self.py_namespace: + ext_map = view.extensions_layout.extension_map(self.extendee_spec) + namespaces = set(x.package.py_namespace for x in ext_map.values()) + namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace) + find_namespace = lang.match_predicate(namespace_re) + if self.py_namespace in namespaces: + conflicts = list(x for x in conflicts if not find_namespace(x)) + + return conflicts + + def add_files_to_view(self, view, merge_map, skip_if_exists=True): + bin_dir = self.spec.prefix.bin + python_prefix = self.extendee_spec.prefix + python_is_external = self.extendee_spec.external + global_view = fs.same_path(python_prefix, view.get_projection_for_spec(self.spec)) + for src, dst in merge_map.items(): + if os.path.exists(dst): + continue + elif global_view or not fs.path_contains_subdirectory(src, bin_dir): + view.link(src, dst) + elif not os.path.islink(src): + shutil.copy2(src, dst) + is_script = fs.is_nonsymlink_exe_with_shebang(src) + if is_script and not python_is_external: + fs.filter_file( + python_prefix, + os.path.abspath(view.get_projection_for_spec(self.spec)), + dst, + ) + else: + orig_link_target = os.path.realpath(src) + new_link_target = os.path.abspath(merge_map[orig_link_target]) + view.link(new_link_target, dst) + + def remove_files_from_view(self, view, merge_map): + ignore_namespace = False + if self.py_namespace: + ext_map = view.extensions_layout.extension_map(self.extendee_spec) + remaining_namespaces = set( + spec.package.py_namespace for name, spec in ext_map.items() if name != self.name + ) + if self.py_namespace in remaining_namespaces: + namespace_init = lang.match_predicate( + r"site-packages/{0}/__init__.py".format(self.py_namespace) + ) + ignore_namespace = True + + bin_dir = self.spec.prefix.bin + global_view = self.extendee_spec.prefix == view.get_projection_for_spec(self.spec) + + to_remove = [] + for src, dst in merge_map.items(): + if ignore_namespace and namespace_init(dst): + continue + + if global_view or not fs.path_contains_subdirectory(src, bin_dir): + to_remove.append(dst) + else: + os.remove(dst) + + view.remove_files(to_remove) + + def test(self): + """Attempts to import modules of the installed package.""" + + # Make sure we are importing the installed modules, + # not the ones in the source directory + for module in self.import_modules: + self.run_test( + inspect.getmodule(self).python.path, + ["-c", "import {0}".format(module)], + purpose="checking import of {0}".format(module), + work_dir="spack-test", + ) + + +class PythonPackage(PythonExtension): + """Specialized class for packages that are built using pip.""" + + #: Package name, version, and extension on PyPI + pypi = None # type: Optional[str] + + maintainers = ["adamjstewart", "pradyunsg"] + + # To be used in UI queries that require to know which + # build-system class we are using + build_system_class = "PythonPackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "python_pip" + + #: Callback names for install-time test + install_time_test_callbacks = ["test"] + + build_system("python_pip") + + with spack.multimethod.when("build_system=python_pip"): + extends("python") + depends_on("py-pip", type="build") + # FIXME: technically wheel is only needed when building from source, not when + # installing a downloaded wheel, but I don't want to add wheel as a dep to every + # package manually + depends_on("py-wheel", type="build") + + py_namespace = None # type: Optional[str] + + @lang.classproperty + def homepage(cls): + if cls.pypi: + name = cls.pypi.split("/")[0] + return "https://pypi.org/project/" + name + "/" + + @lang.classproperty + def url(cls): + if cls.pypi: + return "https://files.pythonhosted.org/packages/source/" + cls.pypi[0] + "/" + cls.pypi + + @lang.classproperty + def list_url(cls): + if cls.pypi: + name = cls.pypi.split("/")[0] + return "https://pypi.org/simple/" + name + "/" + + @property + def headers(self): + """Discover header files in platlib.""" + + # Headers may be in either location + include = self.prefix.join(self.spec["python"].package.include) + platlib = self.prefix.join(self.spec["python"].package.platlib) + headers = fs.find_all_headers(include) + fs.find_all_headers(platlib) + + if headers: + return headers + + msg = "Unable to locate {} headers in {} or {}" + raise NoHeadersError(msg.format(self.spec.name, include, platlib)) + + @property + def libs(self): + """Discover libraries in platlib.""" + + # Remove py- prefix in package name + library = "lib" + self.spec.name[3:].replace("-", "?") + root = self.prefix.join(self.spec["python"].package.platlib) + + for shared in [True, False]: + libs = fs.find_libraries(library, root, shared=shared, recursive=True) + if libs: + return libs + + msg = "Unable to recursively locate {} libraries in {}" + raise NoLibrariesError(msg.format(self.spec.name, root)) + + +@spack.builder.builder("python_pip") +class PythonPipBuilder(BaseBuilder): + phases = ("install",) + + #: Names associated with package methods in the old build-system format + legacy_methods = ("test",) + + #: Same as legacy_methods, but the signature is different + legacy_long_methods = ("install_options", "global_options", "config_settings") + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ("build_directory", "install_time_test_callbacks") + + #: Callback names for install-time test + install_time_test_callbacks = ["test"] + + @staticmethod + def std_args(cls): + return [ + # Verbose + "-vvv", + # Disable prompting for input + "--no-input", + # Disable the cache + "--no-cache-dir", + # Don't check to see if pip is up-to-date + "--disable-pip-version-check", + # Install packages + "install", + # Don't install package dependencies + "--no-deps", + # Overwrite existing packages + "--ignore-installed", + # Use env vars like PYTHONPATH + "--no-build-isolation", + # Don't warn that prefix.bin is not in PATH + "--no-warn-script-location", + # Ignore the PyPI package index + "--no-index", + ] + @property def build_directory(self): """The root directory of the Python package. @@ -170,11 +301,10 @@ class PythonPackage(PackageBase): * ``setup.cfg`` * ``setup.py`` """ - return self.stage.source_path + return self.pkg.stage.source_path def config_settings(self, spec, prefix): """Configuration settings to be passed to the PEP 517 build backend. - Requires pip 22.1+, which requires Python 3.7+. Args: @@ -211,10 +341,10 @@ class PythonPackage(PackageBase): """ return [] - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Install everything from build directory.""" - args = PythonPackage._std_args(self) + ["--prefix=" + prefix] + args = PythonPipBuilder.std_args(pkg) + ["--prefix=" + prefix] for key, value in self.config_settings(spec, prefix).items(): if spec["py-pip"].version < Version("22.1"): @@ -223,137 +353,21 @@ class PythonPackage(PackageBase): "pip 22.1+. Add the following line to the package to fix this:\n\n" ' depends_on("py-pip@22.1:", type="build")'.format(spec.name) ) + args.append("--config-settings={}={}".format(key, value)) + for option in self.install_options(spec, prefix): args.append("--install-option=" + option) for option in self.global_options(spec, prefix): args.append("--global-option=" + option) - if self.stage.archive_file and self.stage.archive_file.endswith(".whl"): - args.append(self.stage.archive_file) + if pkg.stage.archive_file and pkg.stage.archive_file.endswith(".whl"): + args.append(pkg.stage.archive_file) else: args.append(".") - pip = inspect.getmodule(self).pip - with working_dir(self.build_directory): + pip = inspect.getmodule(pkg).pip + with fs.working_dir(self.build_directory): pip(*args) - @property - def headers(self): - """Discover header files in platlib.""" - - # Headers may be in either location - include = self.prefix.join(self.spec["python"].package.include) - platlib = self.prefix.join(self.spec["python"].package.platlib) - headers = find_all_headers(include) + find_all_headers(platlib) - - if headers: - return headers - - msg = "Unable to locate {} headers in {} or {}" - raise NoHeadersError(msg.format(self.spec.name, include, platlib)) - - @property - def libs(self): - """Discover libraries in platlib.""" - - # Remove py- prefix in package name - library = "lib" + self.spec.name[3:].replace("-", "?") - root = self.prefix.join(self.spec["python"].package.platlib) - - for shared in [True, False]: - libs = find_libraries(library, root, shared=shared, recursive=True) - if libs: - return libs - - msg = "Unable to recursively locate {} libraries in {}" - raise NoLibrariesError(msg.format(self.spec.name, root)) - - # Testing - - def test(self): - """Attempts to import modules of the installed package.""" - - # Make sure we are importing the installed modules, - # not the ones in the source directory - for module in self.import_modules: - self.run_test( - inspect.getmodule(self).python.path, - ["-c", "import {0}".format(module)], - purpose="checking import of {0}".format(module), - work_dir="spack-test", - ) - - 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) - - def view_file_conflicts(self, view, merge_map): - """Report all file conflicts, excepting special cases for python. - Specifically, this does not report errors for duplicate - __init__.py files for packages in the same namespace. - """ - conflicts = list(dst for src, dst in merge_map.items() if os.path.exists(dst)) - - if conflicts and self.py_namespace: - ext_map = view.extensions_layout.extension_map(self.extendee_spec) - namespaces = set(x.package.py_namespace for x in ext_map.values()) - namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace) - find_namespace = match_predicate(namespace_re) - if self.py_namespace in namespaces: - conflicts = list(x for x in conflicts if not find_namespace(x)) - - return conflicts - - def add_files_to_view(self, view, merge_map, skip_if_exists=True): - bin_dir = self.spec.prefix.bin - python_prefix = self.extendee_spec.prefix - python_is_external = self.extendee_spec.external - global_view = same_path(python_prefix, view.get_projection_for_spec(self.spec)) - for src, dst in merge_map.items(): - if os.path.exists(dst): - continue - elif global_view or not path_contains_subdirectory(src, bin_dir): - view.link(src, dst) - elif not os.path.islink(src): - shutil.copy2(src, dst) - is_script = is_nonsymlink_exe_with_shebang(src) - if is_script and not python_is_external: - filter_file( - python_prefix, - os.path.abspath(view.get_projection_for_spec(self.spec)), - dst, - ) - else: - orig_link_target = os.path.realpath(src) - new_link_target = os.path.abspath(merge_map[orig_link_target]) - view.link(new_link_target, dst) - - def remove_files_from_view(self, view, merge_map): - ignore_namespace = False - if self.py_namespace: - ext_map = view.extensions_layout.extension_map(self.extendee_spec) - remaining_namespaces = set( - spec.package.py_namespace for name, spec in ext_map.items() if name != self.name - ) - if self.py_namespace in remaining_namespaces: - namespace_init = match_predicate( - r"site-packages/{0}/__init__.py".format(self.py_namespace) - ) - ignore_namespace = True - - bin_dir = self.spec.prefix.bin - global_view = self.extendee_spec.prefix == view.get_projection_for_spec(self.spec) - - to_remove = [] - for src, dst in merge_map.items(): - if ignore_namespace and namespace_init(dst): - continue - - if global_view or not path_contains_subdirectory(src, bin_dir): - to_remove.append(dst) - else: - os.remove(dst) - - view.remove_files(to_remove) + spack.builder.run_after("install")(execute_install_time_tests) diff --git a/lib/spack/spack/build_systems/qmake.py b/lib/spack/spack/build_systems/qmake.py index c2af684592..f18bd9812f 100644 --- a/lib/spack/spack/build_systems/qmake.py +++ b/lib/spack/spack/build_systems/qmake.py @@ -2,82 +2,85 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect from llnl.util.filesystem import working_dir -from spack.directives import depends_on -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, depends_on + +from ._checks import BaseBuilder, execute_build_time_tests -class QMakePackage(PackageBase): +class QMakePackage(spack.package_base.PackageBase): """Specialized class for packages built using qmake. For more information on the qmake build system, see: http://doc.qt.io/qt-5/qmake-manual.html + """ + + #: This attribute is used in UI queries that need to know the build + #: system base class + build_system_class = "QMakePackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "qmake" + + build_system("qmake") + + depends_on("qt", type="build", when="build_system=qmake") + - This class provides three phases that can be overridden: +@spack.builder.builder("qmake") +class QMakeBuilder(BaseBuilder): + """The qmake builder provides three phases that can be overridden: - 1. :py:meth:`~.QMakePackage.qmake` - 2. :py:meth:`~.QMakePackage.build` - 3. :py:meth:`~.QMakePackage.install` + 1. :py:meth:`~.QMakeBuilder.qmake` + 2. :py:meth:`~.QMakeBuilder.build` + 3. :py:meth:`~.QMakeBuilder.install` They all have sensible defaults and for many packages the only thing - necessary will be to override :py:meth:`~.QMakePackage.qmake_args`. + necessary will be to override :py:meth:`~.QMakeBuilder.qmake_args`. """ - #: Phases of a qmake package - phases = ["qmake", "build", "install"] + phases = ("qmake", "build", "install") - #: This attribute is used in UI queries that need to know the build - #: system base class - build_system_class = "QMakePackage" + #: Names associated with package methods in the old build-system format + legacy_methods = ("qmake_args", "check") + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ("build_directory", "build_time_test_callbacks") #: Callback names for build-time test build_time_test_callbacks = ["check"] - depends_on("qt", type="build") - @property def build_directory(self): """The directory containing the ``*.pro`` file.""" return self.stage.source_path def qmake_args(self): - """Produces a list containing all the arguments that must be passed to - qmake - """ + """List of arguments passed to qmake.""" return [] - def qmake(self, spec, prefix): + def qmake(self, pkg, spec, prefix): """Run ``qmake`` to configure the project and generate a Makefile.""" - with working_dir(self.build_directory): - inspect.getmodule(self).qmake(*self.qmake_args()) + inspect.getmodule(self.pkg).qmake(*self.qmake_args()) - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Make the build targets""" - with working_dir(self.build_directory): - inspect.getmodule(self).make() + inspect.getmodule(self.pkg).make() - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Make the install targets""" - with working_dir(self.build_directory): - inspect.getmodule(self).make("install") - - # Tests + inspect.getmodule(self.pkg).make("install") def check(self): - """Searches the Makefile for a ``check:`` target and runs it if found.""" - + """Search the Makefile for a ``check:`` target and runs it if found.""" with working_dir(self.build_directory): self._if_make_target_execute("check") - run_after("build")(PackageBase._run_default_build_time_test_callbacks) - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) + spack.builder.run_after("build")(execute_build_time_tests) diff --git a/lib/spack/spack/build_systems/r.py b/lib/spack/spack/build_systems/r.py index 450cae733b..c62baa3555 100644 --- a/lib/spack/spack/build_systems/r.py +++ b/lib/spack/spack/build_systems/r.py @@ -3,29 +3,63 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) import inspect -from typing import Optional +from typing import Optional, Tuple import llnl.util.lang as lang from spack.directives import extends -from spack.package_base import PackageBase, run_after +from .generic import GenericBuilder, Package -class RPackage(PackageBase): - """Specialized class for packages that are built using R. - - For more information on the R build system, see: - https://stat.ethz.ch/R-manual/R-devel/library/utils/html/INSTALL.html - This class provides a single phase that can be overridden: +class RBuilder(GenericBuilder): + """The R builder provides a single phase that can be overridden: - 1. :py:meth:`~.RPackage.install` + 1. :py:meth:`~.RBuilder.install` It has sensible defaults, and for many packages the only thing - necessary will be to add dependencies + necessary will be to add dependencies. """ - phases = ["install"] + #: Names associated with package methods in the old build-system format + legacy_methods = ( + "configure_args", + "configure_vars", + ) + GenericBuilder.legacy_methods # type: Tuple[str, ...] + + def configure_args(self): + """Arguments to pass to install via ``--configure-args``.""" + return [] + + def configure_vars(self): + """Arguments to pass to install via ``--configure-vars``.""" + return [] + + def install(self, pkg, spec, prefix): + """Installs an R package.""" + + config_args = self.configure_args() + config_vars = self.configure_vars() + + args = ["--vanilla", "CMD", "INSTALL"] + + if config_args: + args.append("--configure-args={0}".format(" ".join(config_args))) + + if config_vars: + args.append("--configure-vars={0}".format(" ".join(config_vars))) + + args.extend(["--library={0}".format(self.pkg.module.r_lib_dir), self.stage.source_path]) + + inspect.getmodule(self.pkg).R(*args) + + +class RPackage(Package): + """Specialized class for packages that are built using R. + + For more information on the R build system, see: + https://stat.ethz.ch/R-manual/R-devel/library/utils/html/INSTALL.html + """ # package attributes that can be expanded to set the homepage, url, # list_url, and git values @@ -35,6 +69,8 @@ class RPackage(PackageBase): # For Bioconductor packages bioc = None # type: Optional[str] + GenericBuilder = RBuilder + maintainers = ["glennpj"] #: This attribute is used in UI queries that need to know the build @@ -70,32 +106,3 @@ class RPackage(PackageBase): def git(self): if self.bioc: return "https://git.bioconductor.org/packages/" + self.bioc - - def configure_args(self): - """Arguments to pass to install via ``--configure-args``.""" - return [] - - def configure_vars(self): - """Arguments to pass to install via ``--configure-vars``.""" - return [] - - def install(self, spec, prefix): - """Installs an R package.""" - - config_args = self.configure_args() - config_vars = self.configure_vars() - - args = ["--vanilla", "CMD", "INSTALL"] - - if config_args: - args.append("--configure-args={0}".format(" ".join(config_args))) - - if config_vars: - args.append("--configure-vars={0}".format(" ".join(config_vars))) - - args.extend(["--library={0}".format(self.module.r_lib_dir), self.stage.source_path]) - - inspect.getmodule(self).R(*args) - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/racket.py b/lib/spack/spack/build_systems/racket.py index 7b37d85cf2..889cc07931 100644 --- a/lib/spack/spack/build_systems/racket.py +++ b/lib/spack/spack/build_systems/racket.py @@ -3,14 +3,15 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) import os -from typing import Optional +from typing import Optional, Tuple +import llnl.util.filesystem as fs import llnl.util.lang as lang import llnl.util.tty as tty -from llnl.util.filesystem import working_dir +import spack.builder from spack.build_environment import SPACK_NO_PARALLEL_MAKE, determine_number_of_jobs -from spack.directives import extends +from spack.directives import build_system, extends from spack.package_base import PackageBase from spack.util.environment import env_flag from spack.util.executable import Executable, ProcessError @@ -19,34 +20,52 @@ from spack.util.executable import Executable, ProcessError class RacketPackage(PackageBase): """Specialized class for packages that are built using Racket's `raco pkg install` and `raco setup` commands. - - This class provides the following phases that can be overridden: - - * install - * setup """ #: Package name, version, and extension on PyPI maintainers = ["elfprince13"] - - # Default phases - phases = ["install"] - # To be used in UI queries that require to know which # build-system class we are using build_system_class = "RacketPackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "racket" - extends("racket") + build_system("racket") + + extends("racket", when="build_system=racket") - pkgs = False - subdirectory = None # type: Optional[str] racket_name = None # type: Optional[str] parallel = True @lang.classproperty def homepage(cls): - if cls.pkgs: + if cls.racket_name: return "https://pkgs.racket-lang.org/package/{0}".format(cls.racket_name) + return None + + +@spack.builder.builder("racket") +class RacketBuilder(spack.builder.Builder): + """The Racket builder provides an ``install`` phase that can be overridden.""" + + phases = ("install",) + + #: Names associated with package methods in the old build-system format + legacy_methods = tuple() # type: Tuple[str, ...] + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ("build_directory", "build_time_test_callbacks", "subdirectory") + + #: Callback names for build-time test + build_time_test_callbacks = ["check"] + + racket_name = None # type: Optional[str] + + @property + def subdirectory(self): + if self.racket_name: + return "pkgs/{0}".format(self.pkg.racket_name) + return None @property def build_directory(self): @@ -55,25 +74,25 @@ class RacketPackage(PackageBase): ret = os.path.join(ret, self.subdirectory) return ret - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Install everything from build directory.""" raco = Executable("raco") - with working_dir(self.build_directory): - allow_parallel = self.parallel and (not env_flag(SPACK_NO_PARALLEL_MAKE)) + with fs.working_dir(self.build_directory): + parallel = self.pkg.parallel and (not env_flag(SPACK_NO_PARALLEL_MAKE)) args = [ "pkg", "install", "-t", "dir", "-n", - self.racket_name, + self.pkg.racket_name, "--deps", "fail", "--ignore-implies", "--copy", "-i", "-j", - str(determine_number_of_jobs(allow_parallel)), + str(determine_number_of_jobs(parallel)), "--", os.getcwd(), ] @@ -82,9 +101,8 @@ class RacketPackage(PackageBase): except ProcessError: args.insert(-2, "--skip-installed") raco(*args) - tty.warn( - ( - "Racket package {0} was already installed, uninstalling via " - "Spack may make someone unhappy!" - ).format(self.racket_name) + msg = ( + "Racket package {0} was already installed, uninstalling via " + "Spack may make someone unhappy!" ) + tty.warn(msg.format(self.pkg.racket_name)) diff --git a/lib/spack/spack/build_systems/ruby.py b/lib/spack/spack/build_systems/ruby.py index fcc071f19e..ef29f164ab 100644 --- a/lib/spack/spack/build_systems/ruby.py +++ b/lib/spack/spack/build_systems/ruby.py @@ -2,35 +2,49 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import glob import inspect -from spack.directives import extends -from spack.package_base import PackageBase, run_after - +import spack.builder +import spack.package_base +from spack.directives import build_system, extends -class RubyPackage(PackageBase): - """Specialized class for building Ruby gems. +from ._checks import BaseBuilder - This class provides two phases that can be overridden if required: - #. :py:meth:`~.RubyPackage.build` - #. :py:meth:`~.RubyPackage.install` - """ +class RubyPackage(spack.package_base.PackageBase): + """Specialized class for building Ruby gems.""" maintainers = ["Kerilk"] - #: Phases of a Ruby package - phases = ["build", "install"] - #: This attribute is used in UI queries that need to know the build #: system base class build_system_class = "RubyPackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "ruby" + + build_system("ruby") + + extends("ruby", when="build_system=ruby") + - extends("ruby") +@spack.builder.builder("ruby") +class RubyBuilder(BaseBuilder): + """The Ruby builder provides two phases that can be overridden if required: - def build(self, spec, prefix): + #. :py:meth:`~.RubyBuilder.build` + #. :py:meth:`~.RubyBuilder.install` + """ + + phases = ("build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = () + + #: Names associated with package attributes in the old build-system format + legacy_attributes = () + + def build(self, pkg, spec, prefix): """Build a Ruby gem.""" # ruby-rake provides both rake.gemspec and Rakefile, but only @@ -38,15 +52,15 @@ class RubyPackage(PackageBase): gemspecs = glob.glob("*.gemspec") rakefiles = glob.glob("Rakefile") if gemspecs: - inspect.getmodule(self).gem("build", "--norc", gemspecs[0]) + inspect.getmodule(self.pkg).gem("build", "--norc", gemspecs[0]) elif rakefiles: - jobs = inspect.getmodule(self).make_jobs - inspect.getmodule(self).rake("package", "-j{0}".format(jobs)) + jobs = inspect.getmodule(self.pkg).make_jobs + inspect.getmodule(self.pkg).rake("package", "-j{0}".format(jobs)) else: # Some Ruby packages only ship `*.gem` files, so nothing to build pass - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Install a Ruby gem. The ruby package sets ``GEM_HOME`` to tell gem where to install to.""" @@ -56,9 +70,6 @@ class RubyPackage(PackageBase): # if --install-dir is not used, GEM_PATH is deleted from the # environement, and Gems required to build native extensions will # not be found. Those extensions are built during `gem install`. - inspect.getmodule(self).gem( + inspect.getmodule(self.pkg).gem( "install", "--norc", "--ignore-dependencies", "--install-dir", prefix, gems[0] ) - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/scons.py b/lib/spack/spack/build_systems/scons.py index 5470091121..f1ae955917 100644 --- a/lib/spack/spack/build_systems/scons.py +++ b/lib/spack/spack/build_systems/scons.py @@ -2,63 +2,75 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect -from spack.directives import depends_on -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, depends_on + +from ._checks import BaseBuilder, execute_build_time_tests -class SConsPackage(PackageBase): +class SConsPackage(spack.package_base.PackageBase): """Specialized class for packages built using SCons. See http://scons.org/documentation.html for more information. - - This class provides the following phases that can be overridden: - - 1. :py:meth:`~.SConsPackage.build` - 2. :py:meth:`~.SConsPackage.install` - - Packages that use SCons as a build system are less uniform than packages - that use other build systems. Developers can add custom subcommands or - variables that control the build. You will likely need to override - :py:meth:`~.SConsPackage.build_args` to pass the appropriate variables. """ - #: Phases of a SCons package - phases = ["build", "install"] - #: To be used in UI queries that require to know which #: build-system class we are using build_system_class = "SConsPackage" #: Callback names for build-time test build_time_test_callbacks = ["build_test"] + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "scons" + + build_system("scons") + + depends_on("scons", type="build", when="build_system=scons") + - depends_on("scons", type="build") +@spack.builder.builder("scons") +class SConsBuilder(BaseBuilder): + """The Scons builder provides the following phases that can be overridden: - def build_args(self, spec, prefix): + 1. :py:meth:`~.SConsBuilder.build` + 2. :py:meth:`~.SConsBuilder.install` + + Packages that use SCons as a build system are less uniform than packages that use + other build systems. Developers can add custom subcommands or variables that + control the build. You will likely need to override + :py:meth:`~.SConsBuilder.build_args` to pass the appropriate variables. + """ + + #: Phases of a SCons package + phases = ("build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("build_args", "install_args", "build_test") + + #: Names associated with package attributes in the old build-system format + legacy_attributes = () + + def build_args(self): """Arguments to pass to build.""" return [] - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Build the package.""" - args = self.build_args(spec, prefix) - - inspect.getmodule(self).scons(*args) + args = self.build_args() + inspect.getmodule(self.pkg).scons(*args) - def install_args(self, spec, prefix): + def install_args(self): """Arguments to pass to install.""" return [] - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Install the package.""" - args = self.install_args(spec, prefix) + args = self.install_args() - inspect.getmodule(self).scons("install", *args) - - # Testing + inspect.getmodule(self.pkg).scons("install", *args) def build_test(self): """Run unit tests after build. @@ -68,7 +80,4 @@ class SConsPackage(PackageBase): """ pass - run_after("build")(PackageBase._run_default_build_time_test_callbacks) - - # Check that self.prefix is there after installation - run_after("install")(PackageBase.sanity_check_prefix) + spack.builder.run_after("build")(execute_build_time_tests) diff --git a/lib/spack/spack/build_systems/sip.py b/lib/spack/spack/build_systems/sip.py index 4d16f6731e..b129ca4e0a 100644 --- a/lib/spack/spack/build_systems/sip.py +++ b/lib/spack/spack/build_systems/sip.py @@ -2,7 +2,6 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import inspect import os import re @@ -10,28 +9,20 @@ import re import llnl.util.tty as tty from llnl.util.filesystem import find, join_path, working_dir -from spack.directives import depends_on, extends -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, depends_on, extends +from spack.multimethod import when + +from ._checks import BaseBuilder, execute_install_time_tests -class SIPPackage(PackageBase): +class SIPPackage(spack.package_base.PackageBase): """Specialized class for packages that are built using the SIP build system. See https://www.riverbankcomputing.com/software/sip/intro for more information. - - This class provides the following phases that can be overridden: - - * configure - * build - * install - - The configure phase already adds a set of default flags. To see more - options, run ``python configure.py --help``. """ - # Default phases - phases = ["configure", "build", "install"] - # To be used in UI queries that require to know which # build-system class we are using build_system_class = "SIPPackage" @@ -41,11 +32,15 @@ class SIPPackage(PackageBase): #: Callback names for install-time test install_time_test_callbacks = ["test"] + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "sip" - extends("python") + build_system("sip") - depends_on("qt") - depends_on("py-sip") + with when("build_system=sip"): + extends("python") + depends_on("qt") + depends_on("py-sip") @property def import_modules(self): @@ -95,11 +90,51 @@ class SIPPackage(PackageBase): """The python ``Executable``.""" inspect.getmodule(self).python(*args, **kwargs) + def test(self): + """Attempts to import modules of the installed package.""" + + # Make sure we are importing the installed modules, + # not the ones in the source directory + for module in self.import_modules: + self.run_test( + inspect.getmodule(self).python.path, + ["-c", "import {0}".format(module)], + purpose="checking import of {0}".format(module), + work_dir="spack-test", + ) + + +@spack.builder.builder("sip") +class SIPBuilder(BaseBuilder): + """The SIP builder provides the following phases that can be overridden: + + * configure + * build + * install + + The configure phase already adds a set of default flags. To see more + options, run ``python configure.py --help``. + """ + + phases = ("configure", "build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ("configure_file", "configure_args", "build_args", "install_args") + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "build_targets", + "install_targets", + "build_time_test_callbacks", + "install_time_test_callbacks", + "build_directory", + ) + def configure_file(self): """Returns the name of the configure file to use.""" return "configure.py" - def configure(self, spec, prefix): + def configure(self, pkg, spec, prefix): """Configure the package.""" configure = self.configure_file() @@ -118,7 +153,7 @@ class SIPPackage(PackageBase): "--bindir", prefix.bin, "--destdir", - inspect.getmodule(self).python_platlib, + inspect.getmodule(self.pkg).python_platlib, ] ) @@ -128,53 +163,35 @@ class SIPPackage(PackageBase): """Arguments to pass to configure.""" return [] - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Build the package.""" args = self.build_args() - inspect.getmodule(self).make(*args) + inspect.getmodule(self.pkg).make(*args) def build_args(self): """Arguments to pass to build.""" return [] - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Install the package.""" args = self.install_args() - inspect.getmodule(self).make("install", parallel=False, *args) + inspect.getmodule(self.pkg).make("install", parallel=False, *args) def install_args(self): """Arguments to pass to install.""" return [] - # Testing - - def test(self): - """Attempts to import modules of the installed package.""" - - # Make sure we are importing the installed modules, - # not the ones in the source directory - for module in self.import_modules: - self.run_test( - inspect.getmodule(self).python.path, - ["-c", "import {0}".format(module)], - purpose="checking import of {0}".format(module), - work_dir="spack-test", - ) - - 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) + spack.builder.run_after("install")(execute_install_time_tests) - @run_after("install") + @spack.builder.run_after("install") def extend_path_setup(self): # See github issue #14121 and PR #15297 - module = self.spec["py-sip"].variants["module"].value + module = self.pkg.spec["py-sip"].variants["module"].value if module != "sip": module = module.split(".")[0] - with working_dir(inspect.getmodule(self).python_platlib): + with working_dir(inspect.getmodule(self.pkg).python_platlib): with open(os.path.join(module, "__init__.py"), "a") as f: f.write("from pkgutil import extend_path\n") f.write("__path__ = extend_path(__path__, __name__)\n") diff --git a/lib/spack/spack/build_systems/waf.py b/lib/spack/spack/build_systems/waf.py index 3571ffd525..f972d4e286 100644 --- a/lib/spack/spack/build_systems/waf.py +++ b/lib/spack/spack/build_systems/waf.py @@ -2,21 +2,38 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import inspect from llnl.util.filesystem import working_dir -from spack.directives import depends_on -from spack.package_base import PackageBase, run_after +import spack.builder +import spack.package_base +from spack.directives import build_system, depends_on + +from ._checks import BaseBuilder, execute_build_time_tests, execute_install_time_tests -class WafPackage(PackageBase): +class WafPackage(spack.package_base.PackageBase): """Specialized class for packages that are built using the Waf build system. See https://waf.io/book/ for more information. + """ + + # To be used in UI queries that require to know which + # build-system class we are using + build_system_class = "WafPackage" + #: Legacy buildsystem attribute used to deserialize and install old specs + legacy_buildsystem = "waf" + + build_system("waf") + # Much like AutotoolsPackage does not require automake and autoconf + # to build, WafPackage does not require waf to build. It only requires + # python to run the waf build script. + depends_on("python@2.5:", type="build", when="build_system=waf") - This class provides the following phases that can be overridden: + +@spack.builder.builder("waf") +class WafBuilder(BaseBuilder): + """The WAF builder provides the following phases that can be overridden: * configure * build @@ -40,12 +57,25 @@ class WafPackage(PackageBase): function, which passes ``--prefix=/path/to/installation/prefix``. """ - # Default phases - phases = ["configure", "build", "install"] - - # To be used in UI queries that require to know which - # build-system class we are using - build_system_class = "WafPackage" + phases = ("configure", "build", "install") + + #: Names associated with package methods in the old build-system format + legacy_methods = ( + "build_test", + "install_test", + "configure_args", + "build_args", + "install_args", + "build_test", + "install_test", + ) + + #: Names associated with package attributes in the old build-system format + legacy_attributes = ( + "build_time_test_callbacks", + "build_time_test_callbacks", + "build_directory", + ) # Callback names for build-time test build_time_test_callbacks = ["build_test"] @@ -53,11 +83,6 @@ class WafPackage(PackageBase): # Callback names for install-time test install_time_test_callbacks = ["install_test"] - # Much like AutotoolsPackage does not require automake and autoconf - # to build, WafPackage does not require waf to build. It only requires - # python to run the waf build script. - depends_on("python@2.5:", type="build") - @property def build_directory(self): """The directory containing the ``waf`` file.""" @@ -65,18 +90,18 @@ class WafPackage(PackageBase): def python(self, *args, **kwargs): """The python ``Executable``.""" - inspect.getmodule(self).python(*args, **kwargs) + inspect.getmodule(self.pkg).python(*args, **kwargs) def waf(self, *args, **kwargs): """Runs the waf ``Executable``.""" - jobs = inspect.getmodule(self).make_jobs + jobs = inspect.getmodule(self.pkg).make_jobs with working_dir(self.build_directory): self.python("waf", "-j{0}".format(jobs), *args, **kwargs) - def configure(self, spec, prefix): + def configure(self, pkg, spec, prefix): """Configures the project.""" - args = ["--prefix={0}".format(self.prefix)] + args = ["--prefix={0}".format(self.pkg.prefix)] args += self.configure_args() self.waf("configure", *args) @@ -85,7 +110,7 @@ class WafPackage(PackageBase): """Arguments to pass to configure.""" return [] - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): """Executes the build.""" args = self.build_args() @@ -95,7 +120,7 @@ class WafPackage(PackageBase): """Arguments to pass to build.""" return [] - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): """Installs the targets on the system.""" args = self.install_args() @@ -105,8 +130,6 @@ class WafPackage(PackageBase): """Arguments to pass to install.""" return [] - # Testing - def build_test(self): """Run unit tests after build. @@ -115,7 +138,7 @@ class WafPackage(PackageBase): """ pass - run_after("build")(PackageBase._run_default_build_time_test_callbacks) + spack.builder.run_after("build")(execute_build_time_tests) def install_test(self): """Run unit tests after install. @@ -125,7 +148,4 @@ class WafPackage(PackageBase): """ pass - 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) + spack.builder.run_after("install")(execute_install_time_tests) diff --git a/lib/spack/spack/builder.py b/lib/spack/spack/builder.py new file mode 100644 index 0000000000..6baa321eea --- /dev/null +++ b/lib/spack/spack/builder.py @@ -0,0 +1,574 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import collections +import copy +import functools +import inspect +from typing import List, Optional, Tuple + +import six + +import llnl.util.compat + +import spack.build_environment + +#: Builder classes, as registered by the "builder" decorator +BUILDER_CLS = {} + +#: An object of this kind is a shared global state used to collect callbacks during +#: class definition time, and is flushed when the class object is created at the end +#: of the class definition +#: +#: Args: +#: attribute_name (str): name of the attribute that will be attached to the builder +#: callbacks (list): container used to temporarily aggregate the callbacks +CallbackTemporaryStage = collections.namedtuple( + "CallbackTemporaryStage", ["attribute_name", "callbacks"] +) + +#: Shared global state to aggregate "@run_before" callbacks +_RUN_BEFORE = CallbackTemporaryStage(attribute_name="run_before_callbacks", callbacks=[]) +#: Shared global state to aggregate "@run_after" callbacks +_RUN_AFTER = CallbackTemporaryStage(attribute_name="run_after_callbacks", callbacks=[]) + +#: Map id(pkg) to a builder, to avoid creating multiple +#: builders for the same package object. +_BUILDERS = {} + + +def builder(build_system_name): + """Class decorator used to register the default builder + for a given build-system. + + Args: + build_system_name (str): name of the build-system + """ + + def _decorator(cls): + cls.build_system = build_system_name + BUILDER_CLS[build_system_name] = cls + return cls + + return _decorator + + +def create(pkg): + """Given a package object with an associated concrete spec, + return the builder object that can install it. + + Args: + pkg (spack.package_base.PackageBase): package for which we want the builder + """ + if id(pkg) not in _BUILDERS: + _BUILDERS[id(pkg)] = _create(pkg) + return _BUILDERS[id(pkg)] + + +class _PhaseAdapter(object): + def __init__(self, builder, phase_fn): + self.builder = builder + self.phase_fn = phase_fn + + def __call__(self, spec, prefix): + return self.phase_fn(self.builder.pkg, spec, prefix) + + +def _create(pkg): + """Return a new builder object for the package object being passed as argument. + + The function inspects the build-system used by the package object and try to: + + 1. Return a custom builder, if any is defined in the same ``package.py`` file. + 2. Return a customization of more generic builders, if any is defined in the + class hierarchy (look at AspellDictPackage for an example of that) + 3. Return a run-time generated adapter builder otherwise + + The run-time generated adapter builder is capable of adapting an old-style package + to the new architecture, where the installation procedure has been extracted from + the ``*Package`` hierarchy into a ``*Builder`` hierarchy. This means that the + adapter looks for attribute or method overrides preferably in the ``*Package`` + before using the default builder implementation. + + Note that in case a builder is explicitly coded in ``package.py``, no attempt is made + to look for build-related methods in the ``*Package``. + + Args: + pkg (spack.package_base.PackageBase): package object for which we need a builder + """ + package_module = inspect.getmodule(pkg) + package_buildsystem = buildsystem_name(pkg) + default_builder_cls = BUILDER_CLS[package_buildsystem] + builder_cls_name = default_builder_cls.__name__ + builder_cls = getattr(package_module, builder_cls_name, None) + if builder_cls: + return builder_cls(pkg) + + # Specialized version of a given buildsystem can subclass some + # base classes and specialize certain phases or methods or attributes. + # In that case they can store their builder class as a class level attribute. + # See e.g. AspellDictPackage as an example. + base_cls = getattr(pkg, builder_cls_name, default_builder_cls) + + # From here on we define classes to construct a special builder that adapts to the + # old, single class, package format. The adapter forwards any call or access to an + # attribute related to the installation procedure to a package object wrapped in + # a class that falls-back on calling the base builder if no override is found on the + # package. The semantic should be the same as the method in the base builder were still + # present in the base class of the package. + + class _ForwardToBaseBuilder(object): + def __init__(self, wrapped_pkg_object, root_builder): + self.wrapped_package_object = wrapped_pkg_object + self.root_builder = root_builder + + package_cls = type(wrapped_pkg_object) + wrapper_cls = type(self) + bases = (package_cls, wrapper_cls) + new_cls_name = package_cls.__name__ + "Wrapper" + new_cls = type(new_cls_name, bases, {}) + new_cls.__module__ = package_cls.__module__ + self.__class__ = new_cls + self.__dict__.update(wrapped_pkg_object.__dict__) + + def __getattr__(self, item): + result = getattr(super(type(self.root_builder), self.root_builder), item) + if item in super(type(self.root_builder), self.root_builder).phases: + result = _PhaseAdapter(self.root_builder, result) + return result + + def forward_method_to_getattr(fn_name): + def __forward(self, *args, **kwargs): + return self.__getattr__(fn_name)(*args, **kwargs) + + return __forward + + # Add fallback methods for the Package object to refer to the builder. If a method + # with the same name is defined in the Package, it will override this definition + # (when _ForwardToBaseBuilder is initialized) + for method_name in ( + base_cls.phases + + base_cls.legacy_methods + + getattr(base_cls, "legacy_long_methods", tuple()) + + ("setup_build_environment", "setup_dependent_build_environment") + ): + setattr(_ForwardToBaseBuilder, method_name, forward_method_to_getattr(method_name)) + + def forward_property_to_getattr(property_name): + def __forward(self): + return self.__getattr__(property_name) + + return __forward + + for attribute_name in base_cls.legacy_attributes: + setattr( + _ForwardToBaseBuilder, + attribute_name, + property(forward_property_to_getattr(attribute_name)), + ) + + class Adapter(six.with_metaclass(_PackageAdapterMeta, base_cls)): + def __init__(self, pkg): + # Deal with custom phases in packages here + if hasattr(pkg, "phases"): + self.phases = pkg.phases + for phase in self.phases: + setattr(Adapter, phase, _PackageAdapterMeta.phase_method_adapter(phase)) + + # Attribute containing the package wrapped in dispatcher with a `__getattr__` + # method that will forward certain calls to the default builder. + self.pkg_with_dispatcher = _ForwardToBaseBuilder(pkg, root_builder=self) + super(Adapter, self).__init__(pkg) + + # These two methods don't follow the (self, spec, prefix) signature of phases nor + # the (self) signature of methods, so they are added explicitly to avoid using a + # catch-all (*args, **kwargs) + def setup_build_environment(self, env): + return self.pkg_with_dispatcher.setup_build_environment(env) + + def setup_dependent_build_environment(self, env, dependent_spec): + return self.pkg_with_dispatcher.setup_dependent_build_environment(env, dependent_spec) + + return Adapter(pkg) + + +def buildsystem_name(pkg): + """Given a package object with an associated concrete spec, + return the name of its build system. + + Args: + pkg (spack.package_base.PackageBase): package for which we want + the build system name + """ + try: + return pkg.spec.variants["build_system"].value + except KeyError: + # We are reading an old spec without the build_system variant + return pkg.legacy_buildsystem + + +class PhaseCallbacksMeta(type): + """Permit to register arbitrary functions during class definition and run them + later, before or after a given install phase. + + Each method decorated with ``run_before`` or ``run_after`` gets temporarily + stored in a global shared state when a class being defined is parsed by the Python + interpreter. At class definition time that temporary storage gets flushed and a list + of callbacks is attached to the class being defined. + """ + + def __new__(mcs, name, bases, attr_dict): + for temporary_stage in (_RUN_BEFORE, _RUN_AFTER): + staged_callbacks = temporary_stage.callbacks + + # We don't have callbacks in this class, move on + if not staged_callbacks: + continue + + # If we are here we have callbacks. To get a complete list, get first what + # was attached to parent classes, then prepend what we have registered here. + # + # The order should be: + # 1. Callbacks are registered in order within the same class + # 2. Callbacks defined in derived classes precede those defined in base + # classes + for base in bases: + callbacks_from_base = getattr(base, temporary_stage.attribute_name, None) + if callbacks_from_base: + break + callbacks_from_base = callbacks_from_base or [] + + # Set the callbacks in this class and flush the temporary stage + attr_dict[temporary_stage.attribute_name] = staged_callbacks[:] + callbacks_from_base + del temporary_stage.callbacks[:] + + return super(PhaseCallbacksMeta, mcs).__new__(mcs, name, bases, attr_dict) + + @staticmethod + def run_after(phase, when=None): + """Decorator to register a function for running after a given phase. + + Args: + phase (str): phase after which the function must run. + when (str): condition under which the function is run (if None, it is always run). + """ + + def _decorator(fn): + key = (phase, when) + item = (key, fn) + _RUN_AFTER.callbacks.append(item) + return fn + + return _decorator + + @staticmethod + def run_before(phase, when=None): + """Decorator to register a function for running before a given phase. + + Args: + phase (str): phase before which the function must run. + when (str): condition under which the function is run (if None, it is always run). + """ + + def _decorator(fn): + key = (phase, when) + item = (key, fn) + _RUN_BEFORE.callbacks.append(item) + return fn + + return _decorator + + +class BuilderMeta(PhaseCallbacksMeta, type(llnl.util.compat.Sequence)): # type: ignore + pass + + +class _PackageAdapterMeta(BuilderMeta): + """Metaclass to adapt old-style packages to the new architecture based on builders + for the installation phase. + + This class does the necessary mangling to function argument so that a call to a + builder object can delegate to a package object. + """ + + @staticmethod + def phase_method_adapter(phase_name): + def _adapter(self, pkg, spec, prefix): + phase_fn = getattr(self.pkg_with_dispatcher, phase_name) + return phase_fn(spec, prefix) + + return _adapter + + @staticmethod + def legacy_long_method_adapter(method_name): + def _adapter(self, spec, prefix): + bind_method = getattr(self.pkg_with_dispatcher, method_name) + return bind_method(spec, prefix) + + return _adapter + + @staticmethod + def legacy_method_adapter(method_name): + def _adapter(self): + bind_method = getattr(self.pkg_with_dispatcher, method_name) + return bind_method() + + return _adapter + + @staticmethod + def legacy_attribute_adapter(attribute_name): + def _adapter(self): + return getattr(self.pkg_with_dispatcher, attribute_name) + + return property(_adapter) + + @staticmethod + def combine_callbacks(pipeline_attribute_name): + """This function combines callbacks from old-style packages with callbacks that might + be registered for the default builder. + + It works by: + 1. Extracting the callbacks from the old-style package + 2. Transforming those callbacks by adding an adapter that receives a builder as argument + and calls the wrapped function with ``builder.pkg`` + 3. Combining the list of transformed callbacks with those that might be present in the + default builder + """ + + def _adapter(self): + def unwrap_pkg(fn): + @functools.wraps(fn) + def _wrapped(builder): + return fn(builder.pkg_with_dispatcher) + + return _wrapped + + # Concatenate the current list with the one from package + callbacks_from_package = getattr(self.pkg, pipeline_attribute_name, []) + callbacks_from_package = [(key, unwrap_pkg(x)) for key, x in callbacks_from_package] + callbacks_from_builder = getattr(super(type(self), self), pipeline_attribute_name, []) + return callbacks_from_package + callbacks_from_builder + + return property(_adapter) + + def __new__(mcs, name, bases, attr_dict): + # Add ways to intercept methods and attribute calls and dispatch + # them first to a package object + default_builder_cls = bases[0] + for phase_name in default_builder_cls.phases: + attr_dict[phase_name] = _PackageAdapterMeta.phase_method_adapter(phase_name) + + for method_name in default_builder_cls.legacy_methods: + attr_dict[method_name] = _PackageAdapterMeta.legacy_method_adapter(method_name) + + # These exist e.g. for Python, see discussion in https://github.com/spack/spack/pull/32068 + for method_name in getattr(default_builder_cls, "legacy_long_methods", []): + attr_dict[method_name] = _PackageAdapterMeta.legacy_long_method_adapter(method_name) + + for attribute_name in default_builder_cls.legacy_attributes: + attr_dict[attribute_name] = _PackageAdapterMeta.legacy_attribute_adapter( + attribute_name + ) + + combine_callbacks = _PackageAdapterMeta.combine_callbacks + attr_dict[_RUN_BEFORE.attribute_name] = combine_callbacks(_RUN_BEFORE.attribute_name) + attr_dict[_RUN_AFTER.attribute_name] = combine_callbacks(_RUN_AFTER.attribute_name) + + return super(_PackageAdapterMeta, mcs).__new__(mcs, name, bases, attr_dict) + + +class InstallationPhase(object): + """Manages a single phase of the installation. + + This descriptor stores at creation time the name of the method it should + search for execution. The method is retrieved at __get__ time, so that + it can be overridden by subclasses of whatever class declared the phases. + + It also provides hooks to execute arbitrary callbacks before and after + the phase. + """ + + def __init__(self, name, builder): + self.name = name + self.builder = builder + self.phase_fn = self._select_phase_fn() + self.run_before = self._make_callbacks(_RUN_BEFORE.attribute_name) + self.run_after = self._make_callbacks(_RUN_AFTER.attribute_name) + + def _make_callbacks(self, callbacks_attribute): + result = [] + callbacks = getattr(self.builder, callbacks_attribute, []) + for (phase, condition), fn in callbacks: + # Same if it is for another phase + if phase != self.name: + continue + + # If we have no condition or the callback satisfies a condition, register it + if condition is None or self.builder.pkg.spec.satisfies(condition): + result.append(fn) + return result + + def __str__(self): + msg = '{0}: executing "{1}" phase' + return msg.format(self.builder, self.name) + + def execute(self): + pkg = self.builder.pkg + self._on_phase_start(pkg) + + for callback in self.run_before: + callback(self.builder) + + self.phase_fn(pkg, pkg.spec, pkg.prefix) + + for callback in self.run_after: + callback(self.builder) + + self._on_phase_exit(pkg) + + def _select_phase_fn(self): + phase_fn = getattr(self.builder, self.name, None) + + if not phase_fn: + msg = ( + 'unexpected error: package "{0.fullname}" must implement an ' + '"{1}" phase for the "{2}" build system' + ) + raise RuntimeError(msg.format(self.builder.pkg, self.name, self.builder.build_system)) + + return phase_fn + + def _on_phase_start(self, instance): + # If a phase has a matching stop_before_phase attribute, + # stop the installation process raising a StopPhase + if getattr(instance, "stop_before_phase", None) == self.name: + raise spack.build_environment.StopPhase( + "Stopping before '{0}' phase".format(self.name) + ) + + def _on_phase_exit(self, instance): + # If a phase has a matching last_phase attribute, + # stop the installation process raising a StopPhase + if getattr(instance, "last_phase", None) == self.name: + raise spack.build_environment.StopPhase("Stopping at '{0}' phase".format(self.name)) + + def copy(self): + return copy.deepcopy(self) + + +class Builder(six.with_metaclass(BuilderMeta, llnl.util.compat.Sequence)): + """A builder is a class that, given a package object (i.e. associated with + concrete spec), knows how to install it. + + The builder behaves like a sequence, and when iterated over return the + "phases" of the installation in the correct order. + + Args: + pkg (spack.package_base.PackageBase): package object to be built + """ + + #: Sequence of phases. Must be defined in derived classes + phases = None # type: Optional[Tuple[str, ...]] + #: Build system name. Must also be defined in derived classes. + build_system = None # type: Optional[str] + + legacy_methods = () # type: Tuple[str, ...] + legacy_attributes = () # type: Tuple[str, ...] + + #: List of glob expressions. Each expression must either be + #: absolute or relative to the package source path. + #: Matching artifacts found at the end of the build process will be + #: copied in the same directory tree as _spack_build_logfile and + #: _spack_build_envfile. + archive_files = [] # type: List[str] + + def __init__(self, pkg): + self.pkg = pkg + self.callbacks = {} + for phase in self.phases: + self.callbacks[phase] = InstallationPhase(phase, self) + + @property + def spec(self): + return self.pkg.spec + + @property + def stage(self): + return self.pkg.stage + + @property + def prefix(self): + return self.pkg.prefix + + def test(self): + # Defer tests to virtual and concrete packages + pass + + def setup_build_environment(self, env): + """Sets up the build environment for a package. + + This method will be called before the current package prefix exists in + Spack's store. + + Args: + env (spack.util.environment.EnvironmentModifications): environment + modifications to be applied when the package is built. Package authors + can call methods on it to alter the build environment. + """ + if not hasattr(super(Builder, self), "setup_build_environment"): + return + super(Builder, self).setup_build_environment(env) + + def setup_dependent_build_environment(self, env, dependent_spec): + """Sets up the build environment of packages that depend on this one. + + This is similar to ``setup_build_environment``, but it is used to + modify the build environments of packages that *depend* on this one. + + This gives packages like Python and others that follow the extension + model a way to implement common environment or compile-time settings + for dependencies. + + This method will be called before the dependent package prefix exists + in Spack's store. + + Examples: + 1. Installing python modules generally requires ``PYTHONPATH`` + to point to the ``lib/pythonX.Y/site-packages`` directory in the + module's install prefix. This method could be used to set that + variable. + + Args: + env (spack.util.environment.EnvironmentModifications): environment + modifications to be applied when the dependent package is built. + Package authors can call methods on it to alter the build environment. + + dependent_spec (spack.spec.Spec): the spec of the dependent package + about to be built. This allows the extendee (self) to query + the dependent's state. Note that *this* package's spec is + available as ``self.spec`` + """ + if not hasattr(super(Builder, self), "setup_dependent_build_environment"): + return + super(Builder, self).setup_dependent_build_environment(env, dependent_spec) + + def __getitem__(self, idx): + key = self.phases[idx] + return self.callbacks[key] + + def __len__(self): + return len(self.phases) + + def __repr__(self): + msg = "{0}({1})" + return msg.format(type(self).__name__, self.pkg.spec.format("{name}/{hash:7}")) + + def __str__(self): + msg = '"{0}" builder for "{1}"' + return msg.format(type(self).build_system, self.pkg.spec.format("{name}/{hash:7}")) + + +# Export these names as standalone to be used in packages +run_after = PhaseCallbacksMeta.run_after +run_before = PhaseCallbacksMeta.run_before diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py index ce69d98dbb..6a16a153ea 100644 --- a/lib/spack/spack/cmd/info.py +++ b/lib/spack/spack/cmd/info.py @@ -210,11 +210,11 @@ def print_maintainers(pkg): def print_phases(pkg): """output installation phases""" - if hasattr(pkg, "phases") and pkg.phases: + if hasattr(pkg.builder, "phases") and pkg.builder.phases: color.cprint("") color.cprint(section_title("Installation Phases:")) phase_str = "" - for phase in pkg.phases: + for phase in pkg.builder.phases: phase_str += " {0}".format(phase) color.cprint(phase_str) diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py index be4b74c54a..af85b64188 100644 --- a/lib/spack/spack/cmd/uninstall.py +++ b/lib/spack/spack/cmd/uninstall.py @@ -228,7 +228,7 @@ def do_uninstall(env, specs, force): except spack.repo.UnknownEntityError: # The package.py file has gone away -- but still # want to uninstall. - spack.package_base.Package.uninstall_by_spec(item, force=True) + spack.package_base.PackageBase.uninstall_by_spec(item, force=True) # A package is ready to be uninstalled when nothing else references it, # unless we are requested to force uninstall it. diff --git a/lib/spack/spack/detection/common.py b/lib/spack/spack/detection/common.py index d79b62b872..b9cd871fc2 100644 --- a/lib/spack/spack/detection/common.py +++ b/lib/spack/spack/detection/common.py @@ -228,7 +228,7 @@ def compute_windows_program_path_for_package(pkg): program files location, return list of best guesses Args: - pkg (spack.package_base.Package): package for which + pkg (spack.package_base.PackageBase): package for which Program Files location is to be computed """ if not is_windows: diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py index c7491861eb..7c49158c79 100644 --- a/lib/spack/spack/directives.py +++ b/lib/spack/spack/directives.py @@ -17,6 +17,7 @@ definition to modify the package, for example: The available directives are: + * ``build_system`` * ``conflicts`` * ``depends_on`` * ``extends`` @@ -59,13 +60,15 @@ __all__ = [ "patch", "variant", "resource", + "build_system", ] #: These are variant names used by Spack internally; packages can't use them reserved_names = ["patches", "dev_path"] -#: Names of possible directives. This list is populated elsewhere in the file. -directive_names = [] +#: Names of possible directives. This list is mostly populated using the @directive decorator. +#: Some directives leverage others and in that case are not automatically added. +directive_names = ["build_system"] _patch_order_index = 0 @@ -758,6 +761,17 @@ def resource(**kwargs): return _execute_resource +def build_system(*values, **kwargs): + default = kwargs.get("default", None) or values[0] + return variant( + "build_system", + values=tuple(values), + description="Build systems supported by the package", + default=default, + multi=False, + ) + + class DirectiveError(spack.error.SpackError): """This is raised when something is wrong with a package directive.""" diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py index 411c076aa8..620f489c77 100644 --- a/lib/spack/spack/installer.py +++ b/lib/spack/spack/installer.py @@ -112,14 +112,15 @@ def _check_last_phase(pkg): Raises: ``BadInstallPhase`` if stop_before or last phase is invalid """ - if pkg.stop_before_phase and pkg.stop_before_phase not in pkg.phases: + phases = pkg.builder.phases + if pkg.stop_before_phase and pkg.stop_before_phase not in phases: raise BadInstallPhase(pkg.name, pkg.stop_before_phase) - if pkg.last_phase and pkg.last_phase not in pkg.phases: + if pkg.last_phase and pkg.last_phase not in phases: raise BadInstallPhase(pkg.name, pkg.last_phase) # If we got a last_phase, make sure it's not already last - if pkg.last_phase and pkg.last_phase == pkg.phases[-1]: + if pkg.last_phase and pkg.last_phase == phases[-1]: pkg.last_phase = None @@ -129,7 +130,7 @@ def _handle_external_and_upstream(pkg, explicit): database if it is external package. Args: - pkg (spack.package_base.Package): the package whose installation is under + pkg (spack.package_base.PackageBase): the package whose installation is under consideration explicit (bool): the package was explicitly requested by the user Return: @@ -559,7 +560,7 @@ def log(pkg): Copy provenance into the install directory on success Args: - pkg (spack.package_base.Package): the package that was built and installed + pkg (spack.package_base.PackageBase): the package that was built and installed """ packages_dir = spack.store.layout.build_packages_path(pkg.spec) @@ -596,7 +597,7 @@ def log(pkg): errors = six.StringIO() target_dir = os.path.join(spack.store.layout.metadata_path(pkg.spec), "archived-files") - for glob_expr in pkg.archive_files: + for glob_expr in pkg.builder.archive_files: # Check that we are trying to copy things that are # in the stage tree (not arbitrary files) abs_expr = os.path.realpath(glob_expr) @@ -810,7 +811,7 @@ class PackageInstaller(object): Creates and queus the initial build task for the package. Args: - pkg (spack.package_base.Package): the package to be built and installed + pkg (spack.package_base.PackageBase): the package to be built and installed request (BuildRequest or None): the associated install request where ``None`` can be used to indicate the package was explicitly requested by the user @@ -1404,7 +1405,7 @@ class PackageInstaller(object): Write a small metadata file with the current spack environment. Args: - pkg (spack.package_base.Package): the package to be built and installed + pkg (spack.package_base.PackageBase): the package to be built and installed """ if not os.path.exists(pkg.spec.prefix): path = spack.util.path.debug_padded_filter(pkg.spec.prefix) @@ -1477,8 +1478,8 @@ class PackageInstaller(object): known dependents. Args: - pkg (spack.package_base.Package): Package that has been installed locally, - externally or upstream + pkg (spack.package_base.PackageBase): Package that has been installed + locally, externally or upstream dependent_ids (list or None): list of the package's dependent ids, or None if the dependent ids are limited to those maintained in the package (dependency DAG) @@ -1562,11 +1563,7 @@ class PackageInstaller(object): return InstallAction.OVERWRITE def install(self): - """ - Install the requested package(s) and or associated dependencies. - - Args: - pkg (spack.package_base.Package): the package to be built and installed""" + """Install the requested package(s) and or associated dependencies.""" self._init_queue() fail_fast_err = "Terminating after first install failure" @@ -1951,6 +1948,8 @@ class BuildProcessInstaller(object): fs.install_tree(pkg.stage.source_path, src_target) def _real_install(self): + import spack.builder + pkg = self.pkg # Do the real install in the source directory. @@ -1981,13 +1980,11 @@ class BuildProcessInstaller(object): # Spawn a daemon that reads from a pipe and redirects # everything to log_path, and provide the phase for logging - for i, (phase_name, phase_attr) in enumerate( - zip(pkg.phases, pkg._InstallPhase_phases) - ): - + builder = spack.builder.create(pkg) + for i, phase_fn in enumerate(builder): # Keep a log file for each phase log_dir = os.path.dirname(pkg.log_path) - log_file = "spack-build-%02d-%s-out.txt" % (i + 1, phase_name.lower()) + log_file = "spack-build-%02d-%s-out.txt" % (i + 1, phase_fn.name.lower()) log_file = os.path.join(log_dir, log_file) try: @@ -2005,20 +2002,20 @@ class BuildProcessInstaller(object): with logger.force_echo(): inner_debug_level = tty.debug_level() tty.set_debug(debug_level) - tty.msg("{0} Executing phase: '{1}'".format(self.pre, phase_name)) + msg = "{0} Executing phase: '{1}'" + tty.msg(msg.format(self.pre, phase_fn.name)) tty.set_debug(inner_debug_level) # Redirect stdout and stderr to daemon pipe - phase = getattr(pkg, phase_attr) - self.timer.phase(phase_name) + self.timer.phase(phase_fn.name) # Catch any errors to report to logging - phase(pkg.spec, pkg.prefix) - spack.hooks.on_phase_success(pkg, phase_name, log_file) + phase_fn.execute() + spack.hooks.on_phase_success(pkg, phase_fn.name, log_file) except BaseException: combine_phase_logs(pkg.phase_log_files, pkg.log_path) - spack.hooks.on_phase_error(pkg, phase_name, log_file) + spack.hooks.on_phase_error(pkg, phase_fn.name, log_file) # phase error indicates install error spack.hooks.on_install_failure(pkg.spec) @@ -2094,7 +2091,7 @@ class BuildTask(object): Instantiate a build task for a package. Args: - pkg (spack.package_base.Package): the package to be built and installed + pkg (spack.package_base.PackageBase): the package to be built and installed request (BuildRequest or None): the associated install request where ``None`` can be used to indicate the package was explicitly requested by the user @@ -2310,7 +2307,7 @@ class BuildRequest(object): Instantiate a build request for a package. Args: - pkg (spack.package_base.Package): the package to be built and installed + pkg (spack.package_base.PackageBase): the package to be built and installed install_args (dict): the install arguments associated with ``pkg`` """ # Ensure dealing with a package that has a concrete spec diff --git a/lib/spack/spack/mixins.py b/lib/spack/spack/mixins.py index ace3681e52..b43b85aa02 100644 --- a/lib/spack/spack/mixins.py +++ b/lib/spack/spack/mixins.py @@ -6,10 +6,9 @@ """This module contains additional behavior that can be attached to any given package. """ -import collections import os import sys -from typing import Callable, DefaultDict, Dict, List # novm +from typing import Callable, DefaultDict, List # novm if sys.version_info >= (3, 5): CallbackDict = DefaultDict[str, List[Callable]] @@ -18,105 +17,7 @@ else: import llnl.util.filesystem -__all__ = [ - "filter_compiler_wrappers", - "PackageMixinsMeta", -] - - -class PackageMixinsMeta(type): - """This metaclass serves the purpose of implementing a declarative syntax - for package mixins. - - Mixins are implemented below in the form of a function. Each one of them - needs to register a callable that takes a single argument to be run - before or after a certain phase. This callable is basically a method that - gets implicitly attached to the package class by calling the mixin. - """ - - _methods_to_be_added = {} # type: Dict[str, Callable] - _add_method_before = collections.defaultdict(list) # type: CallbackDict - _add_method_after = collections.defaultdict(list) # type: CallbackDict - - @staticmethod - def register_method_before(fn, phase): # type: (Callable, str) -> None - """Registers a method to be run before a certain phase. - - Args: - fn: function taking a single argument (self) - phase (str): phase before which fn must run - """ - PackageMixinsMeta._methods_to_be_added[fn.__name__] = fn - PackageMixinsMeta._add_method_before[phase].append(fn) - - @staticmethod - def register_method_after(fn, phase): # type: (Callable, str) -> None - """Registers a method to be run after a certain phase. - - Args: - fn: function taking a single argument (self) - phase (str): phase after which fn must run - """ - PackageMixinsMeta._methods_to_be_added[fn.__name__] = fn - PackageMixinsMeta._add_method_after[phase].append(fn) - - def __init__(cls, name, bases, attr_dict): - - # Add the methods to the class being created - if PackageMixinsMeta._methods_to_be_added: - attr_dict.update(PackageMixinsMeta._methods_to_be_added) - PackageMixinsMeta._methods_to_be_added.clear() - - attr_fmt = "_InstallPhase_{0}" - - # Copy the phases that needs it to the most derived classes - # in order not to interfere with other packages in the hierarchy - phases_to_be_copied = list(PackageMixinsMeta._add_method_before.keys()) - phases_to_be_copied += list(PackageMixinsMeta._add_method_after.keys()) - - for phase in phases_to_be_copied: - - attr_name = attr_fmt.format(phase) - - # Here we want to get the attribute directly from the class (not - # from the instance), so that we can modify it and add the mixin - # method to the pipeline. - phase = getattr(cls, attr_name) - - # Due to MRO, we may have taken a method from a parent class - # and modifying it may influence other packages in unwanted - # manners. Solve the problem by copying the phase into the most - # derived class. - setattr(cls, attr_name, phase.copy()) - - # Insert the methods in the appropriate position - # in the installation pipeline. - - for phase in PackageMixinsMeta._add_method_before: - - attr_name = attr_fmt.format(phase) - phase_obj = getattr(cls, attr_name) - fn_list = PackageMixinsMeta._add_method_after[phase] - - for f in fn_list: - phase_obj.run_before.append(f) - - # Flush the dictionary for the next class - PackageMixinsMeta._add_method_before.clear() - - for phase in PackageMixinsMeta._add_method_after: - - attr_name = attr_fmt.format(phase) - phase_obj = getattr(cls, attr_name) - fn_list = PackageMixinsMeta._add_method_after[phase] - - for f in fn_list: - phase_obj.run_after.append(f) - - # Flush the dictionary for the next class - PackageMixinsMeta._add_method_after.clear() - - super(PackageMixinsMeta, cls).__init__(name, bases, attr_dict) +import spack.builder def filter_compiler_wrappers(*files, **kwargs): @@ -216,4 +117,4 @@ def filter_compiler_wrappers(*files, **kwargs): if self.compiler.name == "nag": x.filter("-Wl,--enable-new-dtags", "", **filter_kwargs) - PackageMixinsMeta.register_method_after(_filter_compiler_wrappers_impl, after) + spack.builder.run_after(after)(_filter_compiler_wrappers_impl) diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py index 2c6a56db1a..66f7a887f3 100644 --- a/lib/spack/spack/multimethod.py +++ b/lib/spack/spack/multimethod.py @@ -120,32 +120,36 @@ class SpecMultiMethod(object): return method return self.default or None - def __call__(self, package_self, *args, **kwargs): + def __call__(self, package_or_builder_self, *args, **kwargs): """Find the first method with a spec that matches the package's spec. If none is found, call the default or if there is none, then raise a NoSuchMethodError. """ - spec_method = self._get_method_by_spec(package_self.spec) + spec_method = self._get_method_by_spec(package_or_builder_self.spec) if spec_method: - return spec_method(package_self, *args, **kwargs) + return spec_method(package_or_builder_self, *args, **kwargs) # Unwrap the MRO of `package_self by hand. Note that we can't # use `super()` here, because using `super()` recursively # requires us to know the class of `package_self`, as well as # its superclasses for successive calls. We don't have that # information within `SpecMultiMethod`, because it is not # associated with the package class. - for cls in inspect.getmro(package_self.__class__)[1:]: + for cls in inspect.getmro(package_or_builder_self.__class__)[1:]: superself = cls.__dict__.get(self.__name__, None) + if isinstance(superself, SpecMultiMethod): # Check parent multimethod for method for spec. - superself_method = superself._get_method_by_spec(package_self.spec) + superself_method = superself._get_method_by_spec(package_or_builder_self.spec) if superself_method: - return superself_method(package_self, *args, **kwargs) + return superself_method(package_or_builder_self, *args, **kwargs) elif superself: - return superself(package_self, *args, **kwargs) + return superself(package_or_builder_self, *args, **kwargs) raise NoSuchMethodError( - type(package_self), self.__name__, package_self.spec, [m[0] for m in self.method_list] + type(package_or_builder_self), + self.__name__, + package_or_builder_self.spec, + [m[0] for m in self.method_list], ) diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 65e699bda4..46c9da4844 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -17,6 +17,7 @@ from llnl.util.filesystem import * import spack.util.executable from spack.build_systems.aspell_dict import AspellDictPackage from spack.build_systems.autotools import AutotoolsPackage +from spack.build_systems.bundle import BundlePackage from spack.build_systems.cached_cmake import ( CachedCMakePackage, cmake_cache_option, @@ -25,12 +26,14 @@ from spack.build_systems.cached_cmake import ( ) from spack.build_systems.cmake import CMakePackage from spack.build_systems.cuda import CudaPackage +from spack.build_systems.generic import Package from spack.build_systems.gnu import GNUMirrorPackage from spack.build_systems.intel import IntelPackage from spack.build_systems.lua import LuaPackage from spack.build_systems.makefile import MakefilePackage from spack.build_systems.maven import MavenPackage from spack.build_systems.meson import MesonPackage +from spack.build_systems.nmake import NMakePackage from spack.build_systems.octave import OctavePackage from spack.build_systems.oneapi import ( IntelOneApiLibraryPackage, @@ -38,7 +41,7 @@ from spack.build_systems.oneapi import ( IntelOneApiStaticLibraryList, ) from spack.build_systems.perl import PerlPackage -from spack.build_systems.python import PythonPackage +from spack.build_systems.python import PythonExtension, PythonPackage from spack.build_systems.qmake import QMakePackage from spack.build_systems.r import RPackage from spack.build_systems.racket import RacketPackage @@ -50,6 +53,7 @@ from spack.build_systems.sourceforge import SourceforgePackage from spack.build_systems.sourceware import SourcewarePackage from spack.build_systems.waf import WafPackage from spack.build_systems.xorg import XorgPackage +from spack.builder import run_after, run_before from spack.dependency import all_deptypes from spack.directives import * from spack.install_test import get_escaped_text_output @@ -62,17 +66,13 @@ from spack.installer import ( from spack.mixins import filter_compiler_wrappers from spack.multimethod import when from spack.package_base import ( - BundlePackage, DependencyConflictError, - Package, build_system_flags, env_flags, flatten_dependencies, inject_flags, install_dependency_symlinks, on_package_attributes, - run_after, - run_before, ) from spack.spec import InvalidSpecDetected, Spec from spack.util.executable import * diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index f05c0ff868..3c3bcaa84d 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -33,7 +33,7 @@ import six import llnl.util.filesystem as fsys import llnl.util.tty as tty -from llnl.util.lang import classproperty, match_predicate, memoized, nullcontext +from llnl.util.lang import classproperty, memoized, nullcontext from llnl.util.link_tree import LinkTree import spack.compilers @@ -104,7 +104,7 @@ def deprecated_version(pkg, version): """Return True if the version is deprecated, False otherwise. Arguments: - pkg (Package): The package whose version is to be checked. + pkg (PackageBase): The package whose version is to be checked. version (str or spack.version.VersionBase): The version being checked """ if not isinstance(version, VersionBase): @@ -122,7 +122,7 @@ def preferred_version(pkg): Returns a sorted list of the preferred versions of the package. Arguments: - pkg (Package): The package whose versions are to be assessed. + pkg (PackageBase): The package whose versions are to be assessed. """ # Here we sort first on the fact that a version is marked # as preferred in the package, then on the fact that the @@ -131,77 +131,6 @@ def preferred_version(pkg): return sorted(pkg.versions, key=key_fn).pop() -class InstallPhase(object): - """Manages a single phase of the installation. - - This descriptor stores at creation time the name of the method it should - search for execution. The method is retrieved at __get__ time, so that - it can be overridden by subclasses of whatever class declared the phases. - - It also provides hooks to execute arbitrary callbacks before and after - the phase. - """ - - def __init__(self, name): - self.name = name - self.run_before = [] - self.run_after = [] - - def __get__(self, instance, owner): - # The caller is a class that is trying to customize - # my behavior adding something - if instance is None: - return self - # If instance is there the caller wants to execute the - # install phase, thus return a properly set wrapper - phase = getattr(instance, self.name) - - @functools.wraps(phase) - def phase_wrapper(spec, prefix): - # Check instance attributes at the beginning of a phase - self._on_phase_start(instance) - # Execute phase pre-conditions, - # and give them the chance to fail - for callback in self.run_before: - callback(instance) - phase(spec, prefix) - # Execute phase sanity_checks, - # and give them the chance to fail - for callback in self.run_after: - callback(instance) - # Check instance attributes at the end of a phase - self._on_phase_exit(instance) - - return phase_wrapper - - def _on_phase_start(self, instance): - # If a phase has a matching stop_before_phase attribute, - # stop the installation process raising a StopPhase - if getattr(instance, "stop_before_phase", None) == self.name: - from spack.build_environment import StopPhase - - raise StopPhase("Stopping before '{0}' phase".format(self.name)) - - def _on_phase_exit(self, instance): - # If a phase has a matching last_phase attribute, - # stop the installation process raising a StopPhase - if getattr(instance, "last_phase", None) == self.name: - from spack.build_environment import StopPhase - - raise StopPhase("Stopping at '{0}' phase".format(self.name)) - - def copy(self): - try: - return copy.deepcopy(self) - except TypeError: - # This bug-fix was not back-ported in Python 2.6 - # http://bugs.python.org/issue1515 - other = InstallPhase(self.name) - other.run_before.extend(self.run_before) - other.run_after.extend(self.run_after) - return other - - class WindowsRPathMeta(object): """Collection of functionality surrounding Windows RPATH specific features @@ -368,23 +297,18 @@ class DetectablePackageMeta(object): class PackageMeta( + spack.builder.PhaseCallbacksMeta, DetectablePackageMeta, spack.directives.DirectiveMeta, - spack.mixins.PackageMixinsMeta, spack.multimethod.MultiMethodMeta, ): """ Package metaclass for supporting directives (e.g., depends_on) and phases """ - phase_fmt = "_InstallPhase_{0}" - - # These are accessed only through getattr, by name - _InstallPhase_run_before = {} # type: Dict[str, List[Callable]] - _InstallPhase_run_after = {} # type: Dict[str, List[Callable]] - def __new__(cls, name, bases, attr_dict): """ + FIXME: REWRITE Instance creation is preceded by phase attribute transformations. Conveniently transforms attributes to permit extensible phases by @@ -392,70 +316,10 @@ class PackageMeta( InstallPhase attributes in the class that will be initialized in __init__. """ - if "phases" in attr_dict: - # Turn the strings in 'phases' into InstallPhase instances - # and add them as private attributes - _InstallPhase_phases = [PackageMeta.phase_fmt.format(x) for x in attr_dict["phases"]] - for phase_name, callback_name in zip(_InstallPhase_phases, attr_dict["phases"]): - attr_dict[phase_name] = InstallPhase(callback_name) - attr_dict["_InstallPhase_phases"] = _InstallPhase_phases - - def _flush_callbacks(check_name): - # Name of the attribute I am going to check it exists - check_attr = PackageMeta.phase_fmt.format(check_name) - checks = getattr(cls, check_attr) - if checks: - for phase_name, funcs in checks.items(): - phase_attr = PackageMeta.phase_fmt.format(phase_name) - try: - # Search for the phase in the attribute dictionary - phase = attr_dict[phase_attr] - except KeyError: - # If it is not there it's in the bases - # and we added a check. We need to copy - # and extend - for base in bases: - phase = getattr(base, phase_attr, None) - if phase is not None: - break - - phase = attr_dict[phase_attr] = phase.copy() - getattr(phase, check_name).extend(funcs) - # Clear the attribute for the next class - setattr(cls, check_attr, {}) - - _flush_callbacks("run_before") - _flush_callbacks("run_after") - - # Reset names for packages that inherit from another - # package with a different name attr_dict["_name"] = None return super(PackageMeta, cls).__new__(cls, name, bases, attr_dict) - @staticmethod - def register_callback(check_type, *phases): - def _decorator(func): - attr_name = PackageMeta.phase_fmt.format(check_type) - check_list = getattr(PackageMeta, attr_name) - for item in phases: - checks = check_list.setdefault(item, []) - checks.append(func) - setattr(PackageMeta, attr_name, check_list) - return func - - return _decorator - - -def run_before(*phases): - """Registers a method of a package to be run before a given phase""" - return PackageMeta.register_callback("run_before", *phases) - - -def run_after(*phases): - """Registers a method of a package to be run after a given phase""" - return PackageMeta.register_callback("run_after", *phases) - def on_package_attributes(**attr_dict): """Decorator: executes instance function only if object has attr valuses. @@ -475,7 +339,9 @@ def on_package_attributes(**attr_dict): has_all_attributes = all([hasattr(instance, key) for key in attr_dict]) if has_all_attributes: has_the_right_values = all( - [getattr(instance, key) == value for key, value in attr_dict.items()] + [ + getattr(instance, key) == value for key, value in attr_dict.items() + ] # NOQA: ignore=E501 ) if has_the_right_values: func(instance, *args, **kwargs) @@ -687,13 +553,6 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM #: directories, sanity checks will fail. sanity_check_is_dir = [] # type: List[str] - #: List of glob expressions. Each expression must either be - #: absolute or relative to the package source path. - #: Matching artifacts found at the end of the build process will be - #: copied in the same directory tree as _spack_build_logfile and - #: _spack_build_envfile. - archive_files = [] # type: List[str] - #: Boolean. Set to ``True`` for packages that require a manual download. #: This is currently used by package sanity tests and generation of a #: more meaningful fetch failure error. @@ -1038,7 +897,7 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM version (spack.version.Version): the version for which a URL is sought """ uf = None - if type(self).url_for_version != Package.url_for_version: + if type(self).url_for_version != PackageBase.url_for_version: uf = self.url_for_version return self._implement_all_urls_for_version(version, uf) @@ -1959,9 +1818,9 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM even with exceptions. restage (bool): Force spack to restage the package source. skip_patch (bool): Skip patch stage of build if True. - stop_before (InstallPhase): stop execution before this + stop_before (str): stop execution before this installation phase (or None) - stop_at (InstallPhase): last installation phase to be executed + stop_at (str): last installation phase to be executed (or None) tests (bool or list or set): False to run no tests, True to test all packages, or a list of package names to run tests for some @@ -2191,46 +2050,6 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM """ return True - def sanity_check_prefix(self): - """This function checks whether install succeeded.""" - - def check_paths(path_list, filetype, predicate): - if isinstance(path_list, six.string_types): - path_list = [path_list] - - for path in path_list: - abs_path = os.path.join(self.prefix, path) - if not predicate(abs_path): - raise InstallError( - "Install failed for %s. No such %s in prefix: %s" - % (self.name, filetype, path) - ) - - check_paths(self.sanity_check_is_file, "file", os.path.isfile) - check_paths(self.sanity_check_is_dir, "directory", os.path.isdir) - - ignore_file = match_predicate(spack.store.layout.hidden_file_regexes) - if all(map(ignore_file, os.listdir(self.prefix))): - raise InstallError("Install failed for %s. Nothing was installed!" % self.name) - - def apply_macos_rpath_fixups(self): - """On Darwin, make installed libraries more easily relocatable. - - Some build systems (handrolled, autotools, makefiles) can set their own - rpaths that are duplicated by spack's compiler wrapper. This fixup - interrogates, and postprocesses if necessary, all libraries installed - by the code. - - It should be added as a @run_after to packaging systems (or individual - packages) that do not install relocatable libraries by default. - """ - if "platform=darwin" not in self.spec: - return - - from spack.relocate import fixup_macos_rpaths - - fixup_macos_rpaths(self.spec) - @property def build_log_path(self): """ @@ -2268,19 +2087,6 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM """ return None, None, flags - def setup_build_environment(self, env): - """Sets up the build environment for a package. - - This method will be called before the current package prefix exists in - Spack's store. - - Args: - env (spack.util.environment.EnvironmentModifications): environment - modifications to be applied when the package is built. Package authors - can call methods on it to alter the build environment. - """ - pass - def setup_run_environment(self, env): """Sets up the run environment for a package. @@ -2291,37 +2097,6 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM """ pass - def setup_dependent_build_environment(self, env, dependent_spec): - """Sets up the build environment of packages that depend on this one. - - This is similar to ``setup_build_environment``, but it is used to - modify the build environments of packages that *depend* on this one. - - This gives packages like Python and others that follow the extension - model a way to implement common environment or compile-time settings - for dependencies. - - This method will be called before the dependent package prefix exists - in Spack's store. - - Examples: - 1. Installing python modules generally requires ``PYTHONPATH`` - to point to the ``lib/pythonX.Y/site-packages`` directory in the - module's install prefix. This method could be used to set that - variable. - - Args: - env (spack.util.environment.EnvironmentModifications): environment - modifications to be applied when the dependent package is built. - Package authors can call methods on it to alter the build environment. - - dependent_spec (spack.spec.Spec): the spec of the dependent package - about to be built. This allows the extendee (self) to query - the dependent's state. Note that *this* package's spec is - available as ``self.spec`` - """ - pass - def setup_dependent_run_environment(self, env, dependent_spec): """Sets up the run environment of packages that depend on this one. @@ -2508,7 +2283,7 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM def do_uninstall(self, force=False): """Uninstall this package by spec.""" # delegate to instance-less method. - Package.uninstall_by_spec(self.spec, force) + PackageBase.uninstall_by_spec(self.spec, force) def do_deprecate(self, deprecator, link_fn): """Deprecate this package in favor of deprecator spec""" @@ -2560,7 +2335,7 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM deprecated.package.do_deprecate(deprecator, link_fn) # Now that we've handled metadata, uninstall and replace with link - Package.uninstall_by_spec(spec, force=True, deprecator=deprecator) + PackageBase.uninstall_by_spec(spec, force=True, deprecator=deprecator) link_fn(deprecator.prefix, spec.prefix) def _check_extendable(self): @@ -2811,21 +2586,25 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM """ return " ".join("-Wl,-rpath,%s" % p for p in self.rpath) - def _run_test_callbacks(self, method_names, callback_type="install"): + @property + def builder(self): + return spack.builder.create(self) + + @staticmethod + def run_test_callbacks(builder, method_names, callback_type="install"): """Tries to call all of the listed methods, returning immediately if the list is None.""" - if method_names is None: + if not builder.pkg.run_tests or method_names is None: return fail_fast = spack.config.get("config:fail_fast", False) - - with self._setup_test(verbose=False, externals=False) as logger: + with builder.pkg._setup_test(verbose=False, externals=False) as logger: # Report running each of the methods in the build log print_test_message(logger, "Running {0}-time tests".format(callback_type), True) for name in method_names: try: - fn = getattr(self, name) + fn = getattr(builder, name) msg = ("RUN-TESTS: {0}-time tests [{1}]".format(callback_type, name),) print_test_message(logger, msg, True) @@ -2835,27 +2614,13 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM msg = ("RUN-TESTS: method not implemented [{0}]".format(name),) print_test_message(logger, msg, True) - self.test_failures.append((e, msg)) + builder.pkg.test_failures.append((e, msg)) if fail_fast: break # Raise any collected failures here - if self.test_failures: - raise TestFailure(self.test_failures) - - @on_package_attributes(run_tests=True) - def _run_default_build_time_test_callbacks(self): - """Tries to call all the methods that are listed in the attribute - ``build_time_test_callbacks`` if ``self.run_tests is True``. - """ - self._run_test_callbacks(self.build_time_test_callbacks, "build") - - @on_package_attributes(run_tests=True) - def _run_default_install_time_test_callbacks(self): - """Tries to call all the methods that are listed in the attribute - ``install_time_test_callbacks`` if ``self.run_tests is True``. - """ - self._run_test_callbacks(self.install_time_test_callbacks, "install") + if builder.pkg.test_failures: + raise TestFailure(builder.pkg.test_failures) def has_test_method(pkg): @@ -2979,37 +2744,6 @@ env_flags = PackageBase.env_flags build_system_flags = PackageBase.build_system_flags -class BundlePackage(PackageBase): - """General purpose bundle, or no-code, package class.""" - - #: There are no phases by default but the property is required to support - #: post-install hooks (e.g., for module generation). - phases = [] # type: List[str] - #: This attribute is used in UI queries that require to know which - #: build-system class we are using - build_system_class = "BundlePackage" - - #: Bundle packages do not have associated source or binary code. - has_code = False - - -class Package(PackageBase): - """General purpose class with a single ``install`` - phase that needs to be coded by packagers. - """ - - #: The one and only phase - phases = ["install"] - #: This attribute is used in UI queries that require to know which - #: build-system class we are using - build_system_class = "Package" - # This will be used as a registration decorator in user - # packages, if need be - run_after("install")(PackageBase.sanity_check_prefix) - # On macOS, force rpaths for shared library IDs and remove duplicate rpaths - run_after("install")(PackageBase.apply_macos_rpath_fixups) - - def install_dependency_symlinks(pkg, spec, prefix): """ Execute a dummy install and flatten dependencies. diff --git a/lib/spack/spack/patch.py b/lib/spack/spack/patch.py index 553b04fda9..06841fa73f 100644 --- a/lib/spack/spack/patch.py +++ b/lib/spack/spack/patch.py @@ -352,7 +352,7 @@ class PatchCache(object): Arguments: sha256 (str): sha256 hash to look up - pkg (spack.package_base.Package): Package object to get patch for. + pkg (spack.package_base.PackageBase): Package object to get patch for. We build patch objects lazily because building them requires that we have information about the package's location in its repo. diff --git a/lib/spack/spack/test/audit.py b/lib/spack/spack/test/audit.py index eded1a92f2..bc5dd3edca 100644 --- a/lib/spack/spack/test/audit.py +++ b/lib/spack/spack/test/audit.py @@ -22,7 +22,7 @@ import spack.config # This package has a GitHub patch URL without full_index=1 (["invalid-github-patch-url"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]), # This package has a stand-alone 'test' method in build-time callbacks - (["test-build-callbacks"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]), + (["fail-test-audit"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]), # This package has no issues (["mpileaks"], None), # This package has a conflict with a trigger which cannot constrain the constraint diff --git a/lib/spack/spack/test/build_systems.py b/lib/spack/spack/test/build_systems.py index f660596bb7..c4255eef49 100644 --- a/lib/spack/spack/test/build_systems.py +++ b/lib/spack/spack/test/build_systems.py @@ -13,10 +13,11 @@ import pytest import llnl.util.filesystem as fs import spack.build_systems.autotools +import spack.build_systems.cmake import spack.environment import spack.platforms import spack.repo -from spack.build_environment import ChildError, get_std_cmake_args, setup_package +from spack.build_environment import ChildError, setup_package from spack.spec import Spec from spack.util.executable import which @@ -144,7 +145,7 @@ class TestAutotoolsPackage(object): # Assert the libtool archive is not there and we have # a log of removed files - assert not os.path.exists(s.package.libtool_archive_file) + assert not os.path.exists(s.package.builder.libtool_archive_file) search_directory = os.path.join(s.prefix, ".spack") libtool_deletion_log = fs.find(search_directory, "removed_la_files.txt", recursive=True) assert libtool_deletion_log @@ -155,11 +156,11 @@ class TestAutotoolsPackage(object): # Install a package that creates a mock libtool archive, # patch its package to preserve the installation s = Spec("libtool-deletion").concretized() - monkeypatch.setattr(s.package, "install_libtool_archives", True) + monkeypatch.setattr(type(s.package.builder), "install_libtool_archives", True) s.package.do_install(explicit=True) # Assert libtool archives are installed - assert os.path.exists(s.package.libtool_archive_file) + assert os.path.exists(s.package.builder.libtool_archive_file) def test_autotools_gnuconfig_replacement(self, mutable_database): """ @@ -253,22 +254,23 @@ class TestCMakePackage(object): def test_cmake_std_args(self): # Call the function on a CMakePackage instance s = Spec("cmake-client").concretized() - assert s.package.std_cmake_args == get_std_cmake_args(s.package) + expected = spack.build_systems.cmake.CMakeBuilder.std_args(s.package) + assert s.package.builder.std_cmake_args == expected # Call it on another kind of package s = Spec("mpich").concretized() - assert get_std_cmake_args(s.package) + assert spack.build_systems.cmake.CMakeBuilder.std_args(s.package) - def test_cmake_bad_generator(self): + def test_cmake_bad_generator(self, monkeypatch): s = Spec("cmake-client").concretized() - s.package.generator = "Yellow Sticky Notes" + monkeypatch.setattr(type(s.package), "generator", "Yellow Sticky Notes", raising=False) with pytest.raises(spack.package_base.InstallError): - get_std_cmake_args(s.package) + s.package.builder.std_cmake_args def test_cmake_secondary_generator(config, mock_packages): s = Spec("cmake-client").concretized() s.package.generator = "CodeBlocks - Unix Makefiles" - assert get_std_cmake_args(s.package) + assert s.package.builder.std_cmake_args def test_define(self): s = Spec("cmake-client").concretized() @@ -361,7 +363,7 @@ def test_autotools_args_from_conditional_variant(config, mock_packages): is not met. When this is the case, the variant is not set in the spec.""" s = Spec("autotools-conditional-variants-test").concretized() assert "example" not in s.variants - assert len(s.package._activate_or_not("example", "enable", "disable")) == 0 + assert len(s.package.builder._activate_or_not("example", "enable", "disable")) == 0 def test_autoreconf_search_path_args_multiple(config, mock_packages, tmpdir): diff --git a/lib/spack/spack/test/builder.py b/lib/spack/spack/test/builder.py new file mode 100644 index 0000000000..bda72c8b49 --- /dev/null +++ b/lib/spack/spack/test/builder.py @@ -0,0 +1,123 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os.path + +import pytest + +import spack.paths + + +@pytest.fixture() +def builder_test_repository(): + builder_test_path = os.path.join(spack.paths.repos_path, "builder.test") + with spack.repo.use_repositories(builder_test_path) as mock_repo: + yield mock_repo + + +@pytest.mark.parametrize( + "spec_str,expected_values", + [ + ( + "callbacks@2.0", + [ + ("BEFORE_INSTALL_1_CALLED", "1"), + ("BEFORE_INSTALL_2_CALLED", "1"), + ("CALLBACKS_INSTALL_CALLED", "1"), + ("AFTER_INSTALL_1_CALLED", "1"), + ("TEST_VALUE", "3"), + ("INSTALL_VALUE", "CALLBACKS"), + ], + ), + # The last callback is conditional on "@1.0", check it's being executed + ( + "callbacks@1.0", + [ + ("BEFORE_INSTALL_1_CALLED", "1"), + ("BEFORE_INSTALL_2_CALLED", "1"), + ("CALLBACKS_INSTALL_CALLED", "1"), + ("AFTER_INSTALL_1_CALLED", "1"), + ("AFTER_INSTALL_2_CALLED", "1"), + ("TEST_VALUE", "4"), + ("INSTALL_VALUE", "CALLBACKS"), + ], + ), + # The package below adds to "callbacks" using inheritance, test that using super() + # works with builder hierarchies + ( + "inheritance@1.0", + [ + ("DERIVED_BEFORE_INSTALL_CALLED", "1"), + ("BEFORE_INSTALL_1_CALLED", "1"), + ("BEFORE_INSTALL_2_CALLED", "1"), + ("CALLBACKS_INSTALL_CALLED", "1"), + ("INHERITANCE_INSTALL_CALLED", "1"), + ("AFTER_INSTALL_1_CALLED", "1"), + ("AFTER_INSTALL_2_CALLED", "1"), + ("TEST_VALUE", "4"), + ("INSTALL_VALUE", "INHERITANCE"), + ], + ), + # Generate custom phases using a GenericBuilder + ( + "custom-phases", + [ + ("CONFIGURE_CALLED", "1"), + ("INSTALL_CALLED", "1"), + ("LAST_PHASE", "INSTALL"), + ], + ), + # Old-style package, with phase defined in base builder + ( + "old-style-autotools@1.0", + [ + ("AFTER_AUTORECONF_1_CALLED", "1"), + ], + ), + ( + "old-style-autotools@2.0", + [ + ("AFTER_AUTORECONF_2_CALLED", "1"), + ], + ), + ( + "old-style-custom-phases", + [ + ("AFTER_CONFIGURE_CALLED", "1"), + ("TEST_VALUE", "0"), + ], + ), + ], +) +@pytest.mark.usefixtures("builder_test_repository", "config") +@pytest.mark.disable_clean_stage_check +def test_callbacks_and_installation_procedure(spec_str, expected_values, working_env): + """Test the correct execution of callbacks and installation procedures for packages.""" + s = spack.spec.Spec(spec_str).concretized() + builder = spack.builder.create(s.package) + for phase_fn in builder: + phase_fn.execute() + + # Check calls have produced the expected side effects + for var_name, expected in expected_values: + assert os.environ[var_name] == expected, os.environ + + +@pytest.mark.usefixtures("builder_test_repository", "config") +@pytest.mark.parametrize( + "spec_str,method_name,expected", + [ + # Call a function defined on the package, which calls the same function defined + # on the super(builder) + ("old-style-autotools", "configure_args", ["--with-foo"]), + # Call a function defined on the package, which calls the same function defined on the + # super(pkg), which calls the same function defined in the super(builder) + ("old-style-derived", "configure_args", ["--with-bar", "--with-foo"]), + ], +) +def test_old_style_compatibility_with_super(spec_str, method_name, expected): + s = spack.spec.Spec(spec_str).concretized() + builder = spack.builder.create(s.package) + value = getattr(builder, method_name)() + assert value == expected diff --git a/lib/spack/spack/test/cmd/info.py b/lib/spack/spack/test/cmd/info.py index 55b875e022..2bbf4d3a7f 100644 --- a/lib/spack/spack/test/cmd/info.py +++ b/lib/spack/spack/test/cmd/info.py @@ -43,7 +43,7 @@ def test_it_just_runs(pkg): def test_info_noversion(mock_packages, print_buffer): - """Check that a mock package with no versions or variants outputs None.""" + """Check that a mock package with no versions outputs None.""" info("noversion") line_iter = iter(print_buffer) @@ -52,7 +52,7 @@ def test_info_noversion(mock_packages, print_buffer): has = [desc in line for desc in ["Preferred", "Safe", "Deprecated"]] if not any(has): continue - elif "Variants" not in line: + else: continue assert "None" in next(line_iter).strip() diff --git a/lib/spack/spack/test/cmd/install.py b/lib/spack/spack/test/cmd/install.py index 8c37ea3e98..41de6565a1 100644 --- a/lib/spack/spack/test/cmd/install.py +++ b/lib/spack/spack/test/cmd/install.py @@ -1087,7 +1087,7 @@ def test_install_empty_env( ("test-install-callbacks", "undefined-install-test"), ], ) -def test_install_callbacks_fail(install_mockery, mock_fetch, name, method): +def test_installation_fail_tests(install_mockery, mock_fetch, name, method): output = install("--test=root", "--no-cache", name, fail_on_error=False) assert output.count(method) == 2 diff --git a/lib/spack/spack/test/cmd/test.py b/lib/spack/spack/test/cmd/test.py index e842dbc465..fac639309d 100644 --- a/lib/spack/spack/test/cmd/test.py +++ b/lib/spack/spack/test/cmd/test.py @@ -237,8 +237,7 @@ def test_test_list_all(mock_packages): "simple-standalone-test", "test-error", "test-fail", - "test-build-callbacks", - "test-install-callbacks", + "fail-test-audit", ] ) diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index 4d15d48dbe..ccdc19a174 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -228,7 +228,7 @@ class TestConcretize(object): check_concretize(spec) def test_concretize_mention_build_dep(self): - spec = check_concretize("cmake-client ^cmake@3.4.3") + spec = check_concretize("cmake-client ^cmake@3.21.3") # Check parent's perspective of child to_dependencies = spec.edges_to_dependencies(name="cmake") diff --git a/lib/spack/spack/test/concretize_requirements.py b/lib/spack/spack/test/concretize_requirements.py index 50cc6baa9a..e43203b969 100644 --- a/lib/spack/spack/test/concretize_requirements.py +++ b/lib/spack/spack/test/concretize_requirements.py @@ -7,6 +7,7 @@ import sys import pytest +import spack.build_systems.generic import spack.config import spack.repo import spack.util.spack_yaml as syaml @@ -102,7 +103,7 @@ def fake_installs(monkeypatch, tmpdir): stage_path = str(tmpdir.ensure("fake-stage", dir=True)) universal_unused_stage = spack.stage.DIYStage(stage_path) monkeypatch.setattr( - spack.package_base.Package, "_make_stage", MakeStage(universal_unused_stage) + spack.build_systems.generic.Package, "_make_stage", MakeStage(universal_unused_stage) ) diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py index f4d0b570fd..f5f91f039d 100644 --- a/lib/spack/spack/test/config.py +++ b/lib/spack/spack/test/config.py @@ -18,6 +18,7 @@ from llnl.util.filesystem import getuid, join_path, mkdirp, touch, touchp import spack.config import spack.environment as ev import spack.main +import spack.package_base import spack.paths import spack.repo import spack.schema.compilers @@ -1170,13 +1171,13 @@ def test_license_dir_config(mutable_config, mock_packages): """Ensure license directory is customizable""" expected_dir = spack.paths.default_license_dir assert spack.config.get("config:license_dir") == expected_dir - assert spack.package.Package.global_license_dir == expected_dir + assert spack.package_base.PackageBase.global_license_dir == expected_dir assert spack.repo.path.get_pkg_class("a").global_license_dir == expected_dir rel_path = os.path.join(os.path.sep, "foo", "bar", "baz") spack.config.set("config:license_dir", rel_path) assert spack.config.get("config:license_dir") == rel_path - assert spack.package.Package.global_license_dir == rel_path + assert spack.package_base.PackageBase.global_license_dir == rel_path assert spack.repo.path.get_pkg_class("a").global_license_dir == rel_path diff --git a/lib/spack/spack/test/graph.py b/lib/spack/spack/test/graph.py index e7aafe4b0d..60d041d60b 100644 --- a/lib/spack/spack/test/graph.py +++ b/lib/spack/spack/test/graph.py @@ -104,6 +104,19 @@ o dyninst |/ o libelf """ + or graph_str + == r"""o mpileaks +|\ +o | callpath +|\| +| o mpich +| +o dyninst +|\ +o | libdwarf +|/ +o libelf +""" ) diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py index 9093593bed..e8b6f415d3 100644 --- a/lib/spack/spack/test/install.py +++ b/lib/spack/spack/test/install.py @@ -144,13 +144,12 @@ class MockStage(object): return getattr(self.wrapped_stage, attr) -def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch): +def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch, working_env): s = Spec("canfail").concretized() instance_rm_prefix = s.package.remove_prefix try: - s.package.succeed = False s.package.remove_prefix = mock_remove_prefix with pytest.raises(MockInstallError): s.package.do_install() @@ -161,7 +160,7 @@ def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch): # must clear failure markings for the package before re-installing it spack.store.db.clear_failure(s, True) - s.package.succeed = True + s.package.set_install_succeed() s.package.stage = MockStage(s.package.stage) s.package.do_install(restage=True) @@ -174,18 +173,20 @@ def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch): @pytest.mark.disable_clean_stage_check -def test_failing_overwrite_install_should_keep_previous_installation(mock_fetch, install_mockery): +def test_failing_overwrite_install_should_keep_previous_installation( + mock_fetch, install_mockery, working_env +): """ Make sure that whenever `spack install --overwrite` fails, spack restores the original install prefix instead of cleaning it. """ # Do a successful install s = Spec("canfail").concretized() - s.package.succeed = True + s.package.set_install_succeed() # Do a failing overwrite install s.package.do_install() - s.package.succeed = False + s.package.set_install_fail() kwargs = {"overwrite": [s.dag_hash()]} with pytest.raises(Exception): @@ -238,13 +239,11 @@ def test_install_dependency_symlinks_pkg(install_mockery, mock_fetch, mutable_mo def test_install_times(install_mockery, mock_fetch, mutable_mock_repo): """Test install times added.""" - spec = Spec("dev-build-test-install-phases") - spec.concretize() - pkg = spec.package - pkg.do_install() + spec = Spec("dev-build-test-install-phases").concretized() + spec.package.do_install() # Ensure dependency directory exists after the installation. - install_times = os.path.join(pkg.prefix, ".spack", "install_times.json") + install_times = os.path.join(spec.package.prefix, ".spack", "install_times.json") assert os.path.isfile(install_times) # Ensure the phases are included @@ -346,12 +345,11 @@ def test_installed_upstream(install_upstream, mock_fetch): @pytest.mark.disable_clean_stage_check -def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch): +def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch, working_env): s = Spec("canfail").concretized() # If remove_prefix is called at any point in this test, that is an error - s.package.succeed = False # make the build fail - monkeypatch.setattr(spack.package_base.Package, "remove_prefix", mock_remove_prefix) + monkeypatch.setattr(spack.package_base.PackageBase, "remove_prefix", mock_remove_prefix) with pytest.raises(spack.build_environment.ChildError): s.package.do_install(keep_prefix=True) assert os.path.exists(s.package.prefix) @@ -359,7 +357,7 @@ def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch): # must clear failure markings for the package before re-installing it spack.store.db.clear_failure(s, True) - s.package.succeed = True # make the build succeed + s.package.set_install_succeed() s.package.stage = MockStage(s.package.stage) s.package.do_install(keep_prefix=True) assert s.package.spec.installed @@ -368,14 +366,14 @@ def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch): def test_second_install_no_overwrite_first(install_mockery, mock_fetch, monkeypatch): s = Spec("canfail").concretized() - monkeypatch.setattr(spack.package_base.Package, "remove_prefix", mock_remove_prefix) + monkeypatch.setattr(spack.package_base.PackageBase, "remove_prefix", mock_remove_prefix) - s.package.succeed = True + s.package.set_install_succeed() s.package.do_install() assert s.package.spec.installed # If Package.install is called after this point, it will fail - s.package.succeed = False + s.package.set_install_fail() s.package.do_install() @@ -589,7 +587,9 @@ def test_log_install_with_build_files(install_mockery, monkeypatch): source = spec.package.stage.source_path config = os.path.join(source, "config.log") fs.touchp(config) - spec.package.archive_files = ["missing", "..", config] + monkeypatch.setattr( + type(spec.package), "archive_files", ["missing", "..", config], raising=False + ) spack.installer.log(spec.package) diff --git a/lib/spack/spack/test/repo.py b/lib/spack/spack/test/repo.py index fcd009edec..402723d226 100644 --- a/lib/spack/spack/test/repo.py +++ b/lib/spack/spack/test/repo.py @@ -113,14 +113,14 @@ def test_absolute_import_spack_packages_as_python_modules(mock_packages): assert hasattr(spack.pkg.builtin.mock, "mpileaks") assert hasattr(spack.pkg.builtin.mock.mpileaks, "Mpileaks") assert isinstance(spack.pkg.builtin.mock.mpileaks.Mpileaks, spack.package_base.PackageMeta) - assert issubclass(spack.pkg.builtin.mock.mpileaks.Mpileaks, spack.package_base.Package) + assert issubclass(spack.pkg.builtin.mock.mpileaks.Mpileaks, spack.package_base.PackageBase) def test_relative_import_spack_packages_as_python_modules(mock_packages): from spack.pkg.builtin.mock.mpileaks import Mpileaks assert isinstance(Mpileaks, spack.package_base.PackageMeta) - assert issubclass(Mpileaks, spack.package_base.Package) + assert issubclass(Mpileaks, spack.package_base.PackageBase) def test_all_virtual_packages_have_default_providers(): diff --git a/lib/spack/spack/util/package_hash.py b/lib/spack/spack/util/package_hash.py index 4877748338..f4435fbba4 100644 --- a/lib/spack/spack/util/package_hash.py +++ b/lib/spack/spack/util/package_hash.py @@ -64,7 +64,7 @@ class RemoveDirectives(ast.NodeTransformer): # list of URL attributes and metadata attributes # these will be removed from packages. self.metadata_attrs = [s.url_attr for s in spack.fetch_strategy.all_strategies] - self.metadata_attrs += spack.package_base.Package.metadata_attrs + self.metadata_attrs += spack.package_base.PackageBase.metadata_attrs self.spec = spec self.in_classdef = False # used to avoid nested classdefs @@ -158,6 +158,7 @@ class TagMultiMethods(ast.NodeVisitor): def visit_FunctionDef(self, func): conditions = [] + for dec in func.decorator_list: if isinstance(dec, ast.Call) and dec.func.id == "when": try: diff --git a/lib/spack/spack/util/web.py b/lib/spack/spack/util/web.py index fa4119f917..939ec669c0 100644 --- a/lib/spack/spack/util/web.py +++ b/lib/spack/spack/util/web.py @@ -784,7 +784,7 @@ def find_versions_of_archive( list_depth (int): max depth to follow links on list_url pages. Defaults to 0. concurrency (int): maximum number of concurrent requests - reference_package (spack.package_base.Package or None): a spack package + reference_package (spack.package_base.PackageBase or None): a spack package used as a reference for url detection. Uses the url_for_version method on the package to produce reference urls which, if found, are preferred. diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py index 864ea26446..92e512219b 100644 --- a/lib/spack/spack/variant.py +++ b/lib/spack/spack/variant.py @@ -96,7 +96,7 @@ class Variant(object): Args: vspec (Variant): instance to be validated - pkg_cls (spack.package_base.Package): the package class + pkg_cls (spack.package_base.PackageBase): the package class that required the validation, if available Raises: diff --git a/share/spack/gitlab/cloud_pipelines/stacks/aws-ahug-aarch64/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/aws-ahug-aarch64/spack.yaml index 488309b294..5ede7b882c 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/aws-ahug-aarch64/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/aws-ahug-aarch64/spack.yaml @@ -24,6 +24,8 @@ spack: - openmpi - mpich variants: +mpi + tbb: + require: "intel-tbb" binutils: variants: +ld +gold +headers +libiberty ~nls version: diff --git a/share/spack/gitlab/cloud_pipelines/stacks/aws-isc-aarch64/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/aws-isc-aarch64/spack.yaml index 61eb974e70..262de722a5 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/aws-isc-aarch64/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/aws-isc-aarch64/spack.yaml @@ -24,6 +24,8 @@ spack: - openmpi - mpich variants: +mpi + tbb: + require: "intel-tbb" binutils: variants: +ld +gold +headers +libiberty ~nls version: diff --git a/share/spack/gitlab/cloud_pipelines/stacks/aws-isc/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/aws-isc/spack.yaml index ed2f205d42..c71b514c6e 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/aws-isc/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/aws-isc/spack.yaml @@ -24,6 +24,8 @@ spack: - openmpi - mpich variants: +mpi + tbb: + require: "intel-tbb" binutils: variants: +ld +gold +headers +libiberty ~nls version: diff --git a/share/spack/gitlab/cloud_pipelines/stacks/build_systems/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/build_systems/spack.yaml index ee6c9457c3..b3b4e8697c 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/build_systems/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/build_systems/spack.yaml @@ -14,6 +14,8 @@ spack: definitions: - default_specs: + - 'uncrustify build_system=autotools' + - 'uncrustify build_system=cmake' - lz4 # MakefilePackage - mpich~fortran # AutotoolsPackage - py-setuptools # PythonPackage diff --git a/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml index 8d6669252a..4d7b875772 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml @@ -21,6 +21,8 @@ spack: mpi: [mpich] target: [x86_64] variants: +mpi + tbb: + require: "intel-tbb" binutils: variants: +ld +gold +headers +libiberty ~nls cuda: diff --git a/share/spack/gitlab/cloud_pipelines/stacks/tutorial/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/tutorial/spack.yaml index ea34141ba1..0090fd2f83 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/tutorial/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/tutorial/spack.yaml @@ -18,6 +18,8 @@ spack: packages: all: target: [x86_64] + tbb: + require: 'intel-tbb' definitions: - gcc_system_packages: diff --git a/var/spack/repos/builder.test/packages/callbacks/package.py b/var/spack/repos/builder.test/packages/callbacks/package.py new file mode 100644 index 0000000000..ea6787d9b5 --- /dev/null +++ b/var/spack/repos/builder.test/packages/callbacks/package.py @@ -0,0 +1,45 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +import spack.build_systems.generic +from spack.package import * + + +class Callbacks(Package): + """Package used to verify that callbacks on phases work correctly, including conditions""" + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("2.0", "abcdef0123456789abcdef0123456789") + version("1.0", "0123456789abcdef0123456789abcdef") + + +class GenericBuilder(spack.build_systems.generic.GenericBuilder): + def install(self, pkg, spec, prefix): + os.environ["CALLBACKS_INSTALL_CALLED"] = "1" + os.environ["INSTALL_VALUE"] = "CALLBACKS" + mkdirp(prefix.bin) + + @run_before("install") + def before_install_1(self): + os.environ["BEFORE_INSTALL_1_CALLED"] = "1" + os.environ["TEST_VALUE"] = "1" + + @run_before("install") + def before_install_2(self): + os.environ["BEFORE_INSTALL_2_CALLED"] = "1" + os.environ["TEST_VALUE"] = "2" + + @run_after("install") + def after_install_1(self): + os.environ["AFTER_INSTALL_1_CALLED"] = "1" + os.environ["TEST_VALUE"] = "3" + + @run_after("install", when="@1.0") + def after_install_2(self): + os.environ["AFTER_INSTALL_2_CALLED"] = "1" + os.environ["TEST_VALUE"] = "4" diff --git a/var/spack/repos/builder.test/packages/custom-phases/package.py b/var/spack/repos/builder.test/packages/custom-phases/package.py new file mode 100644 index 0000000000..37b26e37d0 --- /dev/null +++ b/var/spack/repos/builder.test/packages/custom-phases/package.py @@ -0,0 +1,31 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +import spack.build_systems.generic +from spack.package import * + + +class CustomPhases(Package): + """Package used to verify that we can set custom phases on builders""" + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("2.0", "abcdef0123456789abcdef0123456789") + version("1.0", "0123456789abcdef0123456789abcdef") + + +class GenericBuilder(spack.build_systems.generic.GenericBuilder): + phases = ["configure", "install"] + + def configure(self, pkg, spec, prefix): + os.environ["CONFIGURE_CALLED"] = "1" + os.environ["LAST_PHASE"] = "CONFIGURE" + + def install(self, pkg, spec, prefix): + os.environ["INSTALL_CALLED"] = "1" + os.environ["LAST_PHASE"] = "INSTALL" + mkdirp(prefix.bin) diff --git a/var/spack/repos/builder.test/packages/gnuconfig/package.py b/var/spack/repos/builder.test/packages/gnuconfig/package.py new file mode 100644 index 0000000000..53f8a10705 --- /dev/null +++ b/var/spack/repos/builder.test/packages/gnuconfig/package.py @@ -0,0 +1,15 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +from spack.package import * + + +class Gnuconfig(Package): + """This package is needed to allow mocking AutotoolsPackage objects""" + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("2.0", "abcdef0123456789abcdef0123456789") + version("1.0", "0123456789abcdef0123456789abcdef") diff --git a/var/spack/repos/builder.test/packages/inheritance/package.py b/var/spack/repos/builder.test/packages/inheritance/package.py new file mode 100644 index 0000000000..307d93ca80 --- /dev/null +++ b/var/spack/repos/builder.test/packages/inheritance/package.py @@ -0,0 +1,26 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +import spack.pkg.builder.test.callbacks +from spack.package import * + + +class Inheritance(spack.pkg.builder.test.callbacks.Callbacks): + """Package used to verify that inheritance among packages work as expected""" + + pass + + +class GenericBuilder(spack.pkg.builder.test.callbacks.GenericBuilder): + def install(self, pkg, spec, prefix): + super(GenericBuilder, self).install(pkg, spec, prefix) + os.environ["INHERITANCE_INSTALL_CALLED"] = "1" + os.environ["INSTALL_VALUE"] = "INHERITANCE" + + @run_before("install") + def derived_before_install(self): + os.environ["DERIVED_BEFORE_INSTALL_CALLED"] = "1" + os.environ["TEST_VALUE"] = "0" diff --git a/var/spack/repos/builder.test/packages/old-style-autotools/package.py b/var/spack/repos/builder.test/packages/old-style-autotools/package.py new file mode 100644 index 0000000000..56213d7158 --- /dev/null +++ b/var/spack/repos/builder.test/packages/old-style-autotools/package.py @@ -0,0 +1,50 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +from spack.package import * + + +class OldStyleAutotools(AutotoolsPackage): + """Package used to verify that old-style packages work correctly when executing the + installation procedure. + """ + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("2.0", "abcdef0123456789abcdef0123456789") + version("1.0", "0123456789abcdef0123456789abcdef") + + def configure(self, spec, prefix): + pass + + def build(self, spec, prefix): + pass + + def install(self, spec, prefix): + mkdirp(prefix.bin) + + def configure_args(self): + """This override a function in the builder and construct the result using a method + defined in this class and a super method defined in the builder. + """ + return [self.foo()] + super(OldStyleAutotools, self).configure_args() + + def foo(self): + return "--with-foo" + + @run_before("autoreconf") + def create_configure(self): + mkdirp(self.configure_directory) + touch(self.configure_abs_path) + + @run_after("autoreconf", when="@1.0") + def after_autoreconf_1(self): + os.environ["AFTER_AUTORECONF_1_CALLED"] = "1" + + @run_after("autoreconf", when="@2.0") + def after_autoreconf_2(self): + os.environ["AFTER_AUTORECONF_2_CALLED"] = "1" diff --git a/var/spack/repos/builder.test/packages/old-style-custom-phases/package.py b/var/spack/repos/builder.test/packages/old-style-custom-phases/package.py new file mode 100644 index 0000000000..afa5b52d70 --- /dev/null +++ b/var/spack/repos/builder.test/packages/old-style-custom-phases/package.py @@ -0,0 +1,34 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +from spack.package import * + + +class OldStyleCustomPhases(AutotoolsPackage): + """Package used to verify that old-style packages work correctly when defining custom + phases (though it's not recommended for packagers to do so). + """ + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("2.0", "abcdef0123456789abcdef0123456789") + version("1.0", "0123456789abcdef0123456789abcdef") + + phases = ["configure"] + + def configure(self, spec, prefix): + mkdirp(prefix.bin) + + @run_after("configure") + def after_configure(self): + os.environ["AFTER_CONFIGURE_CALLED"] = "1" + os.environ["TEST_VALUE"] = "0" + + @run_after("install") + def after_install(self): + os.environ["AFTER_INSTALL_CALLED"] = "1" + os.environ["TEST_VALUE"] = "1" diff --git a/var/spack/repos/builder.test/packages/old-style-derived/package.py b/var/spack/repos/builder.test/packages/old-style-derived/package.py new file mode 100644 index 0000000000..a7dd026217 --- /dev/null +++ b/var/spack/repos/builder.test/packages/old-style-derived/package.py @@ -0,0 +1,21 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import spack.pkg.builder.test.old_style_autotools +from spack.package import * + + +class OldStyleDerived(spack.pkg.builder.test.old_style_autotools.OldStyleAutotools): + """Package used to verify that old-style packages work correctly when executing the + installation procedure. + """ + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("2.0", "abcdef0123456789abcdef0123456789") + version("1.0", "0123456789abcdef0123456789abcdef") + + def configure_args(self): + return ["--with-bar"] + super(OldStyleDerived, self).configure_args() diff --git a/var/spack/repos/builder.test/repo.yaml b/var/spack/repos/builder.test/repo.yaml new file mode 100644 index 0000000000..a9031afe21 --- /dev/null +++ b/var/spack/repos/builder.test/repo.yaml @@ -0,0 +1,2 @@ +repo: + namespace: builder.test diff --git a/var/spack/repos/builtin.mock/packages/a/package.py b/var/spack/repos/builtin.mock/packages/a/package.py index b556fbf06f..5dbcd1f9c2 100644 --- a/var/spack/repos/builtin.mock/packages/a/package.py +++ b/var/spack/repos/builtin.mock/packages/a/package.py @@ -2,7 +2,7 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.autotools from spack.package import * @@ -32,21 +32,23 @@ class A(AutotoolsPackage): parallel = False + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): def with_or_without_fee(self, activated): if not activated: return "--no-fee" return "--fee-all-the-time" - def autoreconf(self, spec, prefix): + def autoreconf(self, pkg, spec, prefix): pass - def configure(self, spec, prefix): + def configure(self, pkg, spec, prefix): pass - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): pass - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): # sanity_check_prefix requires something in the install directory # Test requires overriding the one provided by `AutotoolsPackage` mkdirp(prefix.bin) diff --git a/var/spack/repos/builtin.mock/packages/attributes-foo/package.py b/var/spack/repos/builtin.mock/packages/attributes-foo/package.py index 9010c52958..83b41b98ac 100644 --- a/var/spack/repos/builtin.mock/packages/attributes-foo/package.py +++ b/var/spack/repos/builtin.mock/packages/attributes-foo/package.py @@ -8,7 +8,6 @@ from spack.package import * class AttributesFoo(BundlePackage): - phases = ["install"] version("1.0") provides("bar") diff --git a/var/spack/repos/builtin.mock/packages/canfail/package.py b/var/spack/repos/builtin.mock/packages/canfail/package.py index eb35fdec56..75bb66df25 100644 --- a/var/spack/repos/builtin.mock/packages/canfail/package.py +++ b/var/spack/repos/builtin.mock/packages/canfail/package.py @@ -2,6 +2,7 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os from spack.package import * @@ -14,7 +15,16 @@ class Canfail(Package): version("1.0", "0123456789abcdef0123456789abcdef") - succeed = False + def set_install_succeed(self): + os.environ["CANFAIL_SUCCEED"] = "1" + + def set_install_fail(self): + os.environ.pop("CANFAIL_SUCCEED", None) + + @property + def succeed(self): + result = True if "CANFAIL_SUCCEED" in os.environ else False + return result def install(self, spec, prefix): if not self.succeed: diff --git a/var/spack/repos/builtin.mock/packages/cmake-client/package.py b/var/spack/repos/builtin.mock/packages/cmake-client/package.py index 54842fd2d8..ed3998e550 100644 --- a/var/spack/repos/builtin.mock/packages/cmake-client/package.py +++ b/var/spack/repos/builtin.mock/packages/cmake-client/package.py @@ -15,7 +15,7 @@ def check(condition, msg): class CmakeClient(CMakePackage): - """A dumy package that uses cmake.""" + """A dummy package that uses cmake.""" homepage = "https://www.example.com" url = "https://www.example.com/cmake-client-1.0.tar.gz" @@ -38,14 +38,16 @@ class CmakeClient(CMakePackage): did_something = False @run_after("cmake") - @run_before("cmake", "build", "install") + @run_before("cmake") + @run_before("build") + @run_before("install") def increment(self): - self.callback_counter += 1 + CmakeClient.callback_counter += 1 @run_after("cmake") @on_package_attributes(run_this=True, check_this_is_none=None) def flip(self): - self.flipped = True + CmakeClient.flipped = True @run_after("cmake") @on_package_attributes(does_not_exist=None) diff --git a/var/spack/repos/builtin.mock/packages/cmake/package.py b/var/spack/repos/builtin.mock/packages/cmake/package.py index 30c3647df2..dac2c19875 100644 --- a/var/spack/repos/builtin.mock/packages/cmake/package.py +++ b/var/spack/repos/builtin.mock/packages/cmake/package.py @@ -18,12 +18,17 @@ def check(condition, msg): class Cmake(Package): - """A dumy package for the cmake build system.""" + """A dummy package for the cmake build system.""" homepage = "https://www.cmake.org" url = "https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz" version( + "3.23.1", + "4cb3ff35b2472aae70f542116d616e63", + url="https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz", + ) + version( "3.4.3", "4cb3ff35b2472aae70f542116d616e63", url="https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz", diff --git a/var/spack/repos/builtin.mock/packages/dev-build-test-dependent/package.py b/var/spack/repos/builtin.mock/packages/dev-build-test-dependent/package.py index 44374cd1a7..a5ac04d282 100644 --- a/var/spack/repos/builtin.mock/packages/dev-build-test-dependent/package.py +++ b/var/spack/repos/builtin.mock/packages/dev-build-test-dependent/package.py @@ -7,14 +7,12 @@ from spack.package import * -class DevBuildTestDependent(Package): +class DevBuildTestDependent(MakefilePackage): homepage = "example.com" url = "fake.com" version("0.0.0", sha256="0123456789abcdef0123456789abcdef") - phases = ["edit", "install"] - filename = "dev-build-test-file.txt" original_string = "This file should be edited" replacement_string = "This file has been edited" @@ -28,5 +26,8 @@ class DevBuildTestDependent(Package): f.truncate() f.write(self.replacement_string) + def build(self, spec, prefix): + pass + def install(self, spec, prefix): install(self.filename, prefix) diff --git a/var/spack/repos/builtin.mock/packages/dev-build-test-install-phases/package.py b/var/spack/repos/builtin.mock/packages/dev-build-test-install-phases/package.py index fa0f6b794e..916156c1f5 100644 --- a/var/spack/repos/builtin.mock/packages/dev-build-test-install-phases/package.py +++ b/var/spack/repos/builtin.mock/packages/dev-build-test-install-phases/package.py @@ -29,4 +29,5 @@ class DevBuildTestInstallPhases(Package): print("Three locomoco") def install(self, spec, prefix): + mkdirp(prefix.bin) print("install") diff --git a/var/spack/repos/builtin.mock/packages/dev-build-test-install/package.py b/var/spack/repos/builtin.mock/packages/dev-build-test-install/package.py index 185fe5552c..ba0b1400a3 100644 --- a/var/spack/repos/builtin.mock/packages/dev-build-test-install/package.py +++ b/var/spack/repos/builtin.mock/packages/dev-build-test-install/package.py @@ -2,19 +2,15 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - from spack.package import * -class DevBuildTestInstall(Package): +class DevBuildTestInstall(MakefilePackage): homepage = "example.com" url = "fake.com" version("0.0.0", sha256="0123456789abcdef0123456789abcdef") - phases = ["edit", "install"] - filename = "dev-build-test-file.txt" original_string = "This file should be edited" replacement_string = "This file has been edited" @@ -26,5 +22,8 @@ class DevBuildTestInstall(Package): f.truncate() f.write(self.replacement_string) + def build(self, spec, prefix): + pass + def install(self, spec, prefix): install(self.filename, prefix) diff --git a/var/spack/repos/builtin.mock/packages/fail-test-audit/package.py b/var/spack/repos/builtin.mock/packages/fail-test-audit/package.py new file mode 100644 index 0000000000..1e290724d1 --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/fail-test-audit/package.py @@ -0,0 +1,21 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +from spack.package import * + + +class FailTestAudit(MakefilePackage): + """Simple package with one optional dependency""" + + homepage = "http://www.example.com" + url = "http://www.example.com/a-1.0.tar.gz" + + version("1.0", "0123456789abcdef0123456789abcdef") + version("2.0", "abcdef0123456789abcdef0123456789") + + build_time_test_callbacks = ["test"] + + def test(self): + print("test: test-install-callbacks") + print("PASSED") diff --git a/var/spack/repos/builtin.mock/packages/libtool-deletion/package.py b/var/spack/repos/builtin.mock/packages/libtool-deletion/package.py index a169a78d2e..8ab87d2064 100644 --- a/var/spack/repos/builtin.mock/packages/libtool-deletion/package.py +++ b/var/spack/repos/builtin.mock/packages/libtool-deletion/package.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) import os.path +import spack.build_systems.autotools from spack.package import * @@ -19,17 +20,21 @@ class LibtoolDeletion(AutotoolsPackage): def do_stage(self): mkdirp(self.stage.source_path) - def autoreconf(self, spec, prefix): + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): + install_libtool_archives = False + + def autoreconf(self, pkg, spec, prefix): mkdirp(os.path.dirname(self.configure_abs_path)) touch(self.configure_abs_path) - def configure(self, spec, prefix): + def configure(self, pkg, spec, prefix): pass - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): pass - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): mkdirp(os.path.dirname(self.libtool_archive_file)) touch(self.libtool_archive_file) diff --git a/var/spack/repos/builtin.mock/packages/libtool-installation/package.py b/var/spack/repos/builtin.mock/packages/libtool-installation/package.py new file mode 100644 index 0000000000..72883b9042 --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/libtool-installation/package.py @@ -0,0 +1,15 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +from spack.package import * +from spack.pkg.builtin.mock.libtool_deletion import AutotoolsBuilder as BuilderBase +from spack.pkg.builtin.mock.libtool_deletion import LibtoolDeletion + + +class LibtoolInstallation(LibtoolDeletion, AutotoolsPackage): + """Mock AutotoolsPackage to check proper installation of libtool archives.""" + + +class AutotoolsBuilder(BuilderBase): + install_libtool_archives = True diff --git a/var/spack/repos/builtin.mock/packages/nosource-install/package.py b/var/spack/repos/builtin.mock/packages/nosource-install/package.py index a1ca724448..5166480dd4 100644 --- a/var/spack/repos/builtin.mock/packages/nosource-install/package.py +++ b/var/spack/repos/builtin.mock/packages/nosource-install/package.py @@ -16,9 +16,6 @@ class NosourceInstall(BundlePackage): depends_on("dependency-install") - # The install phase must be specified. - phases = ["install"] - # The install method must also be present. def install(self, spec, prefix): touch(join_path(self.prefix, "install.txt")) diff --git a/var/spack/repos/builtin.mock/packages/test-build-callbacks/package.py b/var/spack/repos/builtin.mock/packages/test-build-callbacks/package.py index 4b4b74e9b3..d45f0d295b 100644 --- a/var/spack/repos/builtin.mock/packages/test-build-callbacks/package.py +++ b/var/spack/repos/builtin.mock/packages/test-build-callbacks/package.py @@ -2,9 +2,9 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems._checks as checks +import spack.build_systems.generic from spack.package import * -from spack.package_base import run_after class TestBuildCallbacks(Package): @@ -15,17 +15,16 @@ class TestBuildCallbacks(Package): version("1.0", "0123456789abcdef0123456789abcdef") + +class GenericBuilder(spack.build_systems.generic.GenericBuilder): phases = ["build", "install"] - # Include undefined method (runtime failure) and 'test' (audit failure) - build_time_test_callbacks = ["undefined-build-test", "test"] - run_after("build")(Package._run_default_build_time_test_callbacks) - def build(self, spec, prefix): + # Include undefined method (runtime failure) + build_time_test_callbacks = ["undefined-build-test"] + run_after("build")(checks.execute_build_time_tests) + + def build(self, pkg, spec, prefix): pass - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): mkdirp(prefix.bin) - - def test(self): - print("test: running test-build-callbacks") - print("PASSED") diff --git a/var/spack/repos/builtin.mock/packages/test-install-callbacks/package.py b/var/spack/repos/builtin.mock/packages/test-install-callbacks/package.py index 27a31227c3..0d348c0d67 100644 --- a/var/spack/repos/builtin.mock/packages/test-install-callbacks/package.py +++ b/var/spack/repos/builtin.mock/packages/test-install-callbacks/package.py @@ -2,9 +2,9 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems._checks as checks +import spack.build_systems.generic from spack.package import * -from spack.package_base import run_after class TestInstallCallbacks(Package): @@ -15,13 +15,11 @@ class TestInstallCallbacks(Package): version("1.0", "0123456789abcdef0123456789abcdef") + +class GenericBuilder(spack.build_systems.generic.GenericBuilder): # Include an undefined callback method - install_time_test_callbacks = ["undefined-install-test", "test"] - run_after("install")(Package._run_default_install_time_test_callbacks) + install_time_test_callbacks = ["undefined-install-test"] + run_after("install")(checks.execute_install_time_tests) - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): mkdirp(prefix.bin) - - def test(self): - print("test: test-install-callbacks") - print("PASSED") diff --git a/var/spack/repos/builtin.mock/packages/transitive-conditional-virtual-dependency/package.py b/var/spack/repos/builtin.mock/packages/transitive-conditional-virtual-dependency/package.py index a7c4658810..9078d63b4d 100644 --- a/var/spack/repos/builtin.mock/packages/transitive-conditional-virtual-dependency/package.py +++ b/var/spack/repos/builtin.mock/packages/transitive-conditional-virtual-dependency/package.py @@ -5,12 +5,10 @@ from spack.package import * -class TransitiveConditionalVirtualDependency(Package): +class TransitiveConditionalVirtualDependency(BundlePackage): """Depends on a package with a conditional virtual dependency.""" homepage = "https://dev.null" - has_code = False - phases = [] version("1.0") depends_on("conditional-virtual-dependency") diff --git a/var/spack/repos/builtin.mock/packages/trivial-smoke-test/package.py b/var/spack/repos/builtin.mock/packages/trivial-smoke-test/package.py index ec80640d21..35983a18f4 100644 --- a/var/spack/repos/builtin.mock/packages/trivial-smoke-test/package.py +++ b/var/spack/repos/builtin.mock/packages/trivial-smoke-test/package.py @@ -16,6 +16,9 @@ class TrivialSmokeTest(Package): test_source_filename = "cached_file.in" + def install(self, spec, prefix): + pass + @run_before("install") def create_extra_test_source(self): mkdirp(self.install_test_root) diff --git a/var/spack/repos/builtin/packages/alpgen/package.py b/var/spack/repos/builtin/packages/alpgen/package.py index 717b513cc3..e0816eeca7 100644 --- a/var/spack/repos/builtin/packages/alpgen/package.py +++ b/var/spack/repos/builtin/packages/alpgen/package.py @@ -2,15 +2,16 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import os +import spack.build_systems.makefile from spack.package import * -class Alpgen(MakefilePackage): - """A collection of codes for the generation of - multi-parton processes in hadronic collisions.""" +class Alpgen(CMakePackage, MakefilePackage): + """A collection of codes for the generation of multi-parton processes + in hadronic collisions. + """ homepage = "http://mlm.home.cern.ch/mlm/alpgen/" url = "http://mlm.home.cern.ch/mlm/alpgen/V2.1/v214.tgz" @@ -18,102 +19,44 @@ class Alpgen(MakefilePackage): maintainers = ["iarspider"] tags = ["hep"] - patch("alpgen-214.patch", when="recipe=cms") - patch("alpgen-214-Darwin-x86_84-gfortran.patch", when="platform=darwin recipe=cms") - patch("alpgen-2.1.4-sft.patch", when="recipe=sft", level=0) + version("2.1.4", sha256="2f43f7f526793fe5f81a3a3e1adeffe21b653a7f5851efc599ed69ea13985c5e") - depends_on("cmake", type="build", when="recipe=sft") + build_system("makefile", "cmake", default="makefile") variant( "recipe", - values=("cms", "sft"), + values=( + conditional("cms", when="build_system=makefile"), + conditional("sft", when="build_system=cmake"), + ), default="sft", - description="Select build recipe: CMS for CMS experiment, " + "SFT for ATLAS/LHCb/others.", + description="CMS for CMS experiment, SFT for ATLAS/LHCb/others.", ) - version("2.1.4", sha256="2f43f7f526793fe5f81a3a3e1adeffe21b653a7f5851efc599ed69ea13985c5e") - - phases = ["cmake", "build", "install"] - - # copied from CMakePackage - @property - def build_dirname(self): - """Returns the directory name to use when building the package - - :return: name of the subdirectory for building the package - """ - return "spack-build-%s" % self.spec.dag_hash(7) - - @property - def build_directory(self): - """Returns the directory to use when building the package - - :return: directory where to build the package - """ - return os.path.join(self.stage.path, self.build_dirname) - - @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 - - 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 [] - - @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 - std_cmake_args = CMakePackage._std_args(self) - std_cmake_args += getattr(self, "cmake_flag_args", []) - return std_cmake_args - - # end + patch("alpgen-214.patch", when="recipe=cms") + patch("alpgen-214-Darwin-x86_84-gfortran.patch", when="platform=darwin recipe=cms") + patch("alpgen-2.1.4-sft.patch", when="recipe=sft", level=0) def url_for_version(self, version): root = self.url.rsplit("/", 2)[0] return "{0}/V{1}/v{2}.tgz".format(root, version.up_to(2), version.joined) def patch(self): - if self.spec.satisfies("recipe=sft"): + if self.spec.satisfies("build_system=cmake"): copy(join_path(os.path.dirname(__file__), "CMakeLists.txt"), "CMakeLists.txt") - if self.spec.satisfies("recipe=cms"): + if self.spec.satisfies("build_system=makefile"): filter_file("-fno-automatic", "-fno-automatic -std=legacy", "compile.mk") copy(join_path(os.path.dirname(__file__), "cms_build.sh"), "cms_build.sh") copy(join_path(os.path.dirname(__file__), "cms_install.sh"), "cms_install.sh") - @when("recipe=cms") - def cmake(self, spec, prefix): - return - @when("recipe=cms") - def build(self, spec, prefix): +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder): + def build(self, pkg, spec, prefix): bash = which("bash") bash("./cms_build.sh") - @when("recipe=cms") - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): bash = which("bash") bash("./cms_install.sh", prefix) @@ -121,25 +64,3 @@ class Alpgen(MakefilePackage): set_install_permissions(root) for file in files: set_install_permissions(join_path(root, file)) - - @when("recipe=sft") - def cmake(self, spec, prefix): - """Runs ``cmake`` in the build directory""" - options = self.std_cmake_args - options += self.cmake_args() - options.append(os.path.abspath(self.root_cmakelists_dir)) - with working_dir(self.build_directory, create=True): - cmake_x = which("cmake") - cmake_x(*options) - - @when("recipe=sft") - def build(self, spec, prefix): - """Make the build targets""" - with working_dir(self.build_directory): - make() - - @when("recipe=sft") - def install(self, spec, prefix): - """Make the install targets""" - with working_dir(self.build_directory): - make("install") diff --git a/var/spack/repos/builtin/packages/arpack-ng/package.py b/var/spack/repos/builtin/packages/arpack-ng/package.py index 70b1b4820c..49feb262cb 100644 --- a/var/spack/repos/builtin/packages/arpack-ng/package.py +++ b/var/spack/repos/builtin/packages/arpack-ng/package.py @@ -2,11 +2,12 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.autotools +import spack.build_systems.cmake from spack.package import * -class ArpackNg(Package): +class ArpackNg(CMakePackage, AutotoolsPackage): """ARPACK-NG is a collection of Fortran77 subroutines designed to solve large scale eigenvalue problems. @@ -38,6 +39,8 @@ class ArpackNg(Package): url = "https://github.com/opencollab/arpack-ng/archive/3.3.0.tar.gz" git = "https://github.com/opencollab/arpack-ng.git" + build_system("cmake", "autotools", default="cmake") + version("develop", branch="master") version("3.8.0", sha256="ada5aeb3878874383307239c9235b716a8a170c6d096a6625bfd529844df003d") version("3.7.0", sha256="972e3fc3cd0b9d6b5a737c9bf6fd07515c0d6549319d4ffb06970e64fa3cc2d6") @@ -74,13 +77,14 @@ class ArpackNg(Package): depends_on("blas") depends_on("lapack") - depends_on("automake", when="@3.3.0", type="build") - depends_on("autoconf", when="@3.3.0", type="build") - depends_on("libtool@2.4.2:", when="@3.3.0", type="build") - depends_on("cmake@2.8.6:", when="@3.4.0:", type="build") - depends_on("mpi", when="+mpi") + with when("build_system=autotools"): + depends_on("automake", type="build") + depends_on("autoconf", type="build") + depends_on("libtool@2.4.2:", type="build") + depends_on("pkgconfig", type="build") + def flag_handler(self, name, flags): spec = self.spec iflags = [] @@ -105,36 +109,26 @@ class ArpackNg(Package): return find_libraries(libraries, root=self.prefix, shared=True, recursive=True) - @when("@:3.7.0 %gcc@10:") - def setup_build_environment(self, env): - # version up to and including 3.7.0 are not ported to gcc 10 - # https://github.com/opencollab/arpack-ng/issues/242 - env.set("FFLAGS", "-fallow-argument-mismatch") - - @when("@3.4.0:") - def install(self, spec, prefix): - options = ["-DEXAMPLES=ON"] - options.extend(std_cmake_args) - options.append("-DCMAKE_INSTALL_NAME_DIR:PATH=%s/lib" % prefix) - - # Make sure we use Spack's blas/lapack: +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + spec = self.spec lapack_libs = spec["lapack"].libs.joined(";") blas_libs = spec["blas"].libs.joined(";") - options.extend( - [ - "-DLAPACK_FOUND=true", - "-DLAPACK_INCLUDE_DIRS={0}".format(spec["lapack"].prefix.include), - "-DLAPACK_LIBRARIES={0}".format(lapack_libs), - "-DBLAS_FOUND=true", - "-DBLAS_INCLUDE_DIRS={0}".format(spec["blas"].prefix.include), - "-DBLAS_LIBRARIES={0}".format(blas_libs), - ] - ) - - if "+mpi" in spec: - options.append("-DMPI=ON") + options = [ + self.define("EXAMPLES", "ON"), + self.define("CMAKE_INSTALL_NAME_DIR", self.prefix.lib), + self.define("LAPACK_FOUND", True), + self.define("LAPACK_INCLUDE_DIRS", spec["lapack"].prefix.include), + self.define("LAPACK_LIBRARIES", lapack_libs), + self.define("BLAS_FOUND", True), + self.define("BLAS_INCLUDE_DIRS", spec["blas"].prefix.include), + self.define("BLAS_LIBRARIES", blas_libs), + self.define_from_variant("MPI", "mpi"), + self.define_from_variant("BUILD_SHARED_LIBS", "shared"), + self.define("CMAKE_POSITION_INDEPENDENT_CODE", True), + ] # If 64-bit BLAS is used: if ( @@ -144,41 +138,28 @@ class ArpackNg(Package): ): options.append("-DINTERFACE64=1") - if "+shared" in spec: - options.append("-DBUILD_SHARED_LIBS=ON") - else: - options.append("-DBUILD_SHARED_LIBS=OFF") - options.append("-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=true") - - cmake(".", *options) - make() - if self.run_tests: - make("test") - make("install") - - @when("@3.3.0") - def install(self, spec, prefix): - # Apparently autotools are not bootstrapped - which("libtoolize")() - bootstrap = Executable("./bootstrap") - - options = ["--prefix=%s" % prefix] + return options - if "+mpi" in spec: - options.extend(["--enable-mpi", "F77=%s" % spec["mpi"].mpif77]) - options.extend( - [ +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): + def configure_args(self): + spec = self.spec + options = ( + self.enable_or_disable("mpi") + + [ "--with-blas={0}".format(spec["blas"].libs.ld_flags), "--with-lapack={0}".format(spec["lapack"].libs.ld_flags), ] + + self.enable_or_disable("shared") ) - if "+shared" not in spec: - options.append("--enable-shared=no") - - bootstrap() - configure(*options) - make() - if self.run_tests: - make("check") - make("install") + + if "+mpi" in spec: + options.append("F77={0}".format(spec["mpi"].mpif77)) + + return options + + @when("@:3.7.0 %gcc@10:") + def setup_build_environment(self, env): + # version up to and including 3.7.0 are not ported to gcc 10 + # https://github.com/opencollab/arpack-ng/issues/242 + env.set("FFLAGS", "-fallow-argument-mismatch") diff --git a/var/spack/repos/builtin/packages/binutils/package.py b/var/spack/repos/builtin/packages/binutils/package.py index 9ba963b019..f5a9ae7fd9 100644 --- a/var/spack/repos/builtin/packages/binutils/package.py +++ b/var/spack/repos/builtin/packages/binutils/package.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) import re +import spack.build_systems.autotools from spack.package import * @@ -40,13 +41,21 @@ class Binutils(AutotoolsPackage, GNUMirrorPackage): version("2.20.1", sha256="71d37c96451333c5c0b84b170169fdcb138bbb27397dc06281905d9717c8ed64") variant("plugins", default=True, description="enable plugins, needed for gold linker") - variant("gold", default=False, description="build the gold linker") + # When you build ld.gold you automatically get ld, even when you add the + # --disable-ld flag + variant("gold", default=False, when="+ld", description="build the gold linker") variant("libiberty", default=False, description="Also install libiberty.") variant("nls", default=True, description="Enable Native Language Support") variant("headers", default=False, description="Install extra headers (e.g. ELF)") variant("lto", default=False, description="Enable lto.") variant("ld", default=False, description="Enable ld.") - variant("gas", default=False, description="Enable as assembler.") + # When you build binutils with ~ld and +gas and load it in your PATH, you + # may end up with incompatibilities between a potentially older system ld + # and a recent assembler. For instance the linker on ubuntu 16.04 from + # binutils 2.26 and the assembler from binutils 2.36.1 will result in: + # "unable to initialize decompress status for section .debug_info" + # when compiling with debug symbols on gcc. + variant("gas", default=False, when="+ld", description="Enable as assembler.") variant("interwork", default=False, description="Enable interwork.") variant( "libs", @@ -81,83 +90,12 @@ class Binutils(AutotoolsPackage, GNUMirrorPackage): conflicts("+gold", when="platform=darwin", msg="Binutils cannot build linkers on macOS") - # When you build binutils with ~ld and +gas and load it in your PATH, you - # may end up with incompatibilities between a potentially older system ld - # and a recent assembler. For instance the linker on ubuntu 16.04 from - # binutils 2.26 and the assembler from binutils 2.36.1 will result in: - # "unable to initialize decompress status for section .debug_info" - # when compiling with debug symbols on gcc. - conflicts("+gas", "~ld", msg="Assembler not always compatible with system ld") - - # When you build ld.gold you automatically get ld, even when you add the - # --disable-ld flag - conflicts("~ld", "+gold") - @classmethod def determine_version(cls, exe): output = Executable(exe)("--version", output=str, error=str) match = re.search(r"GNU (nm|readelf).* (\S+)", output) return Version(match.group(2)).dotted.up_to(3) if match else None - def setup_build_environment(self, env): - - if self.spec.satisfies("%cce"): - env.append_flags("LDFLAGS", "-Wl,-z,muldefs") - - if "+nls" in self.spec: - env.append_flags("LDFLAGS", "-lintl") - - def configure_args(self): - spec = self.spec - - args = [ - "--disable-dependency-tracking", - "--disable-werror", - "--enable-multilib", - "--enable-64-bit-bfd", - "--enable-targets=all", - "--with-system-zlib", - "--with-sysroot=/", - ] - - args += self.enable_or_disable("libs") - args += self.enable_or_disable("lto") - args += self.enable_or_disable("ld") - args += self.enable_or_disable("gas") - args += self.enable_or_disable("interwork") - args += self.enable_or_disable("gold") - args += self.enable_or_disable("plugins") - - if "+libiberty" in spec: - args.append("--enable-install-libiberty") - else: - args.append("--disable-install-libiberty") - - if "+nls" in spec: - args.append("--enable-nls") - else: - args.append("--disable-nls") - - # To avoid namespace collisions with Darwin/BSD system tools, - # prefix executables with "g", e.g., gar, gnm; see Homebrew - # https://github.com/Homebrew/homebrew-core/blob/master/Formula/binutils.rb - if spec.satisfies("platform=darwin"): - args.append("--program-prefix=g") - - return args - - @run_after("install") - def install_headers(self): - # some packages (like TAU) need the ELF headers, so install them - # as a subdirectory in include/extras - if "+headers" in self.spec: - extradir = join_path(self.prefix.include, "extra") - mkdirp(extradir) - # grab the full binutils set of headers - install_tree("include", extradir) - # also grab the headers from the bfd directory - install(join_path(self.build_directory, "bfd", "*.h"), extradir) - def flag_handler(self, name, flags): spec = self.spec # Use a separate variable for injecting flags. This way, installing @@ -204,3 +142,55 @@ class Binutils(AutotoolsPackage, GNUMirrorPackage): self.run_test( exe, "--version", expected, installed=True, purpose=reason, skip_missing=True ) + + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): + def configure_args(self): + args = [ + "--disable-dependency-tracking", + "--disable-werror", + "--enable-multilib", + "--enable-64-bit-bfd", + "--enable-targets=all", + "--with-system-zlib", + "--with-sysroot=/", + ] + args += self.enable_or_disable("libs") + args += self.enable_or_disable("lto") + args += self.enable_or_disable("ld") + args += self.enable_or_disable("gas") + args += self.enable_or_disable("interwork") + args += self.enable_or_disable("gold") + args += self.enable_or_disable("nls") + args += self.enable_or_disable("plugins") + + if "+libiberty" in self.spec: + args.append("--enable-install-libiberty") + else: + args.append("--disable-install-libiberty") + + # To avoid namespace collisions with Darwin/BSD system tools, + # prefix executables with "g", e.g., gar, gnm; see Homebrew + # https://github.com/Homebrew/homebrew-core/blob/master/Formula/binutils.rb + if self.spec.satisfies("platform=darwin"): + args.append("--program-prefix=g") + + return args + + @run_after("install", when="+headers") + def install_headers(self): + # some packages (like TAU) need the ELF headers, so install them + # as a subdirectory in include/extras + extradir = join_path(self.prefix.include, "extra") + mkdirp(extradir) + # grab the full binutils set of headers + install_tree("include", extradir) + # also grab the headers from the bfd directory + install(join_path(self.build_directory, "bfd", "*.h"), extradir) + + def setup_build_environment(self, env): + if self.spec.satisfies("%cce"): + env.append_flags("LDFLAGS", "-Wl,-z,muldefs") + + if "+nls" in self.spec: + env.append_flags("LDFLAGS", "-lintl") diff --git a/var/spack/repos/builtin/packages/clara/package.py b/var/spack/repos/builtin/packages/clara/package.py index 3eaea61ce5..fadec16835 100644 --- a/var/spack/repos/builtin/packages/clara/package.py +++ b/var/spack/repos/builtin/packages/clara/package.py @@ -2,19 +2,26 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.generic from spack.package import * -class Clara(CMakePackage): +class Clara(CMakePackage, Package): """A simple to use, composable, command line parser for C++ 11 - and beyond.""" + and beyond. + """ homepage = "https://github.com/catchorg/Clara" url = "https://github.com/catchorg/Clara/archive/v1.1.5.tar.gz" maintainers = ["bvanessen"] + build_system( + conditional("generic", when="+single_header"), + conditional("cmake", when="~single_header"), + default="generic", + ) + variant("single_header", default=True, description="Install a single header only.") version("1.1.5", sha256="767dc1718e53678cbea00977adcd0a8a195802a505aec3c537664cf25a173142") @@ -24,15 +31,8 @@ class Clara(CMakePackage): version("1.1.1", sha256="10915a49a94d371f05af360d40e9cc9615ab86f200d261edf196a8ddd7efa7f8") version("1.1.0", sha256="29ca29d843150aabad702356f79009f5b30dda05ac9674a064362b7edcba5477") - @when("+single_header") - def cmake(self, spec, prefix): - pass - - @when("+single_header") - def build(self, spec, prefix): - pass - @when("+single_header") - def install(self, spec, prefix): +class GenericBuilder(spack.build_systems.generic.GenericBuilder): + def install(self, pkg, spec, prefix): mkdirp(prefix.include) install_tree("single_include", prefix.include) diff --git a/var/spack/repos/builtin/packages/flex/package.py b/var/spack/repos/builtin/packages/flex/package.py index 8f3d496bb5..f0754ec86d 100644 --- a/var/spack/repos/builtin/packages/flex/package.py +++ b/var/spack/repos/builtin/packages/flex/package.py @@ -113,20 +113,19 @@ class Flex(AutotoolsPackage): args += self.enable_or_disable("nls") return args - @run_after("install") + @run_after("install", when="+lex") def symlink_lex(self): """Install symlinks for lex compatibility.""" - if self.spec.satisfies("+lex"): - dso = dso_suffix - for dir, flex, lex in ( - (self.prefix.bin, "flex", "lex"), - (self.prefix.lib, "libfl.a", "libl.a"), - (self.prefix.lib, "libfl." + dso, "libl." + dso), - (self.prefix.lib64, "libfl.a", "libl.a"), - (self.prefix.lib64, "libfl." + dso, "libl." + dso), - ): - - if os.path.isdir(dir): - with working_dir(dir): - if os.path.isfile(flex) and not os.path.lexists(lex): - symlink(flex, lex) + dso = dso_suffix + for dir, flex, lex in ( + (self.prefix.bin, "flex", "lex"), + (self.prefix.lib, "libfl.a", "libl.a"), + (self.prefix.lib, "libfl." + dso, "libl." + dso), + (self.prefix.lib64, "libfl.a", "libl.a"), + (self.prefix.lib64, "libfl." + dso, "libl." + dso), + ): + + if os.path.isdir(dir): + with working_dir(dir): + if os.path.isfile(flex) and not os.path.lexists(lex): + symlink(flex, lex) diff --git a/var/spack/repos/builtin/packages/gobject-introspection/package.py b/var/spack/repos/builtin/packages/gobject-introspection/package.py index 9ea90684e8..624d0941f8 100644 --- a/var/spack/repos/builtin/packages/gobject-introspection/package.py +++ b/var/spack/repos/builtin/packages/gobject-introspection/package.py @@ -2,15 +2,16 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.autotools import spack.hooks.sbang as sbang from spack.package import * -class GobjectIntrospection(MesonPackage): +class GobjectIntrospection(MesonPackage, AutotoolsPackage): """The GObject Introspection is used to describe the program APIs and collect them in a uniform, machine readable format.Cairo is a 2D graphics - library with support for multiple output""" + library with support for multiple output + """ homepage = "https://wiki.gnome.org/Projects/GObjectIntrospection" url = "https://download.gnome.org/sources/gobject-introspection/1.72/gobject-introspection-1.72.0.tar.xz" @@ -22,6 +23,12 @@ class GobjectIntrospection(MesonPackage): version("1.49.2", sha256="73d59470ba1a546b293f54d023fd09cca03a951005745d86d586b9e3a8dde9ac") version("1.48.0", sha256="fa275aaccdbfc91ec0bc9a6fd0562051acdba731e7d584b64a277fec60e75877") + build_system( + conditional("autotools", when="@:1.60"), + conditional("meson", when="@1.61:"), + default="meson", + ) + depends_on("pkgconfig", type="build") depends_on("bison", type="build") depends_on("flex", type="build") @@ -94,22 +101,9 @@ class GobjectIntrospection(MesonPackage): def parallel(self): return not self.spec.satisfies("%fj") - def meson_args(self): - return [] - @when("@:1.60") - def meson(self, spec, prefix): - """Run the AutotoolsPackage configure phase""" - configure("--prefix={0}".format(prefix)) - - @when("@:1.60") - def build(self, spec, prefix): - """Run the AutotoolsPackage build phase""" +class AutotoolsBuilderPackage(spack.build_systems.autotools.AutotoolsBuilder): + @run_before("build") + def filter_file_to_avoid_overly_long_shebangs(self): # we need to filter this file to avoid an overly long hashbang line filter_file("#!/usr/bin/env @PYTHON@", "#!@PYTHON@", "tools/g-ir-tool-template.in") - make() - - @when("@:1.60") - def install(self, spec, prefix): - """Run the AutotoolsPackage install phase""" - make("install", parallel=False) diff --git a/var/spack/repos/builtin/packages/harfbuzz/package.py b/var/spack/repos/builtin/packages/harfbuzz/package.py index 0400c0a21a..e746ed6fbf 100644 --- a/var/spack/repos/builtin/packages/harfbuzz/package.py +++ b/var/spack/repos/builtin/packages/harfbuzz/package.py @@ -2,11 +2,12 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.autotools +import spack.build_systems.meson from spack.package import * -class Harfbuzz(MesonPackage): +class Harfbuzz(MesonPackage, AutotoolsPackage): """The Harfbuzz package contains an OpenType text shaping engine.""" homepage = "https://github.com/harfbuzz/harfbuzz" @@ -14,6 +15,10 @@ class Harfbuzz(MesonPackage): git = "https://github.com/harfbuzz/harfbuzz.git" version("5.1.0", sha256="2edb95db668781aaa8d60959d21be2ff80085f31b12053cdd660d9a50ce84f05") + build_system( + conditional("autotools", when="@:2.9"), conditional("meson", when="@3:"), default="meson" + ) + version("4.2.1", sha256="bd17916513829aeff961359a5ccebba6de2f4bf37a91faee3ac29c120e3d7ee1") version("4.1.0", sha256="f7984ff4241d4d135f318a93aa902d910a170a8265b7eaf93b5d9a504eed40c8") version("4.0.1", sha256="98f68777272db6cd7a3d5152bac75083cd52a26176d87bc04c8b3929d33bce49") @@ -99,31 +104,33 @@ class Harfbuzz(MesonPackage): def setup_run_environment(self, env): env.prepend_path("GI_TYPELIB_PATH", join_path(self.prefix.lib, "girepository-1.0")) - def setup_dependent_build_environment(self, env, dependent_spec): + def setup_dependent_run_environment(self, env, dependent_spec): env.prepend_path("XDG_DATA_DIRS", self.prefix.share) env.prepend_path("GI_TYPELIB_PATH", join_path(self.prefix.lib, "girepository-1.0")) - def setup_dependent_run_environment(self, env, dependent_spec): + def patch(self): + change_sed_delimiter("@", ";", "src/Makefile.in") + + +class SetupEnvironment(object): + def setup_dependent_build_environment(self, env, dependent_spec): env.prepend_path("XDG_DATA_DIRS", self.prefix.share) env.prepend_path("GI_TYPELIB_PATH", join_path(self.prefix.lib, "girepository-1.0")) - def meson_args(self): - args = [] - - # disable building of gtk-doc files following #9885 and #9771 - args.append("-Ddocs=disabled") - args.append( - "-Dgraphite2=" + ("enabled" if self.spec.satisfies("+graphite2") else "disabled") - ) - if "+coretext" in self.spec: - args.append("-Dcoretext=enabled") - elif "~coretext" in self.spec: - args.append("-Dcoretext=disabled") +class MesonBuilder(spack.build_systems.meson.MesonBuilder, SetupEnvironment): + def meson_args(self): + graphite2 = "enabled" if self.pkg.spec.satisfies("+graphite2") else "disabled" + coretext = "enabled" if self.pkg.spec.satisfies("+coretext") else "disabled" + return [ + # disable building of gtk-doc files following #9885 and #9771 + "-Ddocs=disabled", + "-Dgraphite2={0}".format(graphite2), + "-Dcoretext={0}".format(coretext), + ] - return args - @when("@:2.9") +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder, SetupEnvironment): def configure_args(self): args = [] @@ -135,28 +142,6 @@ class Harfbuzz(MesonPackage): args.append("GTKDOC_MKPDF={0}".format(true)) args.append("GTKDOC_REBASE={0}".format(true)) args.extend(self.with_or_without("graphite2")) - - if "+coretext" in self.spec: - args.append("--with-coretext") - elif "~coretext" in self.spec: - args.append("--without-coretext") + args.extend(self.with_or_without("coretext")) return args - - def patch(self): - change_sed_delimiter("@", ";", "src/Makefile.in") - - @when("@:2.9") - def meson(self, spec, prefix): - """Run the AutotoolsPackage configure phase""" - configure("--prefix=" + prefix, *self.configure_args()) - - @when("@:2.9") - def build(self, spec, prefix): - """Run the AutotoolsPackage build phase""" - make() - - @when("@:2.9") - def install(self, spec, prefix): - """Run the AutotoolsPackage install phase""" - make("install") diff --git a/var/spack/repos/builtin/packages/ibm-databroker/package.py b/var/spack/repos/builtin/packages/ibm-databroker/package.py index ae80f92439..9c0516844b 100644 --- a/var/spack/repos/builtin/packages/ibm-databroker/package.py +++ b/var/spack/repos/builtin/packages/ibm-databroker/package.py @@ -7,7 +7,7 @@ from spack.package import * -class IbmDatabroker(CMakePackage, PythonPackage): +class IbmDatabroker(CMakePackage, PythonExtension): """The Data Broker (DBR) is a distributed, in-memory container of key-value stores enabling applications in a workflow to exchange data through one or more shared namespaces. Thanks to a small set of primitives, applications diff --git a/var/spack/repos/builtin/packages/intel-tbb/package.py b/var/spack/repos/builtin/packages/intel-tbb/package.py index 1bc13ac1a3..8921604b08 100644 --- a/var/spack/repos/builtin/packages/intel-tbb/package.py +++ b/var/spack/repos/builtin/packages/intel-tbb/package.py @@ -8,10 +8,12 @@ import inspect import platform import sys +import spack.build_systems.cmake +import spack.build_systems.makefile from spack.package import * -class IntelTbb(CMakePackage): +class IntelTbb(CMakePackage, MakefilePackage): """Widely used C++ template library for task parallelism. Intel Threading Building Blocks (Intel TBB) lets you easily write parallel C++ programs that take full advantage of multicore performance, that are @@ -74,6 +76,12 @@ class IntelTbb(CMakePackage): version("4.4.1", sha256="05737bf6dd220b31aad63d77ca59c742271f81b4cc6643aa6f93d37450ae32b5") version("4.4", sha256="93c74b6054c69c86fa49d0fce7c50061fc907cb198a7237b8dd058298fd40c0e") + build_system( + conditional("makefile", when="@:2020.3"), + conditional("cmake", when="@2021:"), + default="cmake", + ) + provides("tbb") # Clang builds incorrectly determine GCC version which in turn incorrectly @@ -161,12 +169,54 @@ class IntelTbb(CMakePackage): name = "{0}".format(version) return url.format(name) + @property + def libs(self): + shared = True if "+shared" in self.spec else False + return find_libraries("libtbb*", root=self.prefix, shared=shared, recursive=True) + + +class SetupEnvironment(object): # We set OS here in case the user has it set to something else # that TBB doesn't expect. def setup_build_environment(self, env): env.set("OS", platform.system()) - @when("@:2020.3") + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder, SetupEnvironment): + def cmake_args(self): + spec = self.spec + options = [ + self.define("CMAKE_HWLOC_2_INCLUDE_PATH", spec["hwloc"].prefix.include), + self.define("CMAKE_HWLOC_2_LIBRARY_PATH", spec["hwloc"].libs), + self.define("-DTBB_CPF", True), + self.define("TBB_STRICT", False), + ] + if spec.variants["cxxstd"].value != "default": + options.append(self.define("CMAKE_CXX_STANDARD", spec.variants["cxxstd"].value)) + return options + + @run_after("install") + def install_pkgconfig(self): + # pkg-config generation is introduced in May 5, 2021. + # It must not be overwritten by spack-generated tbb.pc. + # https://github.com/oneapi-src/oneTBB/commit/478de5b1887c928e52f029d706af6ea640a877be + if self.spec.satisfies("@:2021.2.0", strict=True): + mkdirp(self.prefix.lib.pkgconfig) + + with open(join_path(self.prefix.lib.pkgconfig, "tbb.pc"), "w") as f: + f.write("prefix={0}\n".format(self.prefix)) + f.write("exec_prefix=${prefix}\n") + f.write("libdir={0}\n".format(self.prefix.lib)) + f.write("includedir={0}\n".format(self.prefix.include)) + f.write("\n") + f.write("Name: Threading Building Blocks\n") + f.write("Description: Intel's parallelism library for C++\n") + f.write("Version: {0}\n".format(self.spec.version)) + f.write("Cflags: -I${includedir}\n") + f.write("Libs: -L${libdir} -ltbb -latomic\n") + + +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder, SetupEnvironment): def coerce_to_spack(self, tbb_build_subdir): for compiler in ["icc", "gcc", "clang"]: fs = glob.glob(join_path(tbb_build_subdir, "*.%s.inc" % compiler)) @@ -183,16 +233,7 @@ class IntelTbb(CMakePackage): else: of.write(lin) - @when("@:2020.3") - def cmake(self, spec, prefix): - return - - @when("@:2020.3") - def cmake_args(self): - return - - @when("@:2020.3") - def build(self, spec, prefix): + def build(self, pkg, spec, prefix): # Deactivate use of RTM with GCC when on an OS with a very old # assembler. if ( @@ -239,8 +280,7 @@ class IntelTbb(CMakePackage): make_opts.append("compiler={0}".format(tbb_compiler)) make(*make_opts) - @when("@:2020.3") - def install(self, spec, prefix): + def install(self, pkg, spec, prefix): mkdirp(prefix) mkdirp(prefix.lib) @@ -267,46 +307,8 @@ class IntelTbb(CMakePackage): with working_dir(join_path(self.stage.source_path, "cmake")): inspect.getmodule(self).cmake(*cmake_args) - @when("@:2020.3") @run_after("install") def darwin_fix(self): # Replace @rpath in ids with full path if sys.platform == "darwin": fix_darwin_install_name(self.prefix.lib) - - @property - def libs(self): - shared = True if "+shared" in self.spec else False - return find_libraries("libtbb*", root=self.prefix, shared=shared, recursive=True) - - @when("@2021.1.1:") - def cmake_args(self): - spec = self.spec - options = [] - options.append("-DCMAKE_HWLOC_2_INCLUDE_PATH=%s" % spec["hwloc"].prefix.include) - options.append("-DCMAKE_HWLOC_2_LIBRARY_PATH=%s" % spec["hwloc"].libs) - options.append("-DTBB_CPF=ON") - options.append("-DTBB_STRICT=OFF") - if spec.variants["cxxstd"].value != "default": - options.append("-DCMAKE_CXX_STANDARD=%s" % spec.variants["cxxstd"].value) - return options - - @run_after("install") - def install_pkgconfig(self): - # pkg-config generation is introduced in May 5, 2021. - # It must not be overwritten by spack-generated tbb.pc. - # https://github.com/oneapi-src/oneTBB/commit/478de5b1887c928e52f029d706af6ea640a877be - if self.spec.satisfies("@:2021.2.0", strict=True): - mkdirp(self.prefix.lib.pkgconfig) - - with open(join_path(self.prefix.lib.pkgconfig, "tbb.pc"), "w") as f: - f.write("prefix={0}\n".format(self.prefix)) - f.write("exec_prefix=${prefix}\n") - f.write("libdir={0}\n".format(self.prefix.lib)) - f.write("includedir={0}\n".format(self.prefix.include)) - f.write("\n") - f.write("Name: Threading Building Blocks\n") - f.write("Description: Intel's parallelism library for C++\n") - f.write("Version: {0}\n".format(self.spec.version)) - f.write("Cflags: -I${includedir}\n") - f.write("Libs: -L${libdir} -ltbb -latomic\n") diff --git a/var/spack/repos/builtin/packages/json-c/package.py b/var/spack/repos/builtin/packages/json-c/package.py index 559039f21d..a62e0323ea 100644 --- a/var/spack/repos/builtin/packages/json-c/package.py +++ b/var/spack/repos/builtin/packages/json-c/package.py @@ -6,7 +6,7 @@ from spack.package import * -class JsonC(CMakePackage): +class JsonC(CMakePackage, AutotoolsPackage): """A JSON implementation in C.""" homepage = "https://github.com/json-c/json-c/wiki" @@ -20,7 +20,13 @@ class JsonC(CMakePackage): version("0.12", sha256="000c01b2b3f82dcb4261751eb71f1b084404fb7d6a282f06074d3c17078b9f3f") version("0.11", sha256="28dfc65145dc0d4df1dfe7701ac173c4e5f9347176c8983edbfac9149494448c") - depends_on("autoconf", when="@:0.13.1", type="build") + build_system( + conditional("cmake", when="@0.14:"), + conditional("autotools", when="@:0.13.1"), + default="cmake", + ) + + depends_on("autoconf", when="build_system=autotools", type="build") parallel = False @@ -32,19 +38,6 @@ class JsonC(CMakePackage): "Makefile.in", ) - @when("@:0.13.1") - def cmake(self, spec, prefix): - configure_args = ["--prefix=" + prefix] - configure(*configure_args) - - @when("@:0.13.1") - def build(self, spec, prefix): - make() - - @when("@:0.13.1") - def install(self, spec, prefix): - make("install") - @when("%cce@11.0.3:") def patch(self): filter_file("-Werror", "", "CMakeLists.txt") diff --git a/var/spack/repos/builtin/packages/libtree/package.py b/var/spack/repos/builtin/packages/libtree/package.py index 572f459b43..a1c97dfd76 100644 --- a/var/spack/repos/builtin/packages/libtree/package.py +++ b/var/spack/repos/builtin/packages/libtree/package.py @@ -2,11 +2,12 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.cmake +import spack.build_systems.makefile from spack.package import * -class Libtree(MakefilePackage): +class Libtree(MakefilePackage, CMakePackage): """ldd as a tree""" homepage = "https://github.com/haampie/libtree" @@ -35,6 +36,10 @@ class Libtree(MakefilePackage): version("1.0.4", sha256="b15a54b6f388b8bd8636e288fcb581029f1e65353660387b0096a554ad8e9e45") version("1.0.3", sha256="67ce886c191d50959a5727246cdb04af38872cd811c9ed4e3822f77a8f40b20b") + build_system( + conditional("cmake", when="@:2"), conditional("makefile", when="@3:"), default="makefile" + ) + def url_for_version(self, version): if version < Version("2.0.0"): return ( @@ -45,13 +50,8 @@ class Libtree(MakefilePackage): return "https://github.com/haampie/libtree/archive/refs/tags/v{0}.tar.gz".format(version) - # Version 3.x (Makefile) - @when("@3:") - def install(self, spec, prefix): - make("install", "PREFIX=" + prefix) - # Version 2.x and earlier (CMake) - with when("@:2"): + with when("build_system=cmake"): variant("chrpath", default=False, description="Use chrpath for deployment") variant("strip", default=False, description="Use binutils strip for deployment") variant( @@ -70,23 +70,19 @@ class Libtree(MakefilePackage): depends_on("cxxopts", when="@2.0.0:2", type="build") depends_on("elfio@:3.9", when="@2.0.0:2", type="build") + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): def cmake_args(self): - tests_enabled = "ON" if self.run_tests else "OFF" + tests_enabled = "ON" if self.pkg.run_tests else "OFF" if self.spec.satisfies("@2.0:"): tests_define = "LIBTREE_BUILD_TESTS" else: tests_define = "BUILD_TESTING" - return [CMakePackage.define(tests_define, tests_enabled)] + return [self.define(tests_define, tests_enabled)] - @when("@:2") - def edit(self, spec, prefix): - options = CMakePackage._std_args(self) + self.cmake_args() - options.append(self.stage.source_path) - with working_dir(self.build_directory): - cmake(*options) - @when("@:2") - def check(self): - with working_dir(self.build_directory): - ctest("--output-on-failure") +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder): + @property + def install_targets(self): + return ["install", "PREFIX=" + self.prefix] diff --git a/var/spack/repos/builtin/packages/libxkbcommon/package.py b/var/spack/repos/builtin/packages/libxkbcommon/package.py index b7003ab5a9..054f05e9bd 100644 --- a/var/spack/repos/builtin/packages/libxkbcommon/package.py +++ b/var/spack/repos/builtin/packages/libxkbcommon/package.py @@ -2,11 +2,12 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.autotools +import spack.build_systems.meson from spack.package import * -class Libxkbcommon(MesonPackage): +class Libxkbcommon(MesonPackage, AutotoolsPackage): """xkbcommon is a library to handle keyboard descriptions, including loading them from disk, parsing them and handling their state. It's mainly meant for client toolkits, window systems, and other system @@ -15,6 +16,10 @@ class Libxkbcommon(MesonPackage): homepage = "https://xkbcommon.org/" url = "https://xkbcommon.org/download/libxkbcommon-0.8.2.tar.xz" + build_system( + conditional("meson", when="@0.9:"), conditional("autotools", when="@:0.8"), default="meson" + ) + version("1.4.0", sha256="106cec5263f9100a7e79b5f7220f889bc78e7d7ffc55d2b6fdb1efefb8024031") version( "0.8.2", @@ -44,6 +49,8 @@ class Libxkbcommon(MesonPackage): depends_on("wayland@1.2.0:", when="+wayland") depends_on("wayland-protocols@1.7:", when="+wayland") + +class MesonBuilder(spack.build_systems.meson.MesonBuilder): def meson_args(self): return [ "-Dxkb-config-root={0}".format(self.spec["xkbdata"].prefix), @@ -51,26 +58,11 @@ class Libxkbcommon(MesonPackage): "-Denable-wayland=" + str(self.spec.satisfies("+wayland")), ] - @when("@:0.8") + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): def configure_args(self): """Configure arguments are passed using meson_args functions""" return [ "--with-xkb-config-root={0}".format(self.spec["xkbdata"].prefix), "--disable-docs", - "--" + ("en" if self.spec.satisfies("+wayland") else "dis") + "able-wayland", - ] - - @when("@:0.8") - def meson(self, spec, prefix): - """Run the AutotoolsPackage configure phase in source_path""" - configure("--prefix=" + prefix, *self.configure_args()) - - @when("@:0.8") - def build(self, spec, prefix): - """Run the AutotoolsPackage build phase in source_path""" - make() - - @when("@:0.8") - def install(self, spec, prefix): - """Run the AutotoolsPackage install phase in source_path""" - make("install") + ] + self.enable_or_disable("wayland") diff --git a/var/spack/repos/builtin/packages/lua/package.py b/var/spack/repos/builtin/packages/lua/package.py index cd314f3df5..4894ab2b91 100644 --- a/var/spack/repos/builtin/packages/lua/package.py +++ b/var/spack/repos/builtin/packages/lua/package.py @@ -26,11 +26,6 @@ class LuaImplPackage(MakefilePackage): description="Fetcher to use in the LuaRocks package manager", ) - phases = MakefilePackage.phases + ["add_luarocks"] - #: This attribute is used in UI queries that need to know the build - #: system base class - build_system_class = "LuaImplPackage" - lua_version_override = None def __init__(self, *args, **kwargs): @@ -105,7 +100,9 @@ class LuaImplPackage(MakefilePackage): ) symlink(real_lib, "liblua" + ext) - def add_luarocks(self, spec, prefix): + @run_after("install") + def add_luarocks(self): + prefix = self.spec.prefix with working_dir(os.path.join("luarocks", "luarocks")): configure("--prefix=" + prefix, "--with-lua=" + prefix) make("build") diff --git a/var/spack/repos/builtin/packages/mesa/package.py b/var/spack/repos/builtin/packages/mesa/package.py index e82bdc3764..caa47f8d64 100644 --- a/var/spack/repos/builtin/packages/mesa/package.py +++ b/var/spack/repos/builtin/packages/mesa/package.py @@ -2,9 +2,9 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import sys +import spack.build_systems.meson from spack.package import * @@ -158,6 +158,28 @@ class Mesa(MesonPackage): flags.append("-std=c99") return super(Mesa, self).flag_handler(name, flags) + @property + def libglx_headers(self): + return find_headers("GL/glx", root=self.spec.prefix.include, recursive=False) + + @property + def libglx_libs(self): + return find_libraries("libGL", root=self.spec.prefix, recursive=True) + + @property + def libosmesa_headers(self): + return find_headers("GL/osmesa", root=self.spec.prefix.include, recursive=False) + + @property + def libosmesa_libs(self): + if "platform=windows" in self.spec: + lib_name = "osmesa" + else: + lib_name = "libOSMesa" + return find_libraries(lib_name, root=self.spec.prefix, recursive=True) + + +class MesonBuilder(spack.build_systems.meson.MesonBuilder): def meson_args(self): spec = self.spec args = [ @@ -274,23 +296,3 @@ class Mesa(MesonPackage): args.append("-Ddri-drivers=" + ",".join(args_dri_drivers)) return args - - @property - def libglx_headers(self): - return find_headers("GL/glx", root=self.spec.prefix.include, recursive=False) - - @property - def libglx_libs(self): - return find_libraries("libGL", root=self.spec.prefix, recursive=True) - - @property - def libosmesa_headers(self): - return find_headers("GL/osmesa", root=self.spec.prefix.include, recursive=False) - - @property - def libosmesa_libs(self): - if "platform=windows" in self.spec: - lib_name = "osmesa" - else: - lib_name = "libOSMesa" - return find_libraries(lib_name, root=self.spec.prefix, recursive=True) diff --git a/var/spack/repos/builtin/packages/metis/gklib_path.patch b/var/spack/repos/builtin/packages/metis/gklib_path.patch new file mode 100644 index 0000000000..2cee12d044 --- /dev/null +++ b/var/spack/repos/builtin/packages/metis/gklib_path.patch @@ -0,0 +1,11 @@ +--- a/CMakeLists.txt 2022-07-20 21:17:20.352231603 +0200 ++++ b/CMakeLists.txt 2022-07-20 21:19:28.998269385 +0200 +@@ -1,7 +1,7 @@ + cmake_minimum_required(VERSION 2.8) + project(METIS) + +-set(GKLIB_PATH "GKlib" CACHE PATH "path to GKlib") ++set(GKLIB_PATH "${CMAKE_SOURCE_DIR}/GKlib" CACHE PATH "path to GKlib") + set(SHARED FALSE CACHE BOOL "build a shared library") + + if(MSVC) diff --git a/var/spack/repos/builtin/packages/metis/package.py b/var/spack/repos/builtin/packages/metis/package.py index edf99be5ee..da6e798783 100644 --- a/var/spack/repos/builtin/packages/metis/package.py +++ b/var/spack/repos/builtin/packages/metis/package.py @@ -2,20 +2,22 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - - import os import sys +import spack.build_systems.cmake +import spack.build_systems.makefile from spack.package import * -class Metis(Package): +class Metis(CMakePackage, MakefilePackage): """METIS is a set of serial programs for partitioning graphs, partitioning finite element meshes, and producing fill reducing orderings for sparse - matrices. The algorithms implemented in METIS are based on the - multilevel recursive-bisection, multilevel k-way, and multi-constraint - partitioning schemes.""" + matrices. + + The algorithms implemented in METIS are based on the multilevel + recursive-bisection, multilevel k-way, and multi-constraint partitioning schemes. + """ homepage = "http://glaros.dtc.umn.edu/gkhome/metis/metis/overview" url = "http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/metis-5.1.0.tar.gz" @@ -27,52 +29,29 @@ class Metis(Package): version("5.1.0", sha256="76faebe03f6c963127dbb73c13eab58c9a3faeae48779f049066a21c087c5db2") version("4.0.3", sha256="5efa35de80703c1b2c4d0de080fafbcf4e0d363a21149a1ad2f96e0144841a55") - variant("shared", default=True, description="Enables the build of shared libraries.") - variant("gdb", default=False, description="Enables gdb support (version 5+).") - variant("int64", default=False, description="Sets the bit width of METIS's index type to 64.") - variant("real64", default=False, description="Sets the bit width of METIS's real type to 64.") - - # For Metis version 5:, the build system is CMake, provide the - # `build_type` variant. - variant( - "build_type", - default="Release", - description="The build type for the installation (only Debug or" - " Release allowed for version 4).", - values=("Debug", "Release", "RelWithDebInfo", "MinSizeRel"), + build_system( + conditional("cmake", when="@5:"), conditional("makefile", when="@:4"), default="cmake" ) + variant("shared", default=True, description="Build shared libraries") + with when("build_system=cmake"): + variant("gdb", default=False, description="Enable gdb support") + variant("int64", default=False, description="Use index type of 64 bit") + variant("real64", default=False, description="Use real type of 64 bit") + + # Use the correct path to GKLIB when building out of source + patch("gklib_path.patch") + # Install both gklib_defs.h and gklib_rename.h + patch("install_gklib_defs_rename.patch") + # Disable the "misleading indentation" warning when compiling + patch("gklib_nomisleadingindentation_warning.patch", when="%gcc@6:") + + with when("build_system=makefile"): + variant("debug", default=False, description="Compile in debug mode") - # Prior to version 5, the (non-cmake) build system only knows about - # 'build_type=Debug|Release'. - conflicts("@:4", when="build_type=RelWithDebInfo") - conflicts("@:4", when="build_type=MinSizeRel") - conflicts("@:4", when="+gdb") - conflicts("@:4", when="+int64") - conflicts("@:4", when="+real64") - - depends_on("cmake@2.8:", when="@5:", type="build") - - patch("install_gklib_defs_rename.patch", when="@5:") - patch("gklib_nomisleadingindentation_warning.patch", when="@5: %gcc@6:") - - def setup_build_environment(self, env): - # Ignore warnings/errors re unrecognized omp pragmas on %intel - if "%intel@14:" in self.spec: - env.append_flags("CFLAGS", "-diag-disable 3180") - # Ignore some warnings to get it to compile with %nvhpc - # 111: statement is unreachable - # 177: variable "foo" was declared but never referenced - # 188: enumerated type mixed with another type - # 550: variable "foo" was set but never used - if "%nvhpc" in self.spec: - env.append_flags("CFLAGS", "--display_error_number") - env.append_flags("CFLAGS", "--diag_suppress 111") - env.append_flags("CFLAGS", "--diag_suppress 177") - env.append_flags("CFLAGS", "--diag_suppress 188") - env.append_flags("CFLAGS", "--diag_suppress 550") - - @when("@5:") def patch(self): + if not self.spec.satisfies("build_system=cmake"): + return + source_path = self.stage.source_path metis_header = FileFilter(join_path(source_path, "include", "metis.h")) @@ -96,18 +75,38 @@ class Metis(Package): join_path(source_path, "GKlib", "error.c"), ) - @when("@:4") - def install(self, spec, prefix): - # Process library spec and options + +class SetupEnvironment(object): + def setup_build_environment(self, env): + # Ignore warnings/errors re unrecognized omp pragmas on %intel + if "%intel@14:" in self.spec: + env.append_flags("CFLAGS", "-diag-disable 3180") + # Ignore some warnings to get it to compile with %nvhpc + # 111: statement is unreachable + # 177: variable "foo" was declared but never referenced + # 188: enumerated type mixed with another type + # 550: variable "foo" was set but never used + if "%nvhpc" in self.spec: + env.append_flags("CFLAGS", "--display_error_number") + env.append_flags("CFLAGS", "--diag_suppress 111") + env.append_flags("CFLAGS", "--diag_suppress 177") + env.append_flags("CFLAGS", "--diag_suppress 188") + env.append_flags("CFLAGS", "--diag_suppress 550") + + +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder, SetupEnvironment): + @property + def build_targets(self): options = [] - if "+shared" in spec: - options.append("COPTIONS={0}".format(self.compiler.cc_pic_flag)) - if spec.variants["build_type"].value == "Debug": + if "+shared" in self.spec: + options.append("COPTIONS={0}".format(self.pkg.compiler.cc_pic_flag)) + if "+debug" in self.spec: options.append("OPTFLAGS=-g -O0") - make(*options) + return options + def install(self, pkg, spec, prefix): # Compile and install library files - ccompile = Executable(self.compiler.cc) + ccompile = Executable(pkg.compiler.cc) mkdir(prefix.bin) binfiles = ( @@ -140,7 +139,7 @@ class Metis(Package): install(sharefile, prefix.share) if "+shared" in spec: - shared_flags = [self.compiler.cc_pic_flag, "-shared"] + shared_flags = [pkg.compiler.cc_pic_flag, "-shared"] if sys.platform == "darwin": shared_suffix = "dylib" shared_flags.extend(["-Wl,-all_load", "libmetis.a"]) @@ -157,7 +156,7 @@ class Metis(Package): ccompile( "-I%s" % prefix.include, "-L%s" % prefix.lib, - (self.compiler.cc_rpath_arg + prefix.lib if "+shared" in spec else ""), + (pkg.compiler.cc_rpath_arg + prefix.lib if "+shared" in spec else ""), join_path("Programs", "io.o"), join_path("Test", "mtest.c"), "-o", @@ -166,58 +165,32 @@ class Metis(Package): "-lm", ) - if self.run_tests: - test_bin = lambda testname: join_path(prefix.bin, testname) - test_graph = lambda graphname: join_path(prefix.share, graphname) - - graph = test_graph("4elt.graph") - os.system("%s %s" % (test_bin("mtest"), graph)) - os.system("%s %s 40" % (test_bin("kmetis"), graph)) - os.system("%s %s" % (test_bin("onmetis"), graph)) - graph = test_graph("test.mgraph") - os.system("%s %s 2" % (test_bin("pmetis"), graph)) - os.system("%s %s 2" % (test_bin("kmetis"), graph)) - os.system("%s %s 5" % (test_bin("kmetis"), graph)) - graph = test_graph("metis.mesh") - os.system("%s %s 10" % (test_bin("partnmesh"), graph)) - os.system("%s %s 10" % (test_bin("partdmesh"), graph)) - os.system("%s %s" % (test_bin("mesh2dual"), graph)) - - # FIXME: The following code should replace the testing code in the - # block above since it causes installs to fail when one or more of - # the Metis tests fail, but it currently doesn't work because the - # 'mtest', 'onmetis', and 'partnmesh' tests return error codes that - # trigger false positives for failure. - """ - Executable(test_bin('mtest'))(test_graph('4elt.graph')) - Executable(test_bin('kmetis'))(test_graph('4elt.graph'), '40') - Executable(test_bin('onmetis'))(test_graph('4elt.graph')) - - Executable(test_bin('pmetis'))(test_graph('test.mgraph'), '2') - Executable(test_bin('kmetis'))(test_graph('test.mgraph'), '2') - Executable(test_bin('kmetis'))(test_graph('test.mgraph'), '5') - - Executable(test_bin('partnmesh'))(test_graph('metis.mesh'), '10') - Executable(test_bin('partdmesh'))(test_graph('metis.mesh'), '10') - Executable(test_bin('mesh2dual'))(test_graph('metis.mesh')) - """ - - @when("@5:") - def install(self, spec, prefix): - source_directory = self.stage.source_path - build_directory = join_path(self.stage.path, "build") - - options = CMakePackage._std_args(self) - options.append("-DGKLIB_PATH:PATH=%s/GKlib" % source_directory) - - # Normally this is available via the 'CMakePackage' object, but metis - # IS-A 'Package' (not a 'CMakePackage') to support non-cmake metis@:5. - build_type = spec.variants["build_type"].value - options.extend(["-DCMAKE_BUILD_TYPE:STRING={0}".format(build_type)]) - - if "+shared" in spec: - options.append("-DSHARED:BOOL=ON") - else: + def check(self): + test_bin = lambda testname: join_path(prefix.bin, testname) + test_graph = lambda graphname: join_path(prefix.share, graphname) + + graph = test_graph("4elt.graph") + os.system("%s %s" % (test_bin("mtest"), graph)) + os.system("%s %s 40" % (test_bin("kmetis"), graph)) + os.system("%s %s" % (test_bin("onmetis"), graph)) + graph = test_graph("test.mgraph") + os.system("%s %s 2" % (test_bin("pmetis"), graph)) + os.system("%s %s 2" % (test_bin("kmetis"), graph)) + os.system("%s %s 5" % (test_bin("kmetis"), graph)) + graph = test_graph("metis.mesh") + os.system("%s %s 10" % (test_bin("partnmesh"), graph)) + os.system("%s %s 10" % (test_bin("partdmesh"), graph)) + os.system("%s %s" % (test_bin("mesh2dual"), graph)) + + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder, SetupEnvironment): + def cmake_args(self): + options = [ + self.define_from_variant("SHARED", "shared"), + self.define_from_variant("GDB", "gdb"), + ] + + if self.spec.satisfies("~shared"): # Remove all RPATH options # (RPATHxxx options somehow trigger cmake to link dynamically) rpath_options = [] @@ -226,40 +199,41 @@ class Metis(Package): rpath_options.append(o) for o in rpath_options: options.remove(o) - if "+gdb" in spec: - options.append("-DGDB:BOOL=ON") - with working_dir(build_directory, create=True): - cmake(source_directory, *options) - make() - make("install") + return options + @run_after("install") + def install_headers(self): + with working_dir(self.build_directory): # install all headers, which will be needed for ParMETIS and other programs - subdirs = ["GKlib", "libmetis", "programs"] - for subd in subdirs: - inc_dist = join_path(prefix.include, subd) + directories = ["GKlib", "libmetis", "programs"] + for directory in directories: + inc_dist = join_path(self.prefix.include, directory) mkdirp(inc_dist) - install(join_path(source_directory, subd, "*.h"), inc_dist) - - if self.run_tests: - # FIXME: On some systems, the installed binaries for METIS cannot - # be executed without first being read. - ls = which("ls") - ls("-a", "-l", prefix.bin) - - for f in ["4elt", "copter2", "mdual"]: - graph = join_path(source_directory, "graphs", "%s.graph" % f) - Executable(join_path(prefix.bin, "graphchk"))(graph) - Executable(join_path(prefix.bin, "gpmetis"))(graph, "2") - Executable(join_path(prefix.bin, "ndmetis"))(graph) - - graph = join_path(source_directory, "graphs", "test.mgraph") - Executable(join_path(prefix.bin, "gpmetis"))(graph, "2") - graph = join_path(source_directory, "graphs", "metis.mesh") - Executable(join_path(prefix.bin, "mpmetis"))(graph, "2") - - @run_after("install") + install(join_path(self.stage.source_path, directory, "*.h"), inc_dist) + + def check(self): + # On some systems, the installed binaries for METIS cannot + # be executed without first being read. + ls = which("ls") + ls("-a", "-l", self.prefix.bin) + + graphchk = Executable(join_path(self.prefix.bin, "graphchk")) + gpmetis = Executable(join_path(self.prefix.bin, "gpmetis")) + ndmetis = Executable(join_path(self.prefix.bin, "ndmetis")) + mpmetis = Executable(join_path(self.prefix.bin, "mpmetis")) + for f in ["4elt", "copter2", "mdual"]: + graph = join_path(self.stage.source_path, "graphs", "%s.graph" % f) + graphchk(graph) + gpmetis(graph, "2") + ndmetis(graph) + + graph = join_path(self.stage.source_path, "graphs", "test.mgraph") + gpmetis(graph, "2") + graph = join_path(self.stage.source_path, "graphs", "metis.mesh") + mpmetis(graph, "2") + + @run_after("install", when="+shared platform=darwin") def darwin_fix(self): # The shared library is not installed correctly on Darwin; fix this - if (sys.platform == "darwin") and ("+shared" in self.spec): - fix_darwin_install_name(prefix.lib) + fix_darwin_install_name(prefix.lib) diff --git a/var/spack/repos/builtin/packages/mmg/package.py b/var/spack/repos/builtin/packages/mmg/package.py index f0269ca430..3cd579dff1 100644 --- a/var/spack/repos/builtin/packages/mmg/package.py +++ b/var/spack/repos/builtin/packages/mmg/package.py @@ -5,6 +5,7 @@ import os +import spack.build_systems.cmake from spack.package import * from spack.util.executable import which @@ -43,24 +44,22 @@ class Mmg(CMakePackage): depends_on("doxygen", when="+doc") depends_on("vtk", when="+vtk") - def cmake_args(self): - args = [] - - args.append(self.define_from_variant("USE_SCOTCH", "scotch")) - args.append(self.define_from_variant("USE_VTK", "vtk")) - if "+shared" in self.spec: - args.append("-DLIBMMG3D_SHARED=ON") - args.append("-DLIBMMG2D_SHARED=ON") - args.append("-DLIBMMGS_SHARED=ON") - args.append("-DLIBMMG_SHARED=ON") - else: - args.append("-DLIBMMG3D_STATIC=ON") - args.append("-DLIBMMG2D_STATIC=ON") - args.append("-DLIBMMGS_STATIC=ON") - args.append("-DLIBMMG_STATIC=ON") - - return args +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + shared_active = self.spec.satisfies("+shared") + return [ + self.define_from_variant("USE_SCOTCH", "scotch"), + self.define_from_variant("USE_VTK", "vtk"), + self.define("LIBMMG3D_SHARED", shared_active), + self.define("LIBMMG2D_SHARED", shared_active), + self.define("LIBMMGS_SHARED", shared_active), + self.define("LIBMMG_SHARED", shared_active), + self.define("LIBMMG3D_STATIC", not shared_active), + self.define("LIBMMG2D_STATIC", not shared_active), + self.define("LIBMMGS_STATIC", not shared_active), + self.define("LIBMMG_STATIC", not shared_active), + ] # parmmg requires this for its build @run_after("install") diff --git a/var/spack/repos/builtin/packages/nasm/package.py b/var/spack/repos/builtin/packages/nasm/package.py index e7d76b1d8f..5514b26b5e 100644 --- a/var/spack/repos/builtin/packages/nasm/package.py +++ b/var/spack/repos/builtin/packages/nasm/package.py @@ -8,7 +8,7 @@ import os from spack.package import * -class Nasm(Package): +class Nasm(AutotoolsPackage): """NASM (Netwide Assembler) is an 80x86 assembler designed for portability and modularity. It includes a disassembler as well.""" @@ -17,6 +17,8 @@ class Nasm(Package): list_url = "https://www.nasm.us/pub/nasm/releasebuilds" list_depth = 1 + build_system("autotools", conditional("generic", when="platform=windows"), default="autotools") + version("2.15.05", sha256="9182a118244b058651c576baa9d0366ee05983c4d4ae1d9ddd3236a9f2304997") version("2.14.02", sha256="b34bae344a3f2ed93b2ca7bf25f1ed3fb12da89eeda6096e3551fd66adeae9fc") version("2.13.03", sha256="23e1b679d64024863e2991e5c166e19309f0fe58a9765622b35bd31be5b2cc99") @@ -31,11 +33,11 @@ class Nasm(Package): when="@2.13.03 %gcc@8:", ) - patch("msvc.mak.patch", when="@2.15.05 platform=windows") - - conflicts("%intel@:14", when="@2.14:", msg="Intel 14 has immature C11 support") + with when("platform=windows"): + depends_on("perl") + patch("msvc.mak.patch", when="@2.15.05") - depends_on("perl", when="platform=windows") + conflicts("%intel@:14", when="@2.14:", msg="Intel <= 14 lacks support for C11") def patch(self): # Remove flags not recognized by the NVIDIA compiler @@ -51,13 +53,8 @@ class Nasm(Package): "configure", ) - def install(self, spec, prefix): - with working_dir(self.stage.source_path, create=True): - configure(*["--prefix={0}".format(self.prefix)]) - make("V=1") - make("install") - @when("platform=windows") +class GenericBuilder(spack.build_systems.generic.GenericBuilder): def install(self, spec, prefix): with working_dir(self.stage.source_path, create=True): # build NASM with nmake diff --git a/var/spack/repos/builtin/packages/netlib-lapack/package.py b/var/spack/repos/builtin/packages/netlib-lapack/package.py index 4908fc1b9d..387657d1ca 100644 --- a/var/spack/repos/builtin/packages/netlib-lapack/package.py +++ b/var/spack/repos/builtin/packages/netlib-lapack/package.py @@ -2,7 +2,7 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.cmake from spack.package import * @@ -55,6 +55,8 @@ class NetlibLapack(CMakePackage): # netlib-lapack is the reference implementation of LAPACK for ver in [ + "3.10.1", + "3.10.0", "3.9.1", "3.9.0", "3.8.0", @@ -182,82 +184,53 @@ class NetlibLapack(CMakePackage): lapacke_h = join_path(include_dir, "lapacke.h") return HeaderList([cblas_h, lapacke_h]) - @property - def build_directory(self): - return join_path( - self.stage.source_path, - "spack-build-shared" if self._building_shared else "spack-build-static", - ) +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): def cmake_args(self): - args = ["-DBUILD_SHARED_LIBS:BOOL=" + ("ON" if self._building_shared else "OFF")] - - if self.spec.satisfies("+lapacke"): - args.extend(["-DLAPACKE:BOOL=ON", "-DLAPACKE_WITH_TMG:BOOL=ON"]) - else: - args.extend(["-DLAPACKE:BOOL=OFF", "-DLAPACKE_WITH_TMG:BOOL=OFF"]) - - if self.spec.satisfies("@3.6.0:"): - args.append("-DCBLAS=ON") # always build CBLAS + args = [ + self.define_from_variant("BUILD_SHARED_LIBS", "shared"), + self.define_from_variant("LAPACKE", "lapacke"), + self.define_from_variant("LAPACKE_WITH_TMG", "lapacke"), + self.define("CBLAS", self.spec.satisfies("@3.6.0:")), + ] if self.spec.satisfies("%intel"): # Intel compiler finds serious syntax issues when trying to # build CBLAS and LapackE - args.extend(["-DCBLAS=OFF", "-DLAPACKE:BOOL=OFF"]) + args.extend([self.define("CBLAS", False), self.define("LAPACKE", False)]) if self.spec.satisfies("%xl") or self.spec.satisfies("%xl_r"): # use F77 compiler if IBM XL args.extend( [ - "-DCMAKE_Fortran_COMPILER=" + self.compiler.f77, - "-DCMAKE_Fortran_FLAGS=" - + (" ".join(self.spec.compiler_flags["fflags"])) - + " -O3 -qnohot", + self.define("CMAKE_Fortran_COMPILER", self.compiler.f77), + self.define( + "CMAKE_Fortran_FLAGS", + " ".join(self.spec.compiler_flags["fflags"]) + " -O3 -qnohot", + ), ] ) # deprecated routines are commonly needed by, for example, suitesparse # Note that OpenBLAS spack is built with deprecated routines - args.append("-DBUILD_DEPRECATED:BOOL=ON") + args.append(self.define("BUILD_DEPRECATED", True)) if self.spec.satisfies("+external-blas"): args.extend( [ - "-DUSE_OPTIMIZED_BLAS:BOOL=ON", - "-DBLAS_LIBRARIES:PATH=" + self.spec["blas"].libs.joined(";"), + self.define("USE_OPTIMIZED_BLAS", True), + self.define("BLAS_LIBRARIES:PATH", self.spec["blas"].libs.joined(";")), ] ) if self.spec.satisfies("+xblas"): args.extend( [ - "-DXBLAS_INCLUDE_DIR=" + self.spec["netlib-xblas"].prefix.include, - "-DXBLAS_LIBRARY=" + self.spec["netlib-xblas"].libs.joined(";"), + self.define("XBLAS_INCLUDE_DIR", self.spec["netlib-xblas"].prefix.include), + self.define("XBLAS_LIBRARY", self.spec["netlib-xblas"].libs.joined(";")), ] ) - args.append("-DBUILD_TESTING:BOOL=" + ("ON" if self.run_tests else "OFF")) + args.append(self.define("BUILD_TESTING", self.pkg.run_tests)) return args - - # Build, install, and check both static and shared versions of the - # libraries when +shared - @when("+shared") - def cmake(self, spec, prefix): - for self._building_shared in (False, True): - super(NetlibLapack, self).cmake(spec, prefix) - - @when("+shared") - def build(self, spec, prefix): - for self._building_shared in (False, True): - super(NetlibLapack, self).build(spec, prefix) - - @when("+shared") - def install(self, spec, prefix): - for self._building_shared in (False, True): - super(NetlibLapack, self).install(spec, prefix) - - @when("+shared") - def check(self): - for self._building_shared in (False, True): - super(NetlibLapack, self).check() diff --git a/var/spack/repos/builtin/packages/openmpi/package.py b/var/spack/repos/builtin/packages/openmpi/package.py index e1a34ac6b3..9405951c35 100644 --- a/var/spack/repos/builtin/packages/openmpi/package.py +++ b/var/spack/repos/builtin/packages/openmpi/package.py @@ -1053,8 +1053,7 @@ class Openmpi(AutotoolsPackage, CudaPackage): return config_args - @when("+wrapper-rpath") - @run_after("install") + @run_after("install", when="+wrapper-rpath") def filter_rpaths(self): def filter_lang_rpaths(lang_tokens, rpath_arg): if self.compiler.cc_rpath_arg == rpath_arg: @@ -1086,8 +1085,7 @@ class Openmpi(AutotoolsPackage, CudaPackage): filter_lang_rpaths(["c++", "CC", "cxx"], self.compiler.cxx_rpath_arg) filter_lang_rpaths(["fort", "f77", "f90"], self.compiler.fc_rpath_arg) - @when("@:3.0.4+wrapper-rpath") - @run_after("install") + @run_after("install", when="@:3.0.4+wrapper-rpath") def filter_pc_files(self): files = find(self.spec.prefix.lib.pkgconfig, "*.pc") x = FileFilter(*[f for f in files if not os.path.islink(f)]) diff --git a/var/spack/repos/builtin/packages/plasma/package.py b/var/spack/repos/builtin/packages/plasma/package.py index 5e32b5af31..f6f32d0629 100644 --- a/var/spack/repos/builtin/packages/plasma/package.py +++ b/var/spack/repos/builtin/packages/plasma/package.py @@ -2,7 +2,8 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.cmake +import spack.build_systems.makefile from spack.package import * @@ -38,6 +39,12 @@ class Plasma(CMakePackage): url="https://github.com/icl-utk-edu/plasma/releases/download/17.01/plasma-17.01.tar.gz", ) + build_system( + conditional("makefile", when="@:17.1"), + conditional("cmake", when="@18.9:"), + default="cmake", + ) + variant("shared", default=True, description="Build shared library (disables static library)") variant("lua", default=False, description="Build Lua support for tuning tile sizes") @@ -82,18 +89,15 @@ class Plasma(CMakePackage): def patch(self): python("tools/generate_precisions.py") - @when("@18.9.0:") - def cmake_args(self): - options = list() - - options.extend( - [ - "-DBLAS_LIBRARIES=%s" % self.spec["blas"].libs.joined(";"), - "-DLAPACK_LIBRARIES=%s" % self.spec["lapack"].libs.joined(";"), - ] - ) - options += ["-DBUILD_SHARED_LIBS=%s" % ("ON" if ("+shared" in self.spec) else "OFF")] +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + options = [ + self.define("BLAS_LIBRARIES", self.spec["blas"].libs.joined(";")), + self.define("LAPACK_LIBRARIES", self.spec["lapack"].libs.joined(";")), + self.define_from_variant("BUILD_SHARED_LIBS", "shared"), + self.define_from_variant("PLASMA_DETECT_LUA", "lua"), + ] for package, provider in ( ("openblas", "openblas"), @@ -102,32 +106,13 @@ class Plasma(CMakePackage): ): if package in self.spec: for lib in ("CBLAS", "LAPACKE"): - options.append("-D%s_PROVIDER=%s" % (lib, provider)) - - if "lua" in self.spec: - options.append("-DPLASMA_DETECT_LUA=TRUE") + options.append(self.define("{}_PROVIDER".format(lib), provider)) return options - # Before 18.9.0 it was an Makefile package - @when("@:17.1") - def cmake(self, spec, prefix): - pass - - # Before 18.9.0 it was an Makefile package - @when("@:17.1") - def build(self, spec, prefix): - pass - - # Before 18.9.0 it was an Makefile package - @when("@:17.1") - def install(self, spec, prefix): - self.edit(spec, prefix) - make() - make("install") - - @when("@:17.1") - def edit(self, spec, prefix): + +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder): + def edit(self, pkg, spec, prefix): # copy "make.inc.mkl-gcc" provided by default into "make.inc" open("make.inc", "w").write(open("make.inc.mkl-gcc").read()) diff --git a/var/spack/repos/builtin/packages/py-dm-tree/package.py b/var/spack/repos/builtin/packages/py-dm-tree/package.py index 36dcdc910f..1c8b7eca36 100644 --- a/var/spack/repos/builtin/packages/py-dm-tree/package.py +++ b/var/spack/repos/builtin/packages/py-dm-tree/package.py @@ -26,20 +26,23 @@ class PyDmTree(PythonPackage): depends_on("bazel", type="build") depends_on("py-six@1.12.0:", type=("build", "run")) + # This is set later + tmp_path = None + @run_after("install") def clean(self): - remove_linked_tree(self.tmp_path) + remove_linked_tree(PyDmTree.tmp_path) def patch(self): - self.tmp_path = tempfile.mkdtemp(prefix="spack") - env["TEST_TMPDIR"] = self.tmp_path - env["HOME"] = self.tmp_path + PyDmTree.tmp_path = tempfile.mkdtemp(prefix="spack") + env["TEST_TMPDIR"] = PyDmTree.tmp_path + env["HOME"] = PyDmTree.tmp_path args = [ # Don't allow user or system .bazelrc to override build settings "'--nohome_rc',\n", "'--nosystem_rc',\n", # Bazel does not work properly on NFS, switch to /tmp - "'--output_user_root={0}',\n".format(self.tmp_path), + "'--output_user_root={0}',\n".format(PyDmTree.tmp_path), "'build',\n", # Spack logs don't handle colored output well "'--color=no',\n", diff --git a/var/spack/repos/builtin/packages/py-onnx-runtime/package.py b/var/spack/repos/builtin/packages/py-onnx-runtime/package.py index 62ea531395..fa956086b8 100644 --- a/var/spack/repos/builtin/packages/py-onnx-runtime/package.py +++ b/var/spack/repos/builtin/packages/py-onnx-runtime/package.py @@ -6,7 +6,7 @@ from spack.package import * -class PyOnnxRuntime(CMakePackage, PythonPackage): +class PyOnnxRuntime(CMakePackage, PythonExtension): """ONNX Runtime is a performance-focused complete scoring engine for Open Neural Network Exchange (ONNX) models, with an open extensible architecture to continually address the diff --git a/var/spack/repos/builtin/packages/py-pillow/package.py b/var/spack/repos/builtin/packages/py-pillow/package.py index 8f3488fce3..aa98037062 100644 --- a/var/spack/repos/builtin/packages/py-pillow/package.py +++ b/var/spack/repos/builtin/packages/py-pillow/package.py @@ -15,6 +15,18 @@ class PyPillowBase(PythonPackage): # These defaults correspond to Pillow defaults # https://pillow.readthedocs.io/en/stable/installation.html#external-libraries + VARIANTS_IN_SETUP_CFG = ( + "zlib", + "jpeg", + "tiff", + "freetype", + "lcms", + "webp", + "webpmux", + "jpeg2000", + "imagequant", + "xcb", + ) variant("zlib", default=True, description="Compressed PNG functionality") variant("jpeg", default=True, description="JPEG functionality") variant("tiff", default=False, description="Compressed TIFF functionality") @@ -79,8 +91,7 @@ class PyPillowBase(PythonPackage): with open("setup.cfg", "a") as setup: setup.write("[build_ext]\n") - variants = list(self.spec.variants) - for variant in variants: + for variant in self.VARIANTS_IN_SETUP_CFG: setup.write(variant_to_cfg(variant)) setup.write("rpath={0}\n".format(":".join(self.rpath))) diff --git a/var/spack/repos/builtin/packages/py-pybind11/package.py b/var/spack/repos/builtin/packages/py-pybind11/package.py index b8a234c668..d50bdeef8e 100644 --- a/var/spack/repos/builtin/packages/py-pybind11/package.py +++ b/var/spack/repos/builtin/packages/py-pybind11/package.py @@ -2,13 +2,14 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import os +import spack.build_systems.cmake +import spack.build_systems.python from spack.package import * -class PyPybind11(CMakePackage, PythonPackage): +class PyPybind11(CMakePackage, PythonExtension): """pybind11 -- Seamless operability between C++11 and Python. pybind11 is a lightweight header-only library that exposes C++ types in @@ -16,7 +17,8 @@ class PyPybind11(CMakePackage, PythonPackage): code. Its goals and syntax are similar to the excellent Boost.Python library by David Abrahams: to minimize boilerplate code in traditional extension modules by inferring type information using compile-time - introspection.""" + introspection. + """ homepage = "https://pybind11.readthedocs.io" url = "https://github.com/pybind/pybind11/archive/v2.6.2.tar.gz" @@ -26,6 +28,7 @@ class PyPybind11(CMakePackage, PythonPackage): version("master", branch="master") version("2.10.0", sha256="eacf582fa8f696227988d08cfc46121770823839fe9e301a20fbce67e7cd70ec") + version("2.9.2", sha256="6bd528c4dbe2276635dc787b6b1f2e5316cf6b49ee3e150264e455a0d68d19c1") version("2.9.1", sha256="c6160321dc98e6e1184cc791fbeadd2907bb4a0ce0e447f2ea4ff8ab56550913") version("2.9.0", sha256="057fb68dafd972bc13afb855f3b0d8cf0fa1a78ef053e815d9af79be7ff567cb") version("2.8.1", sha256="f1bcc07caa568eb312411dde5308b1e250bd0e1bc020fae855bf9f43209940cc") @@ -45,30 +48,25 @@ class PyPybind11(CMakePackage, PythonPackage): version("2.1.1", sha256="f2c6874f1ea5b4ad4ffffe352413f7d2cd1a49f9050940805c2a082348621540") version("2.1.0", sha256="2860f2b8d0c9f65f0698289a161385f59d099b7ead1bf64e8993c486f2b93ee0") - depends_on("ninja", type="build") depends_on("py-setuptools@42:", type="build") depends_on("py-pytest", type="test") depends_on("python@2.7:2.8,3.5:", type=("build", "run")) depends_on("python@3.6:", when="@2.10.0:", type=("build", "run")) - depends_on("cmake@3.13:", type="build") - depends_on("cmake@3.18:", type="build", when="@2.6.0:") + + depends_on("py-pip", type="build") + depends_on("py-wheel", type="build") + extends("python") + + with when("build_system=cmake"): + depends_on("ninja", type="build") + depends_on("cmake@3.13:", type="build") + depends_on("cmake@3.18:", type="build", when="@2.6.0:") # compiler support conflicts("%gcc@:4.7") conflicts("%clang@:3.2") conflicts("%intel@:16") - build_directory = "." - - def cmake_args(self): - args = [] - args.append("-DPYTHON_EXECUTABLE:FILEPATH=%s" % self.spec["python"].command.path) - args += [self.define("PYBIND11_TEST", self.run_tests)] - return args - - def setup_build_environment(self, env): - env.set("PYBIND11_USE_CMAKE", 1) - # https://github.com/pybind/pybind11/pull/1995 @when("@:2.4") def patch(self): @@ -80,13 +78,27 @@ class PyPybind11(CMakePackage, PythonPackage): string=True, ) - def install(self, spec, prefix): - CMakePackage.install(self, spec, prefix) - PythonPackage.install(self, spec, prefix) + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + return [ + self.define("PYTHON_EXECUTABLE:FILEPATH", self.spec["python"].command.path), + self.define("PYBIND11_TEST", self.pkg.run_tests), + ] + + def install(self, pkg, spec, prefix): + super(CMakeBuilder, self).install(pkg, spec, prefix) + python_builder = spack.build_systems.python.PythonPipBuilder(pkg) + python_builder.install(pkg, spec, prefix) + + def setup_build_environment(self, env): + env.set("PYBIND11_USE_CMAKE", 1) @run_after("install") - @on_package_attributes(run_tests=True) def install_test(self): + if not self.pkg.run_tests: + return + with working_dir("spack-test", create=True): # test include helper points to right location python = self.spec["python"].command diff --git a/var/spack/repos/builtin/packages/py-pykokkos-base/package.py b/var/spack/repos/builtin/packages/py-pykokkos-base/package.py index 5d3427b4bb..f9b02c9ae9 100644 --- a/var/spack/repos/builtin/packages/py-pykokkos-base/package.py +++ b/var/spack/repos/builtin/packages/py-pykokkos-base/package.py @@ -8,7 +8,7 @@ from spack.package import * -class PyPykokkosBase(CMakePackage, PythonPackage): +class PyPykokkosBase(CMakePackage, PythonExtension): """Minimal set of bindings for Kokkos interoperability with Python (initialize, finalize, View, DynRankView, Kokkos-tools)""" diff --git a/var/spack/repos/builtin/packages/py-tfdlpack/package.py b/var/spack/repos/builtin/packages/py-tfdlpack/package.py index 2bc5186d92..1304184c2e 100644 --- a/var/spack/repos/builtin/packages/py-tfdlpack/package.py +++ b/var/spack/repos/builtin/packages/py-tfdlpack/package.py @@ -7,7 +7,7 @@ from spack.package import * -class PyTfdlpack(CMakePackage, PythonPackage): +class PyTfdlpack(CMakePackage, PythonExtension): """Tensorflow plugin for DLPack.""" homepage = "https://github.com/VoVAllen/tf-dlpack" diff --git a/var/spack/repos/builtin/packages/quantum-espresso/package.py b/var/spack/repos/builtin/packages/quantum-espresso/package.py index acc07692cf..47fb80e6f9 100644 --- a/var/spack/repos/builtin/packages/quantum-espresso/package.py +++ b/var/spack/repos/builtin/packages/quantum-espresso/package.py @@ -2,11 +2,12 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.cmake +import spack.build_systems.generic from spack.package import * -class QuantumEspresso(CMakePackage): +class QuantumEspresso(CMakePackage, Package): """Quantum ESPRESSO is an integrated suite of Open-Source computer codes for electronic-structure calculations and materials modeling at the nanoscale. It is based on density-functional theory, plane waves, and @@ -19,6 +20,8 @@ class QuantumEspresso(CMakePackage): maintainers = ["ye-luo", "danielecesarini", "bellenlau"] + build_system(conditional("cmake", when="@6.8:"), "generic", default="cmake") + version("develop", branch="develop") version("7.1", sha256="d56dea096635808843bd5a9be2dee3d1f60407c01dbeeda03f8256a3bcfc4eb6") version("7.0", sha256="85beceb1aaa1678a49e774c085866d4612d9d64108e0ac49b23152c8622880ee") @@ -56,10 +59,8 @@ class QuantumEspresso(CMakePackage): destination=".", ) - variant("cmake", default=True, description="Builds via CMake") - with when("+cmake"): + with when("build_system=cmake"): depends_on("cmake@3.14.0:", type="build") - conflicts("@:6.7", msg="+cmake works since QE v6.8") variant("libxc", default=False, description="Uses libxc") depends_on("libxc@5.1.2:", when="+libxc") @@ -93,7 +94,7 @@ class QuantumEspresso(CMakePackage): msg="bugs with NVHPCSDK from v21.11 to v22.3, OpenMP and GPU", ) # only cmake is supported - conflicts("~cmake", msg="Only CMake supported for GPU-enabled version") + conflicts("build_system=generic", msg="Only CMake supported for GPU-enabled version") # NVTX variant for profiling # requires linking to CUDA runtime APIs , handled by CMake @@ -121,9 +122,10 @@ class QuantumEspresso(CMakePackage): with when("+elpa"): # CMake builds only support elpa without openmp - depends_on("elpa~openmp", when="+cmake") - depends_on("elpa+openmp", when="+openmp~cmake") - depends_on("elpa~openmp", when="~openmp~cmake") + depends_on("elpa~openmp", when="build_system=cmake") + with when("build_system=generic"): + depends_on("elpa+openmp", when="+openmp") + depends_on("elpa~openmp", when="~openmp") # Elpa is formally supported by @:5.4.0, but QE configure searches # for it in the wrong folders (or tries to download it within # the build directory). Instead of patching Elpa to provide the @@ -181,12 +183,14 @@ class QuantumEspresso(CMakePackage): with when("@7.0.1:"): # when QE doesn't use hdf5 library, the converter plugin still needs it depends_on("hdf5@1.8.16:+hl~mpi", when="hdf5=none") - conflicts("~cmake", msg="QE-to-QMCPACK wave function converter requires cmake") + conflicts( + "build_system=generic", msg="QE-to-QMCPACK wave function converter requires cmake" + ) # Enables building Electron-phonon Wannier 'epw.x' executable # http://epw.org.uk/Main/About - variant("epw", default=False, description="Builds Electron-phonon Wannier executable") - conflicts("~epw", when="+cmake", msg="epw cannot be turned off when using CMake") + variant("epw", default=True, description="Builds Electron-phonon Wannier executable") + conflicts("~epw", when="build_system=cmake", msg="epw cannot be turned off when using CMake") with when("+epw"): # The first version of Q-E to feature integrated EPW is 6.0.0, @@ -198,8 +202,10 @@ class QuantumEspresso(CMakePackage): # Constraints may be relaxed as successful reports # of different compiler+mpi combinations arrive - # TODO: enable building EPW when ~mpi and ~cmake - conflicts("~mpi", when="~cmake", msg="EPW needs MPI when ~cmake") + # TODO: enable building EPW when ~mpi and build_system=generic + conflicts( + "~mpi", when="build_system=generic", msg="EPW needs MPI when build_system=generic" + ) # EPW doesn't gets along well with OpenMPI 2.x.x conflicts("^openmpi@2.0.0:2", msg="OpenMPI version incompatible with EPW") @@ -212,19 +218,19 @@ class QuantumEspresso(CMakePackage): variant( "environ", default=False, + when="build_system=generic", description="Enables support for introducing environment effects " "into atomistic first-principles simulations." "See http://quantum-environ.org/about.html", ) - conflicts("+environ", when="+cmake", msg="environ doesn't work with CMake") variant( "gipaw", default=False, + when="build_system=generic", description="Builds Gauge-Including Projector Augmented-Waves executable", ) with when("+gipaw"): - conflicts("+cmake", msg="gipaw doesn't work with CMake") conflicts( "@:6.3", msg="gipaw standard support available for QE 6.3 or grater version only" ) @@ -370,6 +376,8 @@ class QuantumEspresso(CMakePackage): # extlibs_makefile updated to work with fujitsu compilers patch("fj-fox.patch", when="+patch %fj") + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): def cmake_args(self): spec = self.spec @@ -400,18 +408,9 @@ class QuantumEspresso(CMakePackage): return cmake_args - @when("~cmake") - def cmake(self, spec, prefix): - print("Bypass cmake stage when building via configure") - - @when("~cmake") - def build(self, spec, prefix): - print("Bypass build stage when building via configure") - - @when("~cmake") - def install(self, spec, prefix): - print("Override install stage when building via configure") +class GenericBuilder(spack.build_systems.generic.GenericBuilder): + def install(self, pkg, spec, prefix): prefix_path = prefix.bin if "@:5.4.0" in spec else prefix options = ["-prefix={0}".format(prefix_path)] @@ -577,7 +576,7 @@ class QuantumEspresso(CMakePackage): # can't be applied to the '+qmcpack' variant if spec.variants["hdf5"].value != "none": if spec.satisfies("@6.1.0:6.4.0") or (spec.satisfies("@6.4.1") and "+qmcpack" in spec): - make_inc = join_path(self.stage.source_path, "make.inc") + make_inc = join_path(self.pkg.stage.source_path, "make.inc") zlib_libs = spec["zlib"].prefix.lib + " -lz" filter_file(zlib_libs, format(spec["zlib"].libs.ld_flags), make_inc) diff --git a/var/spack/repos/builtin/packages/racket/package.py b/var/spack/repos/builtin/packages/racket/package.py index cc9e1ba25b..7031e234ea 100644 --- a/var/spack/repos/builtin/packages/racket/package.py +++ b/var/spack/repos/builtin/packages/racket/package.py @@ -2,13 +2,11 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - -import sys - +import spack.build_systems.makefile from spack.package import * -class Racket(Package): +class Racket(MakefilePackage): """The Racket programming language.""" homepage = "https://www.racket-lang.org" @@ -21,13 +19,6 @@ class Racket(Package): depends_on("patchutils") depends_on("libtool", type=("build")) - phases = ["configure", "build", "install"] - - def url_for_version(self, version): - return "https://mirror.racket-lang.org/installers/{0}/racket-minimal-{0}-src-builtpkgs.tgz".format( - version - ) - variant("cs", default=True, description="Build Racket CS (new ChezScheme VM)") variant("bc", default=False, description="Build Racket BC (old MZScheme VM)") variant("shared", default=True, description="Enable shared") @@ -36,12 +27,22 @@ class Racket(Package): parallel = False extendable = True + def url_for_version(self, version): + return "https://mirror.racket-lang.org/installers/{0}/racket-minimal-{0}-src-builtpkgs.tgz".format( + version + ) + + +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder): + + build_directory = "src" + def toggle(self, spec, variant): toggle_text = "enable" if spec.variants[variant].value else "disable" return "--{0}-{1}".format(toggle_text, variant) - def configure(self, spec, prefix): - with working_dir("src"): + def edit(self, pkg, spec, prefix): + with working_dir(self.build_directory): configure = Executable("./configure") configure_args = [ self.toggle(spec, "cs"), @@ -49,7 +50,7 @@ class Racket(Package): self.toggle(spec, "jit"), ] toggle_shared = self.toggle(spec, "shared") - if sys.platform == "darwin": + if spec.satisfies("platform=darwin"): configure_args += ["--enable-macprefix"] if "+xonx" in spec: configure_args += ["--enable-xonx", toggle_shared] @@ -58,16 +59,20 @@ class Racket(Package): configure_args += ["--prefix={0}".format(prefix)] configure(*configure_args) - def build(self, spec, prefix): - with working_dir("src"): - if spec.variants["bc"].value: - make("bc") - if spec.variants["cs"].value: - make("cs") - - def install(self, spec, prefix): - with working_dir("src"): - if spec.variants["bc"].value: - make("install-bc") - if spec.variants["cs"].value: - make("install-cs") + @property + def build_targets(self): + result = [] + if "+bc" in self.spec: + result.append("bc") + if "+cs" in self.spec: + result.append("cs") + return result + + @property + def install_targets(self): + result = [] + if "+bc" in self.spec: + result.append("install-bc") + if "+cs" in self.spec: + result.append("install-cs") + return result diff --git a/var/spack/repos/builtin/packages/rkt-base/package.py b/var/spack/repos/builtin/packages/rkt-base/package.py index 256f90106e..3eb21e485d 100644 --- a/var/spack/repos/builtin/packages/rkt-base/package.py +++ b/var/spack/repos/builtin/packages/rkt-base/package.py @@ -18,5 +18,3 @@ class RktBase(RacketPackage): depends_on("racket@8.3", type=("build", "run"), when="@8.3") racket_name = "base" - pkgs = True - subdirectory = "pkgs/{0}".format(racket_name) diff --git a/var/spack/repos/builtin/packages/rkt-cext-lib/package.py b/var/spack/repos/builtin/packages/rkt-cext-lib/package.py index 8d9b257c05..daa1ef5226 100644 --- a/var/spack/repos/builtin/packages/rkt-cext-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-cext-lib/package.py @@ -21,5 +21,4 @@ class RktCextLib(RacketPackage): depends_on("rkt-scheme-lib@8.3", type=("build", "run"), when="@8.3") racket_name = "cext-lib" - pkgs = True subdirectory = racket_name diff --git a/var/spack/repos/builtin/packages/rkt-compiler-lib/package.py b/var/spack/repos/builtin/packages/rkt-compiler-lib/package.py index fee9b3ea9a..6a21c774cb 100644 --- a/var/spack/repos/builtin/packages/rkt-compiler-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-compiler-lib/package.py @@ -21,5 +21,3 @@ class RktCompilerLib(RacketPackage): depends_on("rkt-zo-lib@1.3", type=("build", "run"), when="@8.3") racket_name = "compiler-lib" - pkgs = True - subdirectory = "pkgs/{0}".format(racket_name) diff --git a/var/spack/repos/builtin/packages/rkt-dynext-lib/package.py b/var/spack/repos/builtin/packages/rkt-dynext-lib/package.py index 81f7ca811f..9164571ed3 100644 --- a/var/spack/repos/builtin/packages/rkt-dynext-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-dynext-lib/package.py @@ -17,5 +17,4 @@ class RktDynextLib(RacketPackage): depends_on("rkt-base@8.3", type=("build", "run"), when="@8.3") racket_name = "dynext-lib" - pkgs = True subdirectory = racket_name diff --git a/var/spack/repos/builtin/packages/rkt-rackunit-lib/package.py b/var/spack/repos/builtin/packages/rkt-rackunit-lib/package.py index 783aebe306..3391050258 100644 --- a/var/spack/repos/builtin/packages/rkt-rackunit-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-rackunit-lib/package.py @@ -18,5 +18,4 @@ class RktRackunitLib(RacketPackage): depends_on("rkt-testing-util-lib@8.3", type=("build", "run"), when="@8.3") racket_name = "rackunit-lib" - pkgs = True subdirectory = racket_name diff --git a/var/spack/repos/builtin/packages/rkt-scheme-lib/package.py b/var/spack/repos/builtin/packages/rkt-scheme-lib/package.py index 75346eac70..689d3af7c5 100644 --- a/var/spack/repos/builtin/packages/rkt-scheme-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-scheme-lib/package.py @@ -17,4 +17,3 @@ class RktSchemeLib(RacketPackage): depends_on("rkt-base@8.3", type=("build", "run"), when="@8.3") racket_name = "scheme-lib" - pkgs = True diff --git a/var/spack/repos/builtin/packages/rkt-testing-util-lib/package.py b/var/spack/repos/builtin/packages/rkt-testing-util-lib/package.py index 9186845fc9..aa1aafda4e 100644 --- a/var/spack/repos/builtin/packages/rkt-testing-util-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-testing-util-lib/package.py @@ -17,5 +17,4 @@ class RktTestingUtilLib(RacketPackage): depends_on("rkt-base@8.3:", type=("build", "run"), when="@8.3") racket_name = "testing-util-lib" - pkgs = True subdirectory = racket_name diff --git a/var/spack/repos/builtin/packages/rkt-zo-lib/package.py b/var/spack/repos/builtin/packages/rkt-zo-lib/package.py index 66f3d498af..a6b70cd075 100644 --- a/var/spack/repos/builtin/packages/rkt-zo-lib/package.py +++ b/var/spack/repos/builtin/packages/rkt-zo-lib/package.py @@ -17,5 +17,3 @@ class RktZoLib(RacketPackage): depends_on("rkt-base@8.3:", type=("build", "run"), when="@1.3") racket_name = "zo-lib" - pkgs = True - subdirectory = "pkgs/{0}".format(racket_name) diff --git a/var/spack/repos/builtin/packages/ruby/package.py b/var/spack/repos/builtin/packages/ruby/package.py index 9cc7f92151..a472509e08 100644 --- a/var/spack/repos/builtin/packages/ruby/package.py +++ b/var/spack/repos/builtin/packages/ruby/package.py @@ -2,19 +2,17 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import re -import sys -from typing import List +import spack.build_systems.autotools +import spack.build_systems.nmake from spack.package import * -is_windows = sys.platform == "win32" - -class Ruby(Package): +class Ruby(AutotoolsPackage, NMakePackage): """A dynamic, open source programming language with a focus on - simplicity and productivity.""" + simplicity and productivity. + """ maintainers = ["Kerilk"] @@ -33,24 +31,25 @@ class Ruby(Package): version("2.5.3", sha256="9828d03852c37c20fa333a0264f2490f07338576734d910ee3fd538c9520846c") version("2.2.0", sha256="7671e394abfb5d262fbcd3b27a71bf78737c7e9347fa21c39e58b0bb9c4840fc") - if not is_windows: - variant("openssl", default=True, description="Enable OpenSSL support") - variant("readline", default=False, description="Enable Readline support") - depends_on("pkgconfig", type=("build")) - depends_on("libffi") - depends_on("libx11", when="@:2.3") - depends_on("tcl", when="@:2.3") - depends_on("tk", when="@:2.3") - depends_on("readline", when="+readline") - depends_on("zlib") - with when("+openssl"): - depends_on("openssl@:1") - depends_on("openssl@:1.0", when="@:2.3") + build_system("autotools", "nmake", default="autotools") + + for _platform_condition in ("platform=linux", "platform=darwin", "platform=cray"): + with when(_platform_condition): + variant("openssl", default=True, description="Enable OpenSSL support") + variant("readline", default=False, description="Enable Readline support") + depends_on("pkgconfig", type="build") + depends_on("libffi") + depends_on("libx11", when="@:2.3") + depends_on("tcl", when="@:2.3") + depends_on("tk", when="@:2.3") + depends_on("readline", when="+readline") + depends_on("zlib") + with when("+openssl"): + depends_on("openssl@:1") + depends_on("openssl@:1.0", when="@:2.3") extendable = True - phases = ["configure", "build", "install"] - build_targets = [] # type: List[str] - install_targets = ["install"] + # Known build issues when Avira antivirus software is running: # https://github.com/rvm/rvm/issues/4313#issuecomment-374020379 # TODO: add check for this and warn user @@ -82,28 +81,6 @@ class Ruby(Package): url = "https://cache.ruby-lang.org/pub/ruby/{0}/ruby-{1}.tar.gz" return url.format(version.up_to(2), version) - def configure_args(self): - args = [] - if "+openssl" in self.spec: - args.append("--with-openssl-dir=%s" % self.spec["openssl"].prefix) - if "+readline" in self.spec: - args.append("--with-readline-dir=%s" % self.spec["readline"].prefix) - if "^tk" in self.spec: - args.append("--with-tk=%s" % self.spec["tk"].prefix) - if self.spec.satisfies("%fj"): - args.append("--disable-dtrace") - return args - - def setup_dependent_build_environment(self, env, dependent_spec): - # TODO: do this only for actual extensions. - # Set GEM_PATH to include dependent gem directories - for d in dependent_spec.traverse(deptype=("build", "run", "test"), root=True): - if d.package.extends(self.spec): - env.prepend_path("GEM_PATH", d.prefix) - - # The actual installation path for this gem - env.set("GEM_HOME", dependent_spec.prefix) - def setup_dependent_run_environment(self, env, dependent_spec): for d in dependent_spec.traverse(deptype=("run"), root=True): if d.package.extends(self.spec): @@ -122,31 +99,31 @@ class Ruby(Package): module.gem = Executable(self.prefix.bin.gem) module.rake = Executable(self.prefix.bin.rake) - def configure(self, spec, prefix): - with working_dir(self.stage.source_path, create=True): - if is_windows: - Executable("win32\\configure.bat")("--prefix=%s" % self.prefix) - else: - options = getattr(self, "configure_flag_args", []) - options += ["--prefix={0}".format(prefix)] - options += self.configure_args() - configure(*options) - - def build(self, spec, prefix): - with working_dir(self.stage.source_path): - if is_windows: - nmake() - else: - params = ["V=1"] - params += self.build_targets - make(*params) - - def install(self, spec, prefix): - with working_dir(self.stage.source_path): - if is_windows: - nmake("install") - else: - make(*self.install_targets) + +class SetupEnvironment(object): + def setup_dependent_build_environment(self, env, dependent_spec): + # TODO: do this only for actual extensions. + # Set GEM_PATH to include dependent gem directories + for d in dependent_spec.traverse(deptype=("build", "run", "test"), root=True): + if d.package.extends(self.spec): + env.prepend_path("GEM_PATH", d.prefix) + + # The actual installation path for this gem + env.set("GEM_HOME", dependent_spec.prefix) + + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder, SetupEnvironment): + def configure_args(self): + args = [] + if "+openssl" in self.spec: + args.append("--with-openssl-dir=%s" % self.spec["openssl"].prefix) + if "+readline" in self.spec: + args.append("--with-readline-dir=%s" % self.spec["readline"].prefix) + if "^tk" in self.spec: + args.append("--with-tk=%s" % self.spec["tk"].prefix) + if self.spec.satisfies("%fj"): + args.append("--disable-dtrace") + return args @run_after("install") def post_install(self): @@ -158,7 +135,7 @@ class Ruby(Package): """ if self.spec.satisfies("+openssl"): rubygems_updated_cert_path = join_path( - self.stage.source_path, "rubygems-updated-ssl-cert", "GlobalSignRootCA.pem" + self.pkg.stage.source_path, "rubygems-updated-ssl-cert", "GlobalSignRootCA.pem" ) rubygems_certs_path = join_path( self.spec.prefix.lib, @@ -171,11 +148,19 @@ class Ruby(Package): rbconfig = find(self.prefix, "rbconfig.rb")[0] filter_file( - r'^(\s*CONFIG\["CXX"\]\s*=\s*).*', r'\1"{0}"'.format(self.compiler.cxx), rbconfig + r'^(\s*CONFIG\["CXX"\]\s*=\s*).*', r'\1"{0}"'.format(self.pkg.compiler.cxx), rbconfig ) filter_file( - r'^(\s*CONFIG\["CC"\]\s*=\s*).*', r'\1"{0}"'.format(self.compiler.cc), rbconfig + r'^(\s*CONFIG\["CC"\]\s*=\s*).*', r'\1"{0}"'.format(self.pkg.compiler.cc), rbconfig ) filter_file( - r'^(\s*CONFIG\["MJIT_CC"\]\s*=\s*).*', r'\1"{0}"'.format(self.compiler.cc), rbconfig + r'^(\s*CONFIG\["MJIT_CC"\]\s*=\s*).*', + r'\1"{0}"'.format(self.pkg.compiler.cc), + rbconfig, ) + + +class NMakeBuilder(spack.build_systems.nmake.NMakeBuilder, SetupEnvironment): + def edit(self, pkg, spec, prefix): + with working_dir(self.pkg.stage.source_path, create=True): + Executable("win32\\configure.bat")("--prefix=%s" % self.prefix) diff --git a/var/spack/repos/builtin/packages/scotch/package.py b/var/spack/repos/builtin/packages/scotch/package.py index 166455876e..b957db31bf 100644 --- a/var/spack/repos/builtin/packages/scotch/package.py +++ b/var/spack/repos/builtin/packages/scotch/package.py @@ -3,12 +3,15 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +import spack.build_systems.cmake +import spack.build_systems.makefile from spack.package import * -class Scotch(CMakePackage): +class Scotch(CMakePackage, MakefilePackage): """Scotch is a software package for graph and mesh/hypergraph - partitioning, graph clustering, and sparse matrix ordering.""" + partitioning, graph clustering, and sparse matrix ordering. + """ homepage = "https://gitlab.inria.fr/scotch/scotch" git = "https://gitlab.inria.fr/scotch/scotch.git" @@ -32,13 +35,10 @@ class Scotch(CMakePackage): version("6.0.0", sha256="8206127d038bda868dda5c5a7f60ef8224f2e368298fbb01bf13fa250e378dd4") version("5.1.10b", sha256="54c9e7fafefd49d8b2017d179d4f11a655abe10365961583baaddc4eeb6a9add") - variant("mpi", default=True, description="Activate the compilation of parallel libraries") - variant( - "compression", default=True, description="Activate the posibility to use compressed files" - ) - variant( - "esmumps", default=False, description="Activate the compilation of esmumps needed by mumps" - ) + build_system(conditional("cmake", when="@7:"), "makefile", default="cmake") + variant("mpi", default=True, description="Compile parallel libraries") + variant("compression", default=True, description="May use compressed files") + variant("esmumps", default=False, description="Compile esmumps (needed by mumps)") variant("shared", default=True, description="Build a shared version of the library") variant( "metis", default=False, description="Expose vendored METIS/ParMETIS libraries and wrappers" @@ -70,8 +70,6 @@ class Scotch(CMakePackage): conflicts("^metis", when="+metis") conflicts("^parmetis", when="+metis") - # NOTE: In cross-compiling environment parallel build - # produces weird linker errors. parallel = False # NOTE: Versions of Scotch up to version 6.0.0 don't include support for @@ -107,16 +105,30 @@ class Scotch(CMakePackage): return scotchlibs + zlibs - @when("@:6") - def patch(self): - self.configure() - # NOTE: Configuration of Scotch is achieved by writing a 'Makefile.inc' - # file that contains all of the configuration variables and their desired - # values for the installation. This function writes this file based on - # the given installation variants. - @when("@:6") - def configure(self): +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + spec = self.spec + args = [ + self.define_from_variant("BUILD_LIBSCOTCHMETIS", "metis"), + self.define_from_variant("INSTALL_METIS_HEADERS", "metis"), + self.define_from_variant("BUILD_LIBESMUMPS", "esmumps"), + self.define_from_variant("BUILD_SHARED_LIBS", "shared"), + self.define_from_variant("BUILD_PTSCOTCH", "mpi"), + ] + + # TODO should we enable/disable THREADS? + + if "+int64" in spec: + args.append("-DINTSIZE=64") + + return args + + +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder): + build_directory = "src" + + def edit(self, pkg, spec, prefix): makefile_inc = [] cflags = [ "-O3", @@ -147,7 +159,7 @@ class Scotch(CMakePackage): makefile_inc.extend( [ "LIB = .dylib", - "CLIBFLAGS = -dynamiclib {0}".format(self.compiler.cc_pic_flag), + "CLIBFLAGS = -dynamiclib {0}".format(pkg.compiler.cc_pic_flag), "RANLIB = echo", "AR = $(CC)", ( @@ -162,13 +174,13 @@ class Scotch(CMakePackage): makefile_inc.extend( [ "LIB = .so", - "CLIBFLAGS = -shared {0}".format(self.compiler.cc_pic_flag), + "CLIBFLAGS = -shared {0}".format(pkg.compiler.cc_pic_flag), "RANLIB = echo", "AR = $(CC)", "ARFLAGS = -shared $(LDFLAGS) -o", ] ) - cflags.append(self.compiler.cc_pic_flag) + cflags.append(pkg.compiler.cc_pic_flag) else: makefile_inc.extend( [ @@ -182,9 +194,9 @@ class Scotch(CMakePackage): # Compiler-Specific Options # - if self.compiler.name == "gcc": + if pkg.compiler.name == "gcc": cflags.append("-Drestrict=__restrict") - elif self.compiler.name == "intel": + elif pkg.compiler.name == "intel": cflags.append("-Drestrict=") mpicc_path = self.spec["mpi"].mpicc if "+mpi" in self.spec else "mpicc" @@ -237,8 +249,8 @@ class Scotch(CMakePackage): with open("Makefile.inc", "w") as fh: fh.write("\n".join(makefile_inc)) - @when("@:6") - def install(self, spec, prefix): + @property + def build_targets(self): targets = ["scotch"] if "+mpi" in self.spec: targets.append("ptscotch") @@ -248,65 +260,4 @@ class Scotch(CMakePackage): targets.append("esmumps") if "+mpi" in self.spec: targets.append("ptesmumps") - - with working_dir("src"): - for target in targets: - # It seems that building ptesmumps in parallel fails, for - # version prior to 6.0.0 there is no separated targets force - # ptesmumps, this library is built by the ptscotch target. This - # should explain the test for the can_make_parallel variable - can_make_parallel = not ( - target == "ptesmumps" - or (self.spec.version < Version("6.0.0") and target == "ptscotch") - ) - make(target, parallel=can_make_parallel) - - lib_ext = dso_suffix if "+shared" in self.spec else "a" - # It seems easier to remove metis wrappers from the folder that will be - # installed than to tweak the Makefiles - if "+metis" not in self.spec: - with working_dir("lib"): - force_remove("libscotchmetis.{0}".format(lib_ext)) - force_remove("libptscotchparmetis.{0}".format(lib_ext)) - - with working_dir("include"): - force_remove("metis.h") - force_remove("parmetis.h") - - if "~esmumps" in self.spec and self.spec.version < Version("6.0.0"): - with working_dir("lib"): - force_remove("libesmumps.{0}".format(lib_ext)) - force_remove("libptesmumps.{0}".format(lib_ext)) - - with working_dir("include"): - force_remove("esmumps.h") - - install_tree("bin", prefix.bin) - install_tree("lib", prefix.lib) - install_tree("include", prefix.include) - install_tree("man/man1", prefix.share.man.man1) - - @when("@:6") - def cmake(self, spec, prefix): - self.configure() - - @when("@:6") - def build(self, spec, prefix): - pass - - def cmake_args(self): - spec = self.spec - args = [ - self.define_from_variant("BUILD_LIBSCOTCHMETIS", "metis"), - self.define_from_variant("INSTALL_METIS_HEADERS", "metis"), - self.define_from_variant("BUILD_LIBESMUMPS", "esmumps"), - self.define_from_variant("BUILD_SHARED_LIBS", "shared"), - self.define_from_variant("BUILD_PTSCOTCH", "mpi"), - ] - - # TODO should we enable/disable THREADS? - - if "+int64" in spec: - args.append("-DINTSIZE=64") - - return args + return targets diff --git a/var/spack/repos/builtin/packages/superlu/package.py b/var/spack/repos/builtin/packages/superlu/package.py index 8f20f90ea4..e8bd7c38f6 100644 --- a/var/spack/repos/builtin/packages/superlu/package.py +++ b/var/spack/repos/builtin/packages/superlu/package.py @@ -2,15 +2,16 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import os from llnl.util import tty +import spack.build_systems.cmake +import spack.build_systems.generic from spack.package import * -class Superlu(CMakePackage): +class Superlu(CMakePackage, Package): """SuperLU is a general purpose library for the direct solution of large, sparse, nonsymmetric systems of linear equations on high performance machines. SuperLU is designed for sequential machines.""" @@ -36,9 +37,12 @@ class Superlu(CMakePackage): url="https://crd-legacy.lbl.gov/~xiaoye/SuperLU/superlu_4.2.tar.gz", ) + build_system( + conditional("cmake", when="@5:"), conditional("autotools", when="@:4"), default="cmake" + ) + variant("pic", default=True, description="Build with position independent code") - depends_on("cmake", when="@5:", type="build") depends_on("blas") conflicts( "@:5.2.1", @@ -48,76 +52,7 @@ class Superlu(CMakePackage): test_requires_compiler = True - # CMake installation method - def cmake_args(self): - if self.version > Version("5.2.1"): - _blaslib_key = "enable_internal_blaslib" - else: - _blaslib_key = "enable_blaslib" - args = [ - self.define(_blaslib_key, False), - self.define("CMAKE_INSTALL_LIBDIR", self.prefix.lib), - self.define_from_variant("CMAKE_POSITION_INDEPENDENT_CODE", "pic"), - self.define("enable_tests", self.run_tests), - ] - - return args - # Pre-cmake installation method - @when("@:4") - def cmake(self, spec, prefix): - """Use autotools before version 5""" - config = [] - - # Define make.inc file - config.extend( - [ - "PLAT = _x86_64", - "SuperLUroot = %s" % self.stage.source_path, - # 'SUPERLULIB = $(SuperLUroot)/lib/libsuperlu$(PLAT).a', - "SUPERLULIB = $(SuperLUroot)/lib/libsuperlu_{0}.a".format(self.spec.version), - "BLASDEF = -DUSE_VENDOR_BLAS", - "BLASLIB = {0}".format(spec["blas"].libs.ld_flags), - # or BLASLIB = -L/usr/lib64 -lblas - "TMGLIB = libtmglib.a", - "LIBS = $(SUPERLULIB) $(BLASLIB)", - "ARCH = ar", - "ARCHFLAGS = cr", - "RANLIB = {0}".format("ranlib" if which("ranlib") else "echo"), - "CC = {0}".format(env["CC"]), - "FORTRAN = {0}".format(env["FC"]), - "LOADER = {0}".format(env["CC"]), - "CDEFS = -DAdd_", - ] - ) - - if "+pic" in spec: - config.extend( - [ - # Use these lines instead when pic_flag capability arrives - "CFLAGS = -O3 {0}".format(self.compiler.cc_pic_flag), - "NOOPTS = {0}".format(self.compiler.cc_pic_flag), - "FFLAGS = -O2 {0}".format(self.compiler.f77_pic_flag), - "LOADOPTS = {0}".format(self.compiler.cc_pic_flag), - ] - ) - else: - config.extend( - ["CFLAGS = -O3", "NOOPTS = ", "FFLAGS = -O2", "LOADOPTS = "] - ) - - # Write configuration options to make.inc file - with open("make.inc", "w") as inc: - for option in config: - inc.write("{0}\n".format(option)) - - make(parallel=False) - - # Install manually - install_tree("lib", prefix.lib) - mkdir(prefix.include) - install(join_path("SRC", "*.h"), prefix.include) - examples_src_dir = "EXAMPLE" make_hdr_file = "make.inc" @@ -221,3 +156,67 @@ class Superlu(CMakePackage): return self.run_superlu_test(test_dir, exe, args) + + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + if self.pkg.version > Version("5.2.1"): + _blaslib_key = "enable_internal_blaslib" + else: + _blaslib_key = "enable_blaslib" + args = [ + self.define(_blaslib_key, False), + self.define("CMAKE_INSTALL_LIBDIR", self.prefix.lib), + self.define_from_variant("CMAKE_POSITION_INDEPENDENT_CODE", "pic"), + self.define("enable_tests", self.pkg.run_tests), + ] + return args + + +class GenericBuilder(spack.build_systems.generic.GenericBuilder): + def install(self, pkg, spec, prefix): + """Use autotools before version 5""" + # Define make.inc file + config = [ + "PLAT = _x86_64", + "SuperLUroot = %s" % self.pkg.stage.source_path, + # 'SUPERLULIB = $(SuperLUroot)/lib/libsuperlu$(PLAT).a', + "SUPERLULIB = $(SuperLUroot)/lib/libsuperlu_{0}.a".format(self.pkg.spec.version), + "BLASDEF = -DUSE_VENDOR_BLAS", + "BLASLIB = {0}".format(spec["blas"].libs.ld_flags), + # or BLASLIB = -L/usr/lib64 -lblas + "TMGLIB = libtmglib.a", + "LIBS = $(SUPERLULIB) $(BLASLIB)", + "ARCH = ar", + "ARCHFLAGS = cr", + "RANLIB = {0}".format("ranlib" if which("ranlib") else "echo"), + "CC = {0}".format(env["CC"]), + "FORTRAN = {0}".format(env["FC"]), + "LOADER = {0}".format(env["CC"]), + "CDEFS = -DAdd_", + ] + + if "+pic" in spec: + config.extend( + [ + # Use these lines instead when pic_flag capability arrives + "CFLAGS = -O3 {0}".format(self.pkg.compiler.cc_pic_flag), + "NOOPTS = {0}".format(self.pkg.compiler.cc_pic_flag), + "FFLAGS = -O2 {0}".format(self.pkg.compiler.f77_pic_flag), + "LOADOPTS = {0}".format(self.pkg.compiler.cc_pic_flag), + ] + ) + else: + config.extend( + ["CFLAGS = -O3", "NOOPTS = ", "FFLAGS = -O2", "LOADOPTS = "] + ) + + with open("make.inc", "w") as inc: + for option in config: + inc.write("{0}\n".format(option)) + + make(parallel=False) + + install_tree("lib", prefix.lib) + mkdir(prefix.include) + install(join_path("SRC", "*.h"), prefix.include) diff --git a/var/spack/repos/builtin/packages/swig/package.py b/var/spack/repos/builtin/packages/swig/package.py index d1a760ffec..2bb9853526 100644 --- a/var/spack/repos/builtin/packages/swig/package.py +++ b/var/spack/repos/builtin/packages/swig/package.py @@ -2,10 +2,10 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - import os import re +import spack.build_systems.autotools from spack.package import * @@ -57,10 +57,10 @@ class Swig(AutotoolsPackage, SourceforgePackage): depends_on("pcre") - _autoconf_versions = ["@master", "@fortran", "@4.0.2-fortran", "@4.1.dev1-fortran"] + AUTOCONF_VERSIONS = ["@master", "@fortran", "@4.0.2-fortran", "@4.1.dev1-fortran"] # Git releases do *not* include configure script - for _version in _autoconf_versions: + for _version in AUTOCONF_VERSIONS: depends_on("autoconf", type="build", when=_version) depends_on("automake", type="build", when=_version) depends_on("libtool", type="build", when=_version) @@ -70,8 +70,6 @@ class Swig(AutotoolsPackage, SourceforgePackage): depends_on("automake@1.15:", type="build", when="target={0}:".format(_target)) depends_on("pkgconfig", type="build") - build_directory = "spack-build" - conflicts("%nvhpc", when="@:4.0.2") @classmethod @@ -80,18 +78,6 @@ class Swig(AutotoolsPackage, SourceforgePackage): match = re.search(r"SWIG\s+Version\s+(\S+)", output) return match.group(1) if match else None - @run_after("install") - def create_symlink(self): - # CMake compatibility: see https://github.com/spack/spack/pull/6240 - with working_dir(self.prefix.bin): - os.symlink("swig", "swig{0}".format(self.spec.version.up_to(2))) - - for _version in _autoconf_versions: - - @when(_version) - def autoreconf(self, spec, prefix): - which("sh")("./autogen.sh") - @property def _installed_exe(self): return join_path(self.prefix, "bin", "swig") @@ -134,3 +120,19 @@ class Swig(AutotoolsPackage, SourceforgePackage): def test(self): self._test_version() self._test_swiglib() + + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): + build_directory = "spack-build" + + @run_after("install") + def create_symlink(self): + # CMake compatibility: see https://github.com/spack/spack/pull/6240 + with working_dir(self.prefix.bin): + os.symlink("swig", "swig{0}".format(self.spec.version.up_to(2))) + + for _version in Swig.AUTOCONF_VERSIONS: + + @when(_version) + def autoreconf(self, pkg, spec, prefix): + which("sh")("./autogen.sh") diff --git a/var/spack/repos/builtin/packages/sz/package.py b/var/spack/repos/builtin/packages/sz/package.py index e16657a6b6..d83e0a52d5 100644 --- a/var/spack/repos/builtin/packages/sz/package.py +++ b/var/spack/repos/builtin/packages/sz/package.py @@ -2,11 +2,12 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - +import spack.build_systems.autotools +import spack.build_systems.cmake from spack.package import * -class Sz(CMakePackage): +class Sz(CMakePackage, AutotoolsPackage): """Error-bounded Lossy Compressor for HPC Data""" homepage = "https://szcompressor.org" @@ -43,6 +44,12 @@ class Sz(CMakePackage): version("1.4.10.0", sha256="cf23cf1ffd7c69c3d3128ae9c356b6acdc03a38f92c02db5d9bfc04f3fabc506") version("1.4.9.2", sha256="9dc785274d068d04c2836955fc93518a9797bfd409b46fea5733294b7c7c18f8") + build_system( + conditional("autotools", when="@:2.1.8.0"), + conditional("cmake", when="@2.1.8.1:"), + default="cmake", + ) + variant("python", default=False, description="builds the python wrapper") variant("netcdf", default=False, description="build the netcdf reader") variant("hdf5", default=False, description="build the hdf5 filter") @@ -71,86 +78,6 @@ class Sz(CMakePackage): patch("ctags-only-if-requested.patch", when="@2.1.8.1:2.1.8.3") - @property - def build_directory(self): - """autotools needs a different build directory to work""" - if self.version >= Version("2.1.8.1"): - return "spack-build" - else: - return "." - - @when("@:2.1.8.0") - def cmake(self, spec, prefix): - """use autotools before 2.1.8.1""" - configure_args = ["--prefix=" + prefix] - if "+fortran" in spec: - configure_args.append("--enable-fortran") - else: - configure_args.append("--disable-fortran") - configure(*configure_args) - # at least the v2.0.2.0 tarball contains object files - # which need to be cleaned out - make("clean") - - def cmake_args(self): - """configure the package with CMake for version 2.1.8.1 and later""" - args = [] - - if "+python" in self.spec: - args.append("-DBUILD_PYTHON_WRAPPER=ON") - args.append("-DSZ_PYTHON_SITELIB={0}".format(python_platlib)) - else: - args.append("-DBUILD_PYTHON_WRAPPER=OFF") - - if "+netcdf" in self.spec: - args.append("-DBUILD_NETCDF_READER=ON") - else: - args.append("-DBUILD_NETCDF_READER=OFF") - - if "+hdf5" in self.spec: - args.append("-DBUILD_HDF5_FILTER=ON") - else: - args.append("-DBUILD_HDF5_FILTER=OFF") - - if "+pastri" in self.spec: - args.append("-DBUILD_PASTRI=ON") - else: - args.append("-DBUILD_PASTRI=OFF") - - if "+time_compression" in self.spec: - args.append("-DBUILD_TIMECMPR=ON") - else: - args.append("-DBUILD_TIMECMPR=OFF") - - if "+random_access" in self.spec: - args.append("-DBUILD_RANDOMACCESS=ON") - else: - args.append("-DBUILD_RANDOMACCESS=OFF") - - if "+fortran" in self.spec: - args.append("-DBUILD_FORTRAN=ON") - else: - args.append("-DBUILD_FORTRAN=OFF") - - if "+shared" in self.spec: - args.append("-DBUILD_SHARED_LIBS=ON") - else: - args.append("-DBUILD_SHARED_LIBS=OFF") - - if "+stats" in self.spec: - args.append("-DBUILD_STATS=ON") - else: - args.append("-DBUILD_STATS=OFF") - - args.append(self.define("BUILD_TESTS", self.run_tests)) - - return args - - @run_after("build") - @on_package_attributes(run_tests=True) - def test_build(self): - make("test") - def _test_2d_float(self): """This test performs simple 2D compression/decompression (float)""" test_data_dir = self.test_suite.current_test_data_dir @@ -232,3 +159,37 @@ class Sz(CMakePackage): self._test_2d_float() # run 3D compression and decompression (float) self._test_3d_float() + + +class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder): + build_directory = "." + + def configure_args(self): + return self.enable_or_disable("fortran") + + @run_before("build") + def make_clean(self): + # at least the v2.0.2.0 tarball contains object files + # which need to be cleaned out + make("clean") + + +class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder): + def cmake_args(self): + result = [ + self.define_from_variant("BUILD_NETCDF_READER", "netcdf"), + self.define_from_variant("BUILD_HDF5_FILTER", "hdf5"), + self.define_from_variant("BUILD_PASTRI", "pastri"), + self.define_from_variant("BUILD_TIMECPR", "time_compression"), + self.define_from_variant("BUILD_RANDOMACCESS", "random_access"), + self.define_from_variant("BUILD_FORTRAN", "fortran"), + self.define_from_variant("BUILD_SHARED_LIBS", "shared"), + self.define_from_variant("BUILD_STATS", "stats"), + self.define("BUILD_TESTS", self.pkg.run_tests), + self.define_from_variant("BUILD_PYTHON_WRAPPER", "python"), + ] + + if "+python" in self.spec: + result.append(self.define("SZ_PYTHON_SITELIB", python_platlib)) + + return result diff --git a/var/spack/repos/builtin/packages/timemory/package.py b/var/spack/repos/builtin/packages/timemory/package.py index 759a7437d1..f30e578779 100644 --- a/var/spack/repos/builtin/packages/timemory/package.py +++ b/var/spack/repos/builtin/packages/timemory/package.py @@ -8,7 +8,7 @@ from spack.package import * -class Timemory(CMakePackage, PythonPackage): +class Timemory(CMakePackage, PythonExtension): """Modular profiling toolkit and suite of libraries and tools for C/C++/Fortran/CUDA/Python""" diff --git a/var/spack/repos/builtin/packages/uncrustify/package.py b/var/spack/repos/builtin/packages/uncrustify/package.py index a9148c01e5..b528302ff6 100644 --- a/var/spack/repos/builtin/packages/uncrustify/package.py +++ b/var/spack/repos/builtin/packages/uncrustify/package.py @@ -6,7 +6,7 @@ from spack.package import * -class Uncrustify(Package): +class Uncrustify(CMakePackage, AutotoolsPackage): """Source Code Beautifier for C, C++, C#, ObjectiveC, Java, and others.""" homepage = "http://uncrustify.sourceforge.net/" @@ -31,28 +31,15 @@ class Uncrustify(Package): version("0.62", commit="5987f2") version("0.61", sha256="1df0e5a2716e256f0a4993db12f23d10195b3030326fdf2e07f8e6421e172df9") - depends_on("cmake", type="build", when="@0.64:") - depends_on("automake", type="build", when="@0.63") - depends_on("autoconf", type="build", when="@0.63") - - @when("@0.64:") - def install(self, spec, prefix): - with working_dir("spack-build", create=True): - cmake("..", *std_cmake_args) - make() - make("install") - - @when("@0.63") - def install(self, spec, prefix): - which("bash")("autogen.sh") - configure("--prefix={0}".format(self.prefix)) - make() - make("install") - - @when("@:0.62") - def install(self, spec, prefix): - configure("--prefix={0}".format(self.prefix)) - make() - make("install") + build_system( + conditional("cmake", when="@0.64:"), + conditional("autotools", when="@:0.63"), + default="cmake", + ) + + with when("build_system=autotools"): + depends_on("automake", type="build") + depends_on("autoconf", type="build") + depends_on("libtool", type="build", when="@0.63") patch("uncrustify-includes.patch", when="@0.73") diff --git a/var/spack/repos/builtin/packages/zlib/package.py b/var/spack/repos/builtin/packages/zlib/package.py index 76add72552..58db86f6b2 100644 --- a/var/spack/repos/builtin/packages/zlib/package.py +++ b/var/spack/repos/builtin/packages/zlib/package.py @@ -9,10 +9,12 @@ import glob import os +import spack.build_systems.generic +import spack.build_systems.makefile from spack.package import * -class Zlib(Package): +class Zlib(MakefilePackage, Package): """A free, general-purpose, legally unencumbered lossless data-compression library. """ @@ -43,10 +45,14 @@ class Zlib(Package): deprecated=True, ) + build_system("makefile", conditional("generic", when="platform=windows"), default="makefile") + variant("pic", default=True, description="Produce position-independent code (for shared libs)") variant("shared", default=True, description="Enables the build of shared libraries.") variant("optimize", default=True, description="Enable -O2 for a more optimized lib") + conflicts("build_system=makefile", when="platform=windows") + patch("w_patch.patch", when="@1.2.11%cce") patch("configure-cc.patch", when="@1.2.12") @@ -55,11 +61,31 @@ class Zlib(Package): shared = "+shared" in self.spec return find_libraries(["libz"], root=self.prefix, recursive=True, shared=shared) - def win_install(self): - build_dir = self.stage.source_path - install_tree = {} - install_tree["bin"] = glob.glob(os.path.join(build_dir, "*.dll")) - install_tree["lib"] = glob.glob(os.path.join(build_dir, "*.lib")) + +class SetupEnvironment(object): + def setup_build_environment(self, env): + if "+pic" in self.spec: + env.append_flags("CFLAGS", self.pkg.compiler.cc_pic_flag) + if "+optimize" in self.spec: + env.append_flags("CFLAGS", "-O2") + + +class MakefileBuilder(spack.build_systems.makefile.MakefileBuilder, SetupEnvironment): + def edit(self, pkg, spec, prefix): + config_args = [] + if "~shared" in self.spec: + config_args.append("--static") + configure("--prefix={0}".format(prefix), *config_args) + + +class GenericBuilder(spack.build_systems.generic.GenericBuilder, SetupEnvironment): + def install(self, spec, prefix): + nmake("-f" "win32\\Makefile.msc") + build_dir = self.pkg.stage.source_path + install_tree = { + "bin": glob.glob(os.path.join(build_dir, "*.dll")), + "lib": glob.glob(os.path.join(build_dir, "*.lib")), + } compose_src_path = lambda x: os.path.join(build_dir, x) install_tree["include"] = [compose_src_path("zlib.h"), compose_src_path("zconf.h")] # Windows path seps are fine here as this method is Windows specific. @@ -76,24 +102,3 @@ class Zlib(Package): install(file, install_dst) installtree(self.prefix, install_tree) - - def setup_build_environment(self, env): - if "+pic" in self.spec: - env.append_flags("CFLAGS", self.compiler.cc_pic_flag) - if "+optimize" in self.spec: - env.append_flags("CFLAGS", "-O2") - - def install(self, spec, prefix): - if "platform=windows" in self.spec: - nmake("-f" "win32\\Makefile.msc") - self.win_install() - else: - config_args = [] - if "~shared" in spec: - config_args.append("--static") - configure("--prefix={0}".format(prefix), *config_args) - - make() - if self.run_tests: - make("check") - make("install") |