From b34da4e108b7441d26ca6c02871cba65a0530566 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 30 Jul 2019 17:12:48 -0500 Subject: Add SIPPackage base class (#12157) --- lib/spack/docs/build_systems.rst | 1 + lib/spack/docs/build_systems/sippackage.rst | 135 ++++++++++++++++++++++++++++ lib/spack/spack/build_systems/sip.py | 113 +++++++++++++++++++++++ lib/spack/spack/cmd/build.py | 2 + lib/spack/spack/cmd/configure.py | 2 + lib/spack/spack/cmd/create.py | 24 +++++ lib/spack/spack/pkgkit.py | 1 + lib/spack/spack/test/build_system_guess.py | 1 + 8 files changed, 279 insertions(+) create mode 100644 lib/spack/docs/build_systems/sippackage.rst create mode 100644 lib/spack/spack/build_systems/sip.py (limited to 'lib') diff --git a/lib/spack/docs/build_systems.rst b/lib/spack/docs/build_systems.rst index 513082d9b8..93b8e5c7a8 100644 --- a/lib/spack/docs/build_systems.rst +++ b/lib/spack/docs/build_systems.rst @@ -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 diff --git a/lib/spack/docs/build_systems/sippackage.rst b/lib/spack/docs/build_systems/sippackage.rst new file mode 100644 index 0000000000..cb744e858f --- /dev/null +++ b/lib/spack/docs/build_systems/sippackage.rst @@ -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 ```` 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 diff --git a/lib/spack/spack/build_systems/sip.py b/lib/spack/spack/build_systems/sip.py new file mode 100644 index 0000000000..6d51ff57bd --- /dev/null +++ b/lib/spack/spack/build_systems/sip.py @@ -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) diff --git a/lib/spack/spack/cmd/build.py b/lib/spack/spack/cmd/build.py index bbace88b31..84d8221090 100644 --- a/lib/spack/spack/cmd/build.py +++ b/lib/spack/spack/cmd/build.py @@ -13,6 +13,7 @@ from spack.build_systems.waf import WafPackage 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 @@ build_system_to_phase = { PythonPackage: 'build', PerlPackage: 'build', MesonPackage: 'build', + SIPPackage: 'build', } diff --git a/lib/spack/spack/cmd/configure.py b/lib/spack/spack/cmd/configure.py index 64608b4643..b06d4edf1c 100644 --- a/lib/spack/spack/cmd/configure.py +++ b/lib/spack/spack/cmd/configure.py @@ -16,6 +16,7 @@ from spack.build_systems.waf import WafPackage 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 @@ build_system_to_phase = { PerlPackage: 'configure', IntelPackage: 'configure', MesonPackage: 'meson', + SIPPackage: 'configure', } diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py index 792a437f29..59e3274bdb 100644 --- a/lib/spack/spack/cmd/create.py +++ b/lib/spack/spack/cmd/create.py @@ -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 @@ templates = { 'makefile': MakefilePackageTemplate, 'intel': IntelPackageTemplate, 'meson': MesonPackageTemplate, + 'sip': SIPPackageTemplate, 'generic': PackageTemplate, } @@ -440,6 +463,7 @@ class BuildSystemGuesser: (r'/(GNU)?[Mm]akefile$', 'makefile'), (r'/DESCRIPTION$', 'octave'), (r'/meson\.build$', 'meson'), + (r'/configure\.py$', 'sip'), ] # Peek inside the compressed file. diff --git a/lib/spack/spack/pkgkit.py b/lib/spack/spack/pkgkit.py index 85c1ee264e..ff3288f48a 100644 --- a/lib/spack/spack/pkgkit.py +++ b/lib/spack/spack/pkgkit.py @@ -27,6 +27,7 @@ from spack.build_systems.r import RPackage 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 diff --git a/lib/spack/spack/test/build_system_guess.py b/lib/spack/spack/test/build_system_guess.py index 80dbb35f15..cd6216a907 100644 --- a/lib/spack/spack/test/build_system_guess.py +++ b/lib/spack/spack/test/build_system_guess.py @@ -27,6 +27,7 @@ import spack.stage ('makefile', 'makefile'), ('Makefile', 'makefile'), ('meson.build', 'meson'), + ('configure.py', 'sip'), ('foobar', 'generic') ] ) -- cgit v1.2.3-70-g09d2