diff --git a/seps/sep-0002.md b/seps/sep-0002.md new file mode 100644 index 0000000..e8966fa --- /dev/null +++ b/seps/sep-0002.md @@ -0,0 +1,100 @@ +# Support multiple build-systems per package using a directive + +## The problem + +There are multiple packages that are either changing their build-system during the evolution of the project, or using different build-systems for different platforms. Spack, at the moment, provides no support to model this use case. As a result we wrote some [documentation](https://github.com/spack/spack/pull/25174) to enumerate various workarounds that people adopted - each with its own drawbacks. What we would like to have in the long term is proper support for packages that can be built using multiple build-systems. + +## Proposed changes + +What we propose here to add support for packages using multiple build-systems is to __add another directive__ to the package DSL. If we take as an example `hdf5` this would look like: +```python +class Hdf5(Package): + build_system('cmake', when='@X.Y:') + build_system('autotools') +``` +Each build-system can be subject to a constraint. In the example above, for instance, `cmake` can be used only if the version of `hdf5` is greater or equal than `X.Y` (while `autotools` is always available). + +We can also assume that there is an __implicit preference order__ based on on the order of declaration in the class. In the example above this means that, if no other requests are made by the user, `cmake` is preferred to `autotools` when available. + +Build systems can be specified in specs using a key-value pair: +``` +hdf5 build_system=cmake +``` +__which requires the keyword `build_system` to be reserved by Spack__ (similarly to what is done for `dev_path`). It is interesting to note that modeling build-systems this way make them similar to signle-valued variants with the only difference that the set of allowed values is "dynamic" and subject to constraints. + +__Each package needs to have one and only one build-system__. + +### Overriding build-system phases + +Some packages may need to override phases of the build-systems they use and there must be a way to specify which phase for which build-system is overridden. What we propose to use here is a decorator to distinguish among multiple build-systems: +```python +class Hdf5(Package): + build_system('cmake', when='@X.Y:') + build_system('autotools') + + @cmake + def build(self, spec, prefix): + pass + + @autotools + def build(self, spec, prefix): + pass +``` +Using a decorator permits seamlessly to override phases with the same name. + +## Implementation at a high-level + +Currently the build-system specific methods are grouped in base classes and each package uses them via inheritance. There's a fair amount of machinery with metaclasses that is used to have dynamic phases for each build-system. + +To support multiple build-systems we should probably add, as it happens in other directives, a dictionary of builder objects: +```python +hdf5.build_system = { + 'cmake': cmake_builder_obj, + 'autotools': autotools_builder_obj +} +``` + +Each builder object will accept a _package object tied to a concrete spec_ as argument and contain all the methods that are relevant for building it. + +The main idea is to: +1. Move things like `phases` and other similar methods into a class of their own +2. Use a different object for each package so that the package can override these methods if need be +3. Have decorators that can monkey patch the object to customize the install phases + +Dipatching to the correct build system object happens once we have a concrete spec associated with the package (since the spec coveys information about the build system, we know which one to select). + +### Backward compatibility + +Most of the packages are currently fine with a single build-system and they inherit almost all of the related methods from a base class: + +```python +class OpenJpeg(CmakePackage): + pass +``` + +To be backward compatible, if we push the methods and attributes down to a "builder" class, we can maintain the directives in the same base class: +```python +class CMakePackage(PackageBase): + # Can be overridden if conditional in derived classes + build_system('cmake') + depends_on('cmake', type='build') + +class CMakeBuildSystem(object): + phases = [ + 'cmake', + 'build', + 'install' + ] + + @staticmethod + def cmake(pkg, spec, prefix): + pass + + @staticmethod + def setup_build_environment(pkg, spec, prefix): + pass +``` +When we construct the package object we can check the methods defined on the package to see if there are overrides for the build-system being used, without the +need to employ a decorator. + +We can special case this behavior to packages where there's only a single, unconstrained build-system and otherwise require the use of decorators for more clarity.