# Copyright 2013-2024 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
import os
from typing import List
import llnl.util.filesystem as fs
import spack.builder
import spack.package_base
from spack.directives import build_system, conflicts, depends_on, variant
from spack.multimethod import when
from ._checks import BaseBuilder, execute_build_time_tests
[docs]
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 attribute is used in UI queries that need to know the build
#: system base class
build_system_class = "MesonPackage"
#: Legacy buildsystem attribute used to deserialize and install old specs
legacy_buildsystem = "meson"
build_system("meson")
with when("build_system=meson"):
variant(
"buildtype",
default="release",
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")
# Python detection in meson requires distutils to be importable, but distutils no longer
# exists in Python 3.12. In Spack, we can't use setuptools as distutils replacement,
# because the distutils-precedence.pth startup file that setuptools ships with is not run
# when setuptools is in PYTHONPATH; it has to be in system site-packages. In a future meson
# release, the distutils requirement will be dropped, so this conflict can be relaxed.
# We have patches to make it work with meson 1.1 and above.
conflicts("^python@3.12:", when="^meson@:1.0")
[docs]
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", [])
[docs]
@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:`~.MesonBuilder.meson_args`.
For a finer tuning you may also override:
+-----------------------------------------------+--------------------+
| **Method** | **Purpose** |
+===============================================+====================+
| :py:meth:`~.MesonBuilder.root_mesonlists_dir` | Location of the |
| | root MesonLists.txt|
+-----------------------------------------------+--------------------+
| :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")
#: 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: List[str] = []
install_targets = ["install"]
build_time_test_callbacks = ["check"]
@property
def archive_files(self):
"""Files to archive for packages based on Meson"""
return [os.path.join(self.build_directory, "meson-logs", "meson-log.txt")]
@property
def root_mesonlists_dir(self):
"""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 self.pkg.stage.source_path
@property
def std_meson_args(self):
"""Standard meson arguments provided as a property for convenience
of package writers.
"""
# standard Meson arguments
std_meson_args = MesonBuilder.std_args(self.pkg)
std_meson_args += getattr(self, "meson_flag_args", [])
return std_meson_args
[docs]
@staticmethod
def std_args(pkg):
"""Standard meson arguments for a generic package."""
try:
build_type = pkg.spec.variants["buildtype"].value
except KeyError:
build_type = "release"
strip = "true" if "+strip" in pkg.spec else "false"
if "default_library=static,shared" in pkg.spec:
default_library = "both"
elif "default_library=static" in pkg.spec:
default_library = "static"
else:
default_library = "shared"
return [
"-Dprefix={0}".format(pkg.prefix),
# If we do not specify libdir explicitly, Meson chooses something
# like lib/x86_64-linux-gnu, which causes problems when trying to
# find libraries and pkg-config files.
# See https://github.com/mesonbuild/meson/issues/2197
"-Dlibdir={0}".format(pkg.prefix.lib),
"-Dbuildtype={0}".format(build_type),
"-Dstrip={0}".format(strip),
"-Ddefault_library={0}".format(default_library),
# Do not automatically download and install dependencies
"-Dwrap_mode=nodownload",
]
@property
def build_dirname(self):
"""Returns the directory name to use when building the package."""
return "spack-build-{}".format(self.spec.dag_hash(7))
@property
def build_directory(self):
"""Directory to use when building the package."""
return os.path.join(self.pkg.stage.path, self.build_dirname)
[docs]
def meson_args(self):
"""List of arguments that must be passed to meson, except:
* ``--prefix``
* ``--libdir``
* ``--buildtype``
* ``--strip``
* ``--default_library``
which will be set automatically.
"""
return []
[docs]
def meson(self, pkg, spec, prefix):
"""Run ``meson`` in the build directory"""
options = []
if self.spec["meson"].satisfies("@0.64:"):
options.append("setup")
options.append(os.path.abspath(self.root_mesonlists_dir))
options += self.std_meson_args
options += self.meson_args()
with fs.working_dir(self.build_directory, create=True):
inspect.getmodule(self.pkg).meson(*options)
[docs]
def build(self, pkg, spec, prefix):
"""Make the build targets"""
options = ["-v"]
options += self.build_targets
with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).ninja(*options)
[docs]
def install(self, pkg, spec, prefix):
"""Make the install targets"""
with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).ninja(*self.install_targets)
spack.builder.run_after("build")(execute_build_time_tests)
[docs]
def check(self):
"""Search Meson-generated files for the target ``test`` and run it if found."""
with fs.working_dir(self.build_directory):
self.pkg._if_ninja_target_execute("test")
self.pkg._if_ninja_target_execute("check")