From c0aaa8fcea6a9524c7fe0de08c7b373f2aba62d6 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Mon, 16 Jan 2017 18:13:37 -0800 Subject: Add PythonPackage base class - Add a PythonPackage class with build system support. - Support build phases in PythonPackage - Add a custom sanity check for PythonPackages - Get rid of nolink dependencies in python packages - Update spack create to use new PythonPackage class - Port most of Python packages to new PythonPackage class - Conducted a massive install and activate of Python packages. - Fixed bugs introduced by install and activate. - Update API docs on PythonPackage --- lib/spack/spack/__init__.py | 3 +- lib/spack/spack/build_systems/python.py | 309 ++++++++++++++++++++++++++++++++ lib/spack/spack/cmd/build.py | 3 +- lib/spack/spack/cmd/create.py | 15 +- lib/spack/spack/directives.py | 12 +- 5 files changed, 328 insertions(+), 14 deletions(-) create mode 100644 lib/spack/spack/build_systems/python.py (limited to 'lib') diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index 901b8f115c..1f8926e28d 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -159,9 +159,10 @@ from spack.package import Package from spack.build_systems.makefile import MakefilePackage from spack.build_systems.autotools import AutotoolsPackage from spack.build_systems.cmake import CMakePackage +from spack.build_systems.python import PythonPackage from spack.build_systems.r import RPackage __all__ += ['Package', 'CMakePackage', 'AutotoolsPackage', 'MakefilePackage', - 'RPackage'] + 'PythonPackage', 'RPackage'] from spack.version import Version, ver __all__ += ['Version', 'ver'] diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py new file mode 100644 index 0000000000..d21c291ae6 --- /dev/null +++ b/lib/spack/spack/build_systems/python.py @@ -0,0 +1,309 @@ +############################################################################## +# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory. +# +# This file is part of Spack. +# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. +# LLNL-CODE-647188 +# +# For details, see https://github.com/llnl/spack +# Please also see the LICENSE file for our notice and the LGPL. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (as +# published by the Free Software Foundation) version 2.1, February 1999. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and +# conditions of the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## + +import inspect + +from spack.directives import extends +from spack.package import PackageBase + +from llnl.util.filesystem import working_dir + + +class PythonPackage(PackageBase): + """Specialized class for packages that are built using Python + setup.py files + + This class provides the following phases that can be overridden: + + * build + * build_py + * build_ext + * build_clib + * build_scripts + * clean + * install + * install_lib + * install_headers + * install_scripts + * install_data + * sdist + * register + * bdist + * bdist_dumb + * bdist_rpm + * bdist_wininst + * upload + * check + + These are all standard setup.py commands and can be found by running: + + .. code-block:: console + + $ python setup.py --help-commands + + By default, only the 'build' and 'install' phases are run, but if you + need to run more phases, simply modify your ``phases`` list like so: + + .. code-block:: python + + phases = ['build_ext', 'install', 'bdist'] + + Each phase provides a function that runs: + + .. code-block:: console + + $ python --no-user-cfg setup.py + + Each phase also has a function that can pass arguments to + this call. All of these functions are empty except for the ``install_args`` + function, which passes ``--prefix=/path/to/installation/directory``. + + If you need to run a phase which is not a standard setup.py command, + you'll need to define a function for it like so: + + .. code-block:: python + + def configure(self, spec, prefix): + self.setup_py('configure') + """ + # 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 = 'PythonPackage' + + extends('python') + + def setup_file(self, spec, prefix): + """Returns the name of the setup file to use.""" + return 'setup.py' + + def build_directory(self): + """The directory containing the ``setup.py`` file.""" + return self.stage.source_path + + def python(self, *args): + inspect.getmodule(self).python(*args) + + def setup_py(self, *args): + setup = self.setup_file(self.spec, self.prefix) + + with working_dir(self.build_directory()): + self.python(setup, '--no-user-cfg', *args) + + # The following phases and their descriptions come from: + # $ python setup.py --help-commands + # Only standard commands are included here, but some packages + # define extra commands as well + + def build(self, spec, prefix): + """Build everything needed to install.""" + args = self.build_args(spec, prefix) + + self.setup_py('build', *args) + + def build_args(self, spec, prefix): + """Arguments to pass to build.""" + return [] + + def build_py(self, spec, prefix): + '''"Build" pure Python modules (copy to build directory).''' + args = self.build_py_args(spec, prefix) + + self.setup_py('build_py', *args) + + def build_py_args(self, spec, prefix): + """Arguments to pass to build_py.""" + return [] + + def build_ext(self, spec, prefix): + """Build C/C++ extensions (compile/link to build directory).""" + args = self.build_ext_args(spec, prefix) + + self.setup_py('build_ext', *args) + + def build_ext_args(self, spec, prefix): + """Arguments to pass to build_ext.""" + return [] + + def build_clib(self, spec, prefix): + """Build C/C++ libraries used by Python extensions.""" + args = self.build_clib_args(spec, prefix) + + self.setup_py('build_clib', *args) + + def build_clib_args(self, spec, prefix): + """Arguments to pass to build_clib.""" + return [] + + def build_scripts(self, spec, prefix): + '''"Build" scripts (copy and fixup #! line).''' + args = self.build_scripts_args(spec, prefix) + + self.setup_py('build_scripts', *args) + + def clean(self, spec, prefix): + """Clean up temporary files from 'build' command.""" + args = self.clean_args(spec, prefix) + + self.setup_py('clean', *args) + + def clean_args(self, spec, prefix): + """Arguments to pass to clean.""" + return [] + + def install(self, spec, prefix): + """Install everything from build directory.""" + args = self.install_args(spec, prefix) + + self.setup_py('install', *args) + + def install_args(self, spec, prefix): + """Arguments to pass to install.""" + return ['--prefix={0}'.format(prefix)] + + def install_lib(self, spec, prefix): + """Install all Python modules (extensions and pure Python).""" + args = self.install_lib_args(spec, prefix) + + self.setup_py('install_lib', *args) + + def install_lib_args(self, spec, prefix): + """Arguments to pass to install_lib.""" + return [] + + def install_headers(self, spec, prefix): + """Install C/C++ header files.""" + args = self.install_headers_args(spec, prefix) + + self.setup_py('install_headers', *args) + + def install_headers_args(self, spec, prefix): + """Arguments to pass to install_headers.""" + return [] + + def install_scripts(self, spec, prefix): + """Install scripts (Python or otherwise).""" + args = self.install_scripts_args(spec, prefix) + + self.setup_py('install_scripts', *args) + + def install_scripts_args(self, spec, prefix): + """Arguments to pass to install_scripts.""" + return [] + + def install_data(self, spec, prefix): + """Install data files.""" + args = self.install_data_args(spec, prefix) + + self.setup_py('install_data', *args) + + def install_data_args(self, spec, prefix): + """Arguments to pass to install_data.""" + return [] + + def sdist(self, spec, prefix): + """Create a source distribution (tarball, zip file, etc.).""" + args = self.sdist_args(spec, prefix) + + self.setup_py('sdist', *args) + + def sdist_args(self, spec, prefix): + """Arguments to pass to sdist.""" + return [] + + def register(self, spec, prefix): + """Register the distribution with the Python package index.""" + args = self.register_args(spec, prefix) + + self.setup_py('register', *args) + + def register_args(self, spec, prefix): + """Arguments to pass to register.""" + return [] + + def bdist(self, spec, prefix): + """Create a built (binary) distribution.""" + args = self.bdist_args(spec, prefix) + + self.setup_py('bdist', *args) + + def bdist_args(self, spec, prefix): + """Arguments to pass to bdist.""" + return [] + + def bdist_dumb(self, spec, prefix): + '''Create a "dumb" built distribution.''' + args = self.bdist_dumb_args(spec, prefix) + + self.setup_py('bdist_dumb', *args) + + def bdist_dumb_args(self, spec, prefix): + """Arguments to pass to bdist_dumb.""" + return [] + + def bdist_rpm(self, spec, prefix): + """Create an RPM distribution.""" + args = self.bdist_rpm(spec, prefix) + + self.setup_py('bdist_rpm', *args) + + def bdist_rpm_args(self, spec, prefix): + """Arguments to pass to bdist_rpm.""" + return [] + + def bdist_wininst(self, spec, prefix): + """Create an executable installer for MS Windows.""" + args = self.bdist_wininst_args(spec, prefix) + + self.setup_py('bdist_wininst', *args) + + def bdist_wininst_args(self, spec, prefix): + """Arguments to pass to bdist_wininst.""" + return [] + + def upload(self, spec, prefix): + """Upload binary package to PyPI.""" + args = self.upload_args(spec, prefix) + + self.setup_py('upload', *args) + + def upload_args(self, spec, prefix): + """Arguments to pass to upload.""" + return [] + + def check(self, spec, prefix): + """Perform some checks on the package.""" + args = self.check_args(spec, prefix) + + self.setup_py('check', *args) + + def check_args(self, spec, prefix): + """Arguments to pass to check.""" + return [] + + # Check that self.prefix is there after installation + PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix) diff --git a/lib/spack/spack/cmd/build.py b/lib/spack/spack/cmd/build.py index 703705a32c..6c0029794f 100644 --- a/lib/spack/spack/cmd/build.py +++ b/lib/spack/spack/cmd/build.py @@ -30,7 +30,8 @@ description = 'Stops at build stage when installing a package, if possible' build_system_to_phase = { CMakePackage: 'build', - AutotoolsPackage: 'build' + AutotoolsPackage: 'build', + PythonPackage: 'build' } diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py index a488104282..2575229581 100644 --- a/lib/spack/spack/cmd/create.py +++ b/lib/spack/spack/cmd/create.py @@ -194,19 +194,20 @@ class BazelPackageTemplate(PackageTemplate): class PythonPackageTemplate(PackageTemplate): - """Provides appropriate overrides for Python extensions""" + """Provides appropriate overrides for python extensions""" + base_class_name = 'PythonPackage' dependencies = """\ - extends('python') - - # FIXME: Add additional dependencies if required. + # FIXME: Add dependencies if required. # depends_on('py-setuptools', type='build') # depends_on('py-foo', type=('build', 'run'))""" body = """\ - def install(self, spec, prefix): - # FIXME: Add logic to build and install here. - setup_py('install', '--prefix={0}'.format(prefix))""" + def build_args(self): + # FIXME: Add arguments other than --prefix + # FIXME: If not needed delete the function + args = [] + return args""" def __init__(self, name, *args): # If the user provided `--name py-numpy`, don't rename it py-py-numpy diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py index 72656684e0..58eabb9e3b 100644 --- a/lib/spack/spack/directives.py +++ b/lib/spack/spack/directives.py @@ -289,9 +289,10 @@ def extends(spec, **kwargs): """ def _execute(pkg): - if pkg.extendees: - msg = 'Packages can extend at most one other package.' - raise DirectiveError(msg) + # if pkg.extendees: + # directive = 'extends' + # msg = 'Packages can extend at most one other package.' + # raise DirectiveError(directive, msg) when = kwargs.pop('when', pkg.name) _depends_on(pkg, spec, when=when) @@ -344,8 +345,9 @@ def variant(name, default=False, description=""): def _execute(pkg): if not re.match(spack.spec.identifier_re, name): - msg = 'Invalid variant name in {0}: \'{1}\'' - raise DirectiveError(msg.format(pkg.name, name)) + directive = 'variant' + msg = "Invalid variant name in {0}: '{1}'" + raise DirectiveError(directive, msg.format(pkg.name, name)) pkg.variants[name] = Variant(default, description) return _execute -- cgit v1.2.3-70-g09d2