Extendable Perl (#3614)

* perl: make extendable and add Module::Build package
* perl: allow 'spack create' to identify perl packages from their contents
* perl-module-build: fix indenting of package docstring
* perl: split install() method for extensions into phases
* perl: auto-detect build method (Makefile.PL vs Build.PL) and define a 'check' method
* PerlPackage: use import statements similar to those in AutotoolsPackage
* PerlModule: fix detection of Build.PL
* PerlPackageTemplate: remove extraneous lines to avoid flake8 warnings
* PerlPackageTemplate: split into separate templates for Makefile.PL and Build.PL
* PerlPackage: add cross-references to docstrings
* AutotoolsPackage: fix ambiguous cross-references to avoid errors in doc tests
* PerlbuildPackageTemplate: depend on perl-module-build if Build.PL exists
This commit is contained in:
Milton Woods 2017-03-31 09:38:58 +10:00 committed by Todd Gamblin
parent 9e1abb13dc
commit 9e43ff821c
10 changed files with 264 additions and 8 deletions

View File

@ -2043,6 +2043,10 @@ The classes that are currently provided by Spack are:
| :py:class:`.PythonPackage` | Specialized class for | | :py:class:`.PythonPackage` | Specialized class for |
| | :py:class:`.Python` extensions | | | :py:class:`.Python` extensions |
+------------------------------------+----------------------------------+ +------------------------------------+----------------------------------+
| :py:class:`.PerlPackage` | Specialized class for |
| | :py:class:`.Perl` extensions |
+------------------------------------+----------------------------------+

View File

@ -162,6 +162,7 @@
from spack.build_systems.cmake import CMakePackage from spack.build_systems.cmake import CMakePackage
from spack.build_systems.python import PythonPackage from spack.build_systems.python import PythonPackage
from spack.build_systems.r import RPackage from spack.build_systems.r import RPackage
from spack.build_systems.perl import PerlPackage
__all__ += [ __all__ += [
'run_before', 'run_before',
@ -172,7 +173,8 @@
'AutotoolsPackage', 'AutotoolsPackage',
'MakefilePackage', 'MakefilePackage',
'PythonPackage', 'PythonPackage',
'RPackage' 'RPackage',
'PerlPackage'
] ]
from spack.version import Version, ver from spack.version import Version, ver

View File

@ -49,7 +49,8 @@ class AutotoolsPackage(PackageBase):
4. :py:meth:`~.AutotoolsPackage.install` 4. :py:meth:`~.AutotoolsPackage.install`
They all have sensible defaults and for many packages the only thing They all have sensible defaults and for many packages the only thing
necessary will be to override the helper method :py:meth:`.configure_args`. necessary will be to override the helper method
:py:meth:`~.AutotoolsPackage.configure_args`.
For a finer tuning you may also override: For a finer tuning you may also override:
+-----------------------------------------------+--------------------+ +-----------------------------------------------+--------------------+
@ -234,7 +235,7 @@ def set_configure_or_die(self):
appropriately, otherwise raises an error. appropriately, otherwise raises an error.
:raises RuntimeError: if a configure script is not found in :raises RuntimeError: if a configure script is not found in
:py:meth:`~.configure_directory` :py:meth:`~AutotoolsPackage.configure_directory`
""" """
# Check if a configure script is there. If not raise a RuntimeError. # Check if a configure script is there. If not raise a RuntimeError.
if not os.path.exists(self.configure_abs_path): if not os.path.exists(self.configure_abs_path):
@ -255,7 +256,8 @@ def configure_args(self):
return [] return []
def configure(self, spec, prefix): def configure(self, spec, prefix):
"""Runs configure with the arguments specified in :py:meth:`.configure_args` """Runs configure with the arguments specified in
:py:meth:`~.AutotoolsPackage.configure_args`
and an appropriately set prefix. and an appropriately set prefix.
""" """
options = ['--prefix={0}'.format(prefix)] + self.configure_args() options = ['--prefix={0}'.format(prefix)] + self.configure_args()

View File

@ -0,0 +1,117 @@
##############################################################################
# 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
import os
from llnl.util.filesystem import join_path
from spack.directives import extends
from spack.package import PackageBase, run_after
from spack.util.executable import Executable
class PerlPackage(PackageBase):
"""Specialized class for packages that are built using Perl.
This class provides four phases that can be overridden if required:
1. :py:meth:`~.PerlPackage.configure`
2. :py:meth:`~.PerlPackage.build`
3. :py:meth:`~.PerlPackage.check`
4. :py:meth:`~.PerlPackage.install`
The default methods use, in order of preference:
(1) Makefile.PL,
(2) Build.PL.
Some packages may need to override
:py:meth:`~.PerlPackage.configure_args`,
which produces a list of arguments for
:py:meth:`~.PerlPackage.configure`.
Arguments should not include the installation base directory.
"""
#: Phases of a Perl package
phases = ['configure', 'build', 'install']
#: This attribute is used in UI queries that need to know the build
#: system base class
build_system_class = 'PerlPackage'
#: Callback names for build-time test
build_time_test_callbacks = ['check']
extends('perl')
def configure_args(self):
"""Produces a list containing the arguments that must be passed to
:py:meth:`~.PerlPackage.configure`. Arguments should not include
the installation base directory, which is prepended automatically.
:return: list of arguments for Makefile.PL or Build.PL
"""
return []
def configure(self, spec, prefix):
"""Runs Makefile.PL or Build.PL with arguments consisting of
an appropriate installation base directory followed by the
list returned by :py:meth:`~.PerlPackage.configure_args`.
:raise RuntimeError: if neither Makefile.PL or Build.PL exist
"""
if os.path.isfile('Makefile.PL'):
self.build_method = 'Makefile.PL'
self.build_executable = inspect.getmodule(self).make
elif os.path.isfile('Build.PL'):
self.build_method = 'Build.PL'
self.build_executable = Executable(
join_path(self.stage.source_path, 'Build'))
else:
raise RuntimeError('Unknown build_method for perl package')
if self.build_method == 'Makefile.PL':
options = ['Makefile.PL', 'INSTALL_BASE={0}'.format(prefix)]
elif self.build_method == 'Build.PL':
options = ['Build.PL', '--install_base', prefix]
options += self.configure_args()
inspect.getmodule(self).perl(*options)
def build(self, spec, prefix):
"""Builds a Perl package."""
self.build_executable()
# Ensure that tests run after build (if requested):
run_after('build')(PackageBase._run_default_build_time_test_callbacks)
def check(self):
"""Runs built-in tests of a Perl package."""
self.build_executable('test')
def install(self, spec, prefix):
"""Installs a Perl package."""
self.build_executable('install')
# Check that self.prefix is there after installation
run_after('install')(PackageBase.sanity_check_prefix)

View File

@ -31,7 +31,8 @@
build_system_to_phase = { build_system_to_phase = {
CMakePackage: 'build', CMakePackage: 'build',
AutotoolsPackage: 'build', AutotoolsPackage: 'build',
PythonPackage: 'build' PythonPackage: 'build',
PerlPackage: 'build'
} }

View File

@ -36,7 +36,8 @@
build_system_to_phase = { build_system_to_phase = {
CMakePackage: 'cmake', CMakePackage: 'cmake',
AutotoolsPackage: 'configure' AutotoolsPackage: 'configure',
PerlPackage: 'configure'
} }

View File

@ -268,6 +268,45 @@ def __init__(self, name, *args):
super(RPackageTemplate, self).__init__(name, *args) super(RPackageTemplate, self).__init__(name, *args)
class PerlmakePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Perl extensions
that come with a Makefile.PL"""
base_class_name = 'PerlPackage'
dependencies = """\
# FIXME: Add dependencies if required:
# depends_on('perl-foo')
# depends_on('barbaz', type=('build', 'link', 'run'))"""
body = """\
# FIXME: If non-standard arguments are used for configure step:
# def configure_args(self):
# return ['my', 'configure', 'args']
# FIXME: in unusual cases, it may be necessary to override methods for
# configure(), build(), check() or install()."""
def __init__(self, name, *args):
# If the user provided `--name perl-cpp`, don't rename it perl-perl-cpp
if not name.startswith('perl-'):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to perl-{0}".format(name))
name = 'perl-{0}'.format(name)
super(PerlmakePackageTemplate, self).__init__(name, *args)
class PerlbuildPackageTemplate(PerlmakePackageTemplate):
"""Provides appropriate overrides for Perl extensions
that come with a Build.PL instead of a Makefile.PL"""
dependencies = """\
depends_on('perl-module-build', type='build')
# FIXME: Add additional dependencies if required:
# depends_on('perl-foo')
# depends_on('barbaz', type=('build', 'link', 'run'))"""
class OctavePackageTemplate(PackageTemplate): class OctavePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for octave packages""" """Provides appropriate overrides for octave packages"""
@ -305,6 +344,8 @@ def __init__(self, name, *args):
'bazel': BazelPackageTemplate, 'bazel': BazelPackageTemplate,
'python': PythonPackageTemplate, 'python': PythonPackageTemplate,
'r': RPackageTemplate, 'r': RPackageTemplate,
'perlmake': PerlmakePackageTemplate,
'perlbuild': PerlbuildPackageTemplate,
'octave': OctavePackageTemplate, 'octave': OctavePackageTemplate,
'generic': PackageTemplate 'generic': PackageTemplate
} }
@ -363,7 +404,9 @@ def __call__(self, stage, url):
(r'/SConstruct$', 'scons'), (r'/SConstruct$', 'scons'),
(r'/setup.py$', 'python'), (r'/setup.py$', 'python'),
(r'/NAMESPACE$', 'r'), (r'/NAMESPACE$', 'r'),
(r'/WORKSPACE$', 'bazel') (r'/WORKSPACE$', 'bazel'),
(r'/Build.PL$', 'perlbuild'),
(r'/Makefile.PL$', 'perlmake'),
] ]
# Peek inside the compressed file. # Peek inside the compressed file.

View File

@ -38,6 +38,8 @@
('setup.py', 'python'), ('setup.py', 'python'),
('NAMESPACE', 'r'), ('NAMESPACE', 'r'),
('WORKSPACE', 'bazel'), ('WORKSPACE', 'bazel'),
('Makefile.PL', 'perlmake'),
('Build.PL', 'perlbuild'),
('foobar', 'generic') ('foobar', 'generic')
] ]
) )

View File

@ -0,0 +1,41 @@
##############################################################################
# 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
##############################################################################
#
from spack import *
class PerlModuleBuild(PerlPackage):
"""Module::Build is a system for building, testing, and installing Perl
modules. It is meant to be an alternative to ExtUtils::MakeMaker.
Developers may alter the behavior of the module through subclassing in a
much more straightforward way than with MakeMaker. It also does not
require a make on your system - most of the Module::Build code is
pure-perl and written in a very cross-platform way.
"""
homepage = "http://search.cpan.org/perldoc/Module::Build"
url = "http://search.cpan.org/CPAN/authors/id/L/LE/LEONT/Module-Build-0.4220.tar.gz"
version('0.4220', '9df204e188462a4410d496f316c2c531')

View File

@ -23,6 +23,8 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
############################################################################## ##############################################################################
# #
# Author: Milton Woods <milton.woods@bom.gov.au>
# Date: March 22, 2017
# Author: George Hartzell <hartzell@alerce.com> # Author: George Hartzell <hartzell@alerce.com>
# Date: July 21, 2016 # Date: July 21, 2016
# Author: Justin Too <justin@doubleotoo.com> # Author: Justin Too <justin@doubleotoo.com>
@ -46,6 +48,8 @@ class Perl(Package): # Perl doesn't use Autotools, it should subclass Package
# https://rt.perl.org/Public/Bug/Display.html?id=123784 # https://rt.perl.org/Public/Bug/Display.html?id=123784
# version('5.18.4' , '1f9334ff730adc05acd3dd7130d295db') # version('5.18.4' , '1f9334ff730adc05acd3dd7130d295db')
extendable = True
# Installing cpanm alongside the core makes it safe and simple for # Installing cpanm alongside the core makes it safe and simple for
# people/projects to install their own sets of perl modules. Not # people/projects to install their own sets of perl modules. Not
# having it in core increases the "energy of activation" for doing # having it in core increases the "energy of activation" for doing
@ -80,3 +84,42 @@ def install(self, spec, prefix):
perl('Makefile.PL') perl('Makefile.PL')
make() make()
make('install') make('install')
def setup_environment(self, spack_env, run_env):
"""Set PERL5LIB to support activation of Perl packages"""
run_env.set('PERL5LIB', join_path(self.prefix, 'lib', 'perl5'))
def setup_dependent_environment(self, spack_env, run_env, extension_spec):
"""Set PATH and PERL5LIB to include the extension and
any other perl extensions it depends on,
assuming they were installed with INSTALL_BASE defined."""
perl_lib_dirs = []
perl_bin_dirs = []
for d in extension_spec.traverse(
deptype=('build', 'run'), deptype_query='run'):
if d.package.extends(self.spec):
perl_lib_dirs.append(join_path(d.prefix, 'lib', 'perl5'))
perl_bin_dirs.append(join_path(d.prefix, 'bin'))
perl_bin_path = ':'.join(perl_bin_dirs)
perl_lib_path = ':'.join(perl_lib_dirs)
spack_env.prepend_path('PATH', perl_bin_path)
spack_env.prepend_path('PERL5LIB', perl_lib_path)
run_env.prepend_path('PATH', perl_bin_path)
run_env.prepend_path('PERL5LIB', perl_lib_path)
def setup_dependent_package(self, module, ext_spec):
"""Called before perl modules' install() methods.
In most cases, extensions will only need to have one line:
perl('Makefile.PL','INSTALL_BASE=%s' % self.prefix)
"""
# perl extension builds can have a global perl executable function
module.perl = Executable(join_path(self.spec.prefix.bin, 'perl'))
# Add variables for library directory
module.perl_lib_dir = join_path(ext_spec.prefix, 'lib', 'perl5')
# Make the site packages directory for extensions,
# if it does not exist already.
if ext_spec.package.is_extension:
mkdirp(module.perl_lib_dir)