Merge pull request #1171 from adamjstewart/features/octave

Allow spack create to automatically detect octave build system
This commit is contained in:
Todd Gamblin 2016-07-18 12:32:32 -07:00 committed by GitHub
commit 653f25d676
4 changed files with 111 additions and 100 deletions

View File

@ -42,7 +42,8 @@ def setup_parser(subparser):
'--keep-stage', action='store_true', dest='keep_stage', '--keep-stage', action='store_true', dest='keep_stage',
help="Don't clean up staging area when command completes.") help="Don't clean up staging area when command completes.")
subparser.add_argument( subparser.add_argument(
'versions', nargs=argparse.REMAINDER, help='Versions to generate checksums for') 'versions', nargs=argparse.REMAINDER,
help='Versions to generate checksums for')
def get_checksums(versions, urls, **kwargs): def get_checksums(versions, urls, **kwargs):
@ -59,10 +60,10 @@ def get_checksums(versions, urls, **kwargs):
with Stage(url, keep=keep_stage) as stage: with Stage(url, keep=keep_stage) as stage:
stage.fetch() stage.fetch()
if i == 0 and first_stage_function: if i == 0 and first_stage_function:
first_stage_function(stage) first_stage_function(stage, url)
hashes.append((version, hashes.append((version, spack.util.crypto.checksum(
spack.util.crypto.checksum(hashlib.md5, stage.archive_file))) hashlib.md5, stage.archive_file)))
i += 1 i += 1
except FailedDownloadError as e: except FailedDownloadError as e:
tty.msg("Failed to fetch %s" % url) tty.msg("Failed to fetch %s" % url)
@ -79,12 +80,12 @@ def checksum(parser, args):
# If the user asked for specific versions, use those. # If the user asked for specific versions, use those.
if args.versions: if args.versions:
versions = {} versions = {}
for v in args.versions: for version in args.versions:
v = ver(v) version = ver(version)
if not isinstance(v, Version): if not isinstance(version, Version):
tty.die("Cannot generate checksums for version lists or " + tty.die("Cannot generate checksums for version lists or " +
"version ranges. Use unambiguous versions.") "version ranges. Use unambiguous versions.")
versions[v] = pkg.url_for_version(v) versions[version] = pkg.url_for_version(version)
else: else:
versions = pkg.fetch_remote_versions() versions = pkg.fetch_remote_versions()
if not versions: if not versions:
@ -111,5 +112,7 @@ def checksum(parser, args):
if not version_hashes: if not version_hashes:
tty.die("Could not fetch any versions for %s" % pkg.name) tty.die("Could not fetch any versions for %s" % pkg.name)
version_lines = [" version('%s', '%s')" % (v, h) for v, h in version_hashes] version_lines = [
" version('%s', '%s')" % (v, h) for v, h in version_hashes
]
tty.msg("Checksummed new versions of %s:" % pkg.name, *version_lines) tty.msg("Checksummed new versions of %s:" % pkg.name, *version_lines)

View File

