Add SIPPackage base class (#12157)

This commit is contained in:
Adam J. Stewart
2019-07-30 17:12:48 -05:00
committed by GitHub
parent f487f87e3e
commit b34da4e108
18 changed files with 371 additions and 44 deletions

View File

@@ -40,6 +40,7 @@ on these ideas for each distinct build system that Spack supports:
build_systems/cmakepackage
build_systems/mesonpackage
build_systems/qmakepackage
build_systems/sippackage
.. toctree::
:maxdepth: 1

View File

@@ -0,0 +1,135 @@
.. Copyright 2013-2019 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)
.. _sippackage:
----------
SIPPackage
----------
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
the Qt toolkit, but can be used to create bindings for any C or C++ library.
SIP comprises a code generator and a Python module. The code generator
processes a set of specification files and generates C or C++ code which is
then compiled to create the bindings extension module. The SIP Python module
provides support functions to the automatically generated code.
^^^^^^
Phases
^^^^^^
The ``SIPPackage`` base class comes with the following phases:
#. ``configure`` - configure the package
#. ``build`` - build the package
#. ``install`` - install the package
By default, these phases run:
.. code-block:: console
$ python configure.py --bindir ... --destdir ...
$ make
$ make install
^^^^^^^^^^^^^^^
Important files
^^^^^^^^^^^^^^^
Each SIP package comes with a custom ``configure.py`` build script,
written in Python. This script contains instructions to build the project.
^^^^^^^^^^^^^^^^^^^^^^^^^
Build system dependencies
^^^^^^^^^^^^^^^^^^^^^^^^^
``SIPPackage`` requires several dependencies. Python is needed to run
the ``configure.py`` build script, and to run the resulting Python
libraries. Qt is needed to provide the ``qmake`` command. SIP is also
needed to build the package. All of these dependencies are automatically
added via the base class
.. code-block:: python
extends('python')
depends_on('qt', type='build')
depends_on('py-sip', type='build')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Passing arguments to ``configure.py``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Each phase comes with a ``<phase_args>`` function that can be used to pass
arguments to that particular phase. For example, if you need to pass
arguments to the configure phase, you can use:
.. code-block:: python
def configure_args(self, spec, prefix):
return ['--no-python-dbus']
A list of valid options can be found by running ``python configure.py --help``.
^^^^^^^
Testing
^^^^^^^
Just because a package successfully built does not mean that it built
correctly. The most reliable test of whether or not the package was
correctly installed is to attempt to import all of the modules that
get installed. To get a list of modules, run the following command
in the site-packages directory:
.. code-block:: console
$ python
>>> import setuptools
>>> setuptools.find_packages()
['QtPy5']
Large, complex packages like ``QtPy5`` will return a long list of
packages, while other packages may return an empty list. These packages
only install a single ``foo.py`` file. In Python packaging lingo,
a "package" is a directory containing files like:
.. code-block:: none
foo/__init__.py
foo/bar.py
foo/baz.py
whereas a "module" is a single Python file. Since ``find_packages``
only returns packages, you'll have to determine the correct module
names yourself. You can now add these packages and modules to the
package like so:
.. code-block:: python
import_modules = ['PyQt5']
When you run ``spack install --test=root py-pyqt5``, Spack will attempt
to import the ``PyQt5`` module after installation.
These tests most often catch missing dependencies and non-RPATHed
libraries.
^^^^^^^^^^^^^^^^^^^^^^
External documentation
^^^^^^^^^^^^^^^^^^^^^^
For more information on the SIP build system, see:
* https://www.riverbankcomputing.com/software/sip/intro
* https://www.riverbankcomputing.com/static/Docs/sip/
* https://wiki.python.org/moin/SIP

View File