@ -103,54 +103,19 @@ def install(self, spec, prefix):
${install} ${install}
""") """)
# Build dependencies and extensions
def make_version_calls(ver_hash_tuples): dependencies_dict = {
"""Adds a version() call to the package for each version found."""
max_len = max(len(str(v)) for v, h in ver_hash_tuples)
format = " version(%%-%ds, '%%s')" % (max_len + 2)
return '\n'.join(format % ("'%s'" % v, h) for v, h in ver_hash_tuples)
def setup_parser(subparser):
subparser.add_argument('url', nargs='?', help="url of package archive")
subparser.add_argument(
'--keep-stage', action='store_true',
help="Don't clean up staging area when command completes.")
subparser.add_argument(
'-n', '--name', dest='alternate_name', default=None, metavar='NAME',
help="Override the autodetected name for the created package.")
subparser.add_argument(
'-r', '--repo', default=None,
help="Path to a repository where the package should be created.")
subparser.add_argument(
'-N', '--namespace',
help="Specify a namespace for the package. Must be the namespace of "
"a repository registered with Spack.")
subparser.add_argument(
'-f', '--force', action='store_true', dest='force',
help="Overwrite any existing package file with the same name.")
setup_parser.subparser = subparser
class ConfigureGuesser(object):
def __call__(self, stage):
"""Try to guess the type of build system used by the project.
Set any necessary build dependencies or extensions.
Set the appropriate default installation instructions."""
# Build dependencies and extensions
dependenciesDict = {
'autotools': "# depends_on('foo')", 'autotools': "# depends_on('foo')",
'cmake': "depends_on('cmake', type='build')", 'cmake': "depends_on('cmake')",
'scons': "depends_on('scons', type='build')", 'scons': "depends_on('scons')",
'python': "extends('python', type=nolink)", 'python': "extends('python')",
'R': "extends('R')", 'R': "extends('R')",
'octave': "extends('octave')",
'unknown': "# depends_on('foo')" 'unknown': "# depends_on('foo')"
} }
# Default installation instructions # Default installation instructions
installDict = { install_dict = {
'autotools': """\ 'autotools': """\
# FIXME: Modify the configure line to suit your build system here. # FIXME: Modify the configure line to suit your build system here.
configure('--prefix={0}'.format(prefix)) configure('--prefix={0}'.format(prefix))
@ -182,11 +147,61 @@ def __call__(self, stage):
R('CMD', 'INSTALL', '--library={0}'.format(self.module.r_lib_dir), R('CMD', 'INSTALL', '--library={0}'.format(self.module.r_lib_dir),
self.stage.source_path)""", self.stage.source_path)""",
'octave': """\
# FIXME: Add logic to build and install here.
octave('--quiet', '--norc',
'--built-in-docstrings-file=/dev/null',
'--texi-macros-file=/dev/null',
'--eval', 'pkg prefix {0}; pkg install {1}'.format(
prefix, self.stage.archive_file))""",
'unknown': """\ 'unknown': """\
# FIXME: Unknown build system # FIXME: Unknown build system
make() make()
make('install')""" make('install')"""
} }
def make_version_calls(ver_hash_tuples):
"""Adds a version() call to the package for each version found."""
max_len = max(len(str(v)) for v, h in ver_hash_tuples)
format = " version(%%-%ds, '%%s')" % (max_len + 2)
return '\n'.join(format % ("'%s'" % v, h) for v, h in ver_hash_tuples)
def setup_parser(subparser):
subparser.add_argument('url', nargs='?', help="url of package archive")
subparser.add_argument(
'--keep-stage', action='store_true',
help="Don't clean up staging area when command completes.")
subparser.add_argument(
'-n', '--name', dest='alternate_name', default=None, metavar='NAME',
help="Override the autodetected name for the created package.")
subparser.add_argument(
'-r', '--repo', default=None,
help="Path to a repository where the package should be created.")
subparser.add_argument(
'-N', '--namespace',
help="Specify a namespace for the package. Must be the namespace of "
"a repository registered with Spack.")
subparser.add_argument(
'-f', '--force', action='store_true', dest='force',
help="Overwrite any existing package file with the same name.")
setup_parser.subparser = subparser
class BuildSystemGuesser(object):
def __call__(self, stage, url):
"""Try to guess the type of build system used by a project based on
the contents of its archive or the URL it was downloaded from."""
# Most octave extensions are hosted on Octave-Forge:
# http://octave.sourceforge.net/index.html
# They all have the same base URL.
if 'downloads.sourceforge.net/octave/' in url:
self.build_system = 'octave'
return
# A list of clues that give us an idea of the build system a package # A list of clues that give us an idea of the build system a package
# uses. If the regular expression matches a file contained in the # uses. If the regular expression matches a file contained in the
@ -224,12 +239,6 @@ def __call__(self, stage):
self.build_system = build_system self.build_system = build_system
# Set any necessary build dependencies or extensions.
self.dependencies = dependenciesDict[build_system]
# Set the appropriate default installation instructions
self.install = installDict[build_system]
def guess_name_and_version(url, args): def guess_name_and_version(url, args):
# Try to deduce name and version of the new package from the URL # Try to deduce name and version of the new package from the URL
@ -334,8 +343,8 @@ def create(parser, args):
# Fetch tarballs (prompting user if necessary) # Fetch tarballs (prompting user if necessary)
versions, urls = fetch_tarballs(url, name, version) versions, urls = fetch_tarballs(url, name, version)
# Try to guess what configure system is used. # Try to guess what build system is used.
guesser = ConfigureGuesser() guesser = BuildSystemGuesser()
ver_hash_tuples = spack.cmd.checksum.get_checksums( ver_hash_tuples = spack.cmd.checksum.get_checksums(
versions, urls, versions, urls,
first_stage_function=guesser, first_stage_function=guesser,
@ -344,13 +353,13 @@ def create(parser, args):
if not ver_hash_tuples: if not ver_hash_tuples:
tty.die("Could not fetch any tarballs for %s" % name) tty.die("Could not fetch any tarballs for %s" % name)
# Prepend 'py-' to python package names, by convention. # Add prefix to package name if it is an extension.
if guesser.build_system == 'python': if guesser.build_system == 'python':
name = 'py-%s' % name name = 'py-{0}'.format(name)
# Prepend 'r-' to R package names, by convention.
if guesser.build_system == 'R': if guesser.build_system == 'R':
name = 'r-%s' % name name = 'r-{0}'.format(name)
if guesser.build_system == 'octave':
name = 'octave-{0}'.format(name)
# Create a directory for the new package. # Create a directory for the new package.
pkg_path = repo.filename_for_package_name(name) pkg_path = repo.filename_for_package_name(name)
@ -367,8 +376,8 @@ def create(parser, args):
class_name=mod_to_class(name), class_name=mod_to_class(name),
url=url, url=url,
versions=make_version_calls(ver_hash_tuples), versions=make_version_calls(ver_hash_tuples),
dependencies=guesser.dependencies, dependencies=dependencies_dict[guesser.build_system],
install=guesser.install)) install=install_dict[guesser.build_system]))
# If everything checks out, go ahead and edit. # If everything checks out, go ahead and edit.
spack.editor(pkg_path) spack.editor(pkg_path)

View File

@ -32,15 +32,16 @@
from spack.test.tally_plugin import Tally from spack.test.tally_plugin import Tally
"""Names of tests to be included in Spack's test suite""" """Names of tests to be included in Spack's test suite"""
test_names = ['architecture', 'versions', 'url_parse', 'url_substitution', 'packages', 'stage', test_names = [
'spec_syntax', 'spec_semantics', 'spec_dag', 'concretize', 'architecture', 'versions', 'url_parse', 'url_substitution', 'packages',
'multimethod', 'install', 'package_sanity', 'config', 'stage', 'spec_syntax', 'spec_semantics', 'spec_dag', 'concretize',
'directory_layout', 'pattern', 'python_version', 'git_fetch', 'multimethod', 'install', 'package_sanity', 'config', 'directory_layout',
'svn_fetch', 'hg_fetch', 'mirror', 'modules', 'url_extrapolate', 'pattern', 'python_version', 'git_fetch', 'svn_fetch', 'hg_fetch',
'cc', 'link_tree', 'spec_yaml', 'optional_deps', 'mirror', 'modules', 'url_extrapolate', 'cc', 'link_tree', 'spec_yaml',
'make_executable', 'configure_guess', 'lock', 'database', 'optional_deps', 'make_executable', 'build_system_guess', 'lock',
'namespace_trie', 'yaml', 'sbang', 'environment', 'cmd.find', 'database', 'namespace_trie', 'yaml', 'sbang', 'environment', 'cmd.find',
'cmd.uninstall', 'cmd.test_install', 'cmd.test_compiler_cmd'] 'cmd.uninstall', 'cmd.test_install', 'cmd.test_compiler_cmd'
]
def list_tests(): def list_tests():

View File

@ -28,14 +28,14 @@
import unittest import unittest
from llnl.util.filesystem import * from llnl.util.filesystem import *
from spack.cmd.create import ConfigureGuesser from spack.cmd.create import BuildSystemGuesser
from spack.stage import Stage from spack.stage import Stage
from spack.test.mock_packages_test import * from spack.test.mock_packages_test import *
from spack.util.executable import which from spack.util.executable import which
class InstallTest(unittest.TestCase): class InstallTest(unittest.TestCase):
"""Tests the configure guesser in spack create""" """Tests the build system guesser in spack create"""
def setUp(self): def setUp(self):
self.tar = which('tar') self.tar = which('tar')
@ -44,12 +44,10 @@ def setUp(self):
os.chdir(self.tmpdir) os.chdir(self.tmpdir)
self.stage = None self.stage = None
def tearDown(self): def tearDown(self):
shutil.rmtree(self.tmpdir, ignore_errors=True) shutil.rmtree(self.tmpdir, ignore_errors=True)
os.chdir(self.orig_dir) os.chdir(self.orig_dir)
def check_archive(self, filename, system): def check_archive(self, filename, system):
mkdirp('archive') mkdirp('archive')
touch(join_path('archive', filename)) touch(join_path('archive', filename))
@ -60,24 +58,24 @@ def check_archive(self, filename, system):
with Stage(url) as stage: with Stage(url) as stage:
stage.fetch() stage.fetch()
guesser = ConfigureGuesser() guesser = BuildSystemGuesser()
guesser(stage) guesser(stage, url)
self.assertEqual(system, guesser.build_system) self.assertEqual(system, guesser.build_system)
def test_python(self):
self.check_archive('setup.py', 'python')
def test_autotools(self): def test_autotools(self):
self.check_archive('configure', 'autotools') self.check_archive('configure', 'autotools')
def test_cmake(self): def test_cmake(self):
self.check_archive('CMakeLists.txt', 'cmake') self.check_archive('CMakeLists.txt', 'cmake')
def test_scons(self):
self.check_archive('SConstruct', 'scons')
def test_python(self):
self.check_archive('setup.py', 'python')
def test_R(self):
self.check_archive('NAMESPACE', 'R')
def test_unknown(self): def test_unknown(self):
self.check_archive('foobar', 'unknown') self.check_archive('foobar', 'unknown')