@@ -0,0 +1,113 @@
# Copyright 2013-2019 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 llnl.util.filesystem import working_dir
from spack.directives import depends_on, extends
from spack.package import PackageBase, run_after
class SIPPackage(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'
#: Callback names for install-time test
install_time_test_callbacks = ['import_module_test']
extends('python')
depends_on('qt')
depends_on('py-sip')
def configure_file(self):
"""Returns the name of the configure file to use."""
return 'configure.py'
def python(self, *args, **kwargs):
"""The python ``Executable``."""
inspect.getmodule(self).python(*args, **kwargs)
def configure(self, spec, prefix):
"""Configure the package."""
configure = self.configure_file()
args = self.configure_args()
args.extend([
'--verbose',
'--confirm-license',
'--qmake', spec['qt'].prefix.bin.qmake,
'--sip', spec['py-sip'].prefix.bin.sip,
'--sip-incdir', os.path.join(
spec['py-sip'].prefix,
spec['python'].package.python_include_dir
),
'--bindir', prefix.bin,
'--destdir', inspect.getmodule(self).site_packages_dir,
])
self.python(configure, *args)
def configure_args(self):
"""Arguments to pass to configure."""
return []
def build(self, spec, prefix):
"""Build the package."""
args = self.build_args()
inspect.getmodule(self).make(*args)
def build_args(self):
"""Arguments to pass to build."""
return []
def install(self, spec, prefix):
"""Install the package."""
args = self.install_args()
inspect.getmodule(self).make('install', parallel=False, *args)
def install_args(self):
"""Arguments to pass to install."""
return []
# Testing
def import_module_test(self):
"""Attempts to import the module that was just installed.
This test is only run if the package overrides
:py:attr:`import_modules` with a list of module names."""
# Make sure we are importing the installed modules,
# not the ones in the current directory
with working_dir('spack-test', create=True):
for module in self.import_modules:
self.python('-c', 'import {0}'.format(module))
run_after('install')(PackageBase._run_default_install_time_test_callbacks)
# Check that self.prefix is there after installation
run_after('install')(PackageBase.sanity_check_prefix)

View File

@@ -13,6 +13,7 @@
from spack.build_systems.python import PythonPackage
from spack.build_systems.perl import PerlPackage
from spack.build_systems.meson import MesonPackage
from spack.build_systems.sip import SIPPackage
description = 'stops at build stage when installing a package, if possible'
section = "build"
@@ -28,6 +29,7 @@
PythonPackage: 'build',
PerlPackage: 'build',
MesonPackage: 'build',
SIPPackage: 'build',
}

View File

@@ -16,6 +16,7 @@
from spack.build_systems.perl import PerlPackage
from spack.build_systems.intel import IntelPackage
from spack.build_systems.meson import MesonPackage
from spack.build_systems.sip import SIPPackage
description = 'stage and configure a package but do not install'
section = "build"
@@ -30,6 +31,7 @@
PerlPackage: 'configure',
IntelPackage: 'configure',
MesonPackage: 'meson',
SIPPackage: 'configure',
}

View File

@@ -350,6 +350,28 @@ class IntelPackageTemplate(PackageTemplate):
# FIXME: Override `setup_environment` if necessary."""
class SIPPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for SIP packages."""
base_class_name = 'SIPPackage'
body = """\
def configure_args(self, spec, prefix):
# FIXME: Add arguments other than --bindir and --destdir
# FIXME: If not needed delete this function
args = []
return args"""
def __init__(self, name, *args):
# If the user provided `--name py-pyqt4`, don't rename it py-py-pyqt4
if not name.startswith('py-'):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to py-{0}".format(name))
name = 'py-{0}'.format(name)
super(SIPPackageTemplate, self).__init__(name, *args)
templates = {
'autotools': AutotoolsPackageTemplate,
'autoreconf': AutoreconfPackageTemplate,
@@ -366,6 +388,7 @@ class IntelPackageTemplate(PackageTemplate):
'makefile': MakefilePackageTemplate,
'intel': IntelPackageTemplate,
'meson': MesonPackageTemplate,
'sip': SIPPackageTemplate,
'generic': PackageTemplate,
}
@@ -440,6 +463,7 @@ def __call__(self, stage, url):
(r'/(GNU)?[Mm]akefile$', 'makefile'),
(r'/DESCRIPTION$', 'octave'),
(r'/meson\.build$', 'meson'),
(r'/configure\.py$', 'sip'),
]
# Peek inside the compressed file.

View File

@@ -27,6 +27,7 @@
from spack.build_systems.perl import PerlPackage
from spack.build_systems.intel import IntelPackage
from spack.build_systems.meson import MesonPackage
from spack.build_systems.sip import SIPPackage
from spack.mixins import filter_compiler_wrappers

View File

@@ -27,6 +27,7 @@
('makefile', 'makefile'),
('Makefile', 'makefile'),
('meson.build', 'meson'),
('configure.py', 'sip'),
('foobar', 'generic')
]
)