Initial version of spack with one package: cmake
This commit is contained in:
commit
cc76c0f5f9
23 changed files with 1094 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.pyc
|
||||||
|
/opt/
|
||||||
|
/var/
|
||||||
|
*~
|
41
bin/spack
Executable file
41
bin/spack
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# Find spack's location and its prefix.
|
||||||
|
SPACK_FILE = os.environ["SPACK_FILE"] = os.path.expanduser(__file__)
|
||||||
|
SPACK_PREFIX = os.path.dirname(os.path.dirname(SPACK_FILE))
|
||||||
|
|
||||||
|
# Allow spack libs to be imported in our scripts
|
||||||
|
SPACK_LIB_PATH = os.path.join(SPACK_PREFIX, "lib", "spack")
|
||||||
|
sys.path.insert(0, SPACK_LIB_PATH)
|
||||||
|
|
||||||
|
# clean up the scope and start using spack package instead.
|
||||||
|
del SPACK_FILE, SPACK_PREFIX, SPACK_LIB_PATH
|
||||||
|
import spack
|
||||||
|
|
||||||
|
# Command parsing
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Spack: the Supercomputing PACKage Manager.')
|
||||||
|
parser.add_argument('-V', '--version', action='version', version="%s" % spack.version)
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose')
|
||||||
|
|
||||||
|
# each command module implements a parser() function, to which we pass its
|
||||||
|
# subparser for setup.
|
||||||
|
subparsers = parser.add_subparsers(title="subcommands", dest="command")
|
||||||
|
|
||||||
|
import spack.cmd
|
||||||
|
for cmd in spack.cmd.commands:
|
||||||
|
subparser = subparsers.add_parser(cmd)
|
||||||
|
module = spack.cmd.get_module(cmd)
|
||||||
|
module.setup_parser(subparser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Set up environment based on args.
|
||||||
|
spack.verbose = args.verbose
|
||||||
|
|
||||||
|
# Try to load the particular command asked for and run it
|
||||||
|
command = spack.cmd.get_command(args.command)
|
||||||
|
command(args)
|
168
lib/spack/spack/Package.py
Normal file
168
lib/spack/spack/Package.py
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from spack import *
|
||||||
|
import tty
|
||||||
|
import attr
|
||||||
|
import validate
|
||||||
|
import version
|
||||||
|
import shutil
|
||||||
|
import platform
|
||||||
|
from stage import Stage
|
||||||
|
|
||||||
|
def depends_on(*args, **kwargs):
|
||||||
|
"""Adds a depends_on local variable in the locals of
|
||||||
|
the calling class, based on args.
|
||||||
|
"""
|
||||||
|
stack = inspect.stack()
|
||||||
|
try:
|
||||||
|
locals = stack[1][0].f_locals
|
||||||
|
finally:
|
||||||
|
del stack
|
||||||
|
print locals
|
||||||
|
|
||||||
|
locals["depends_on"] = kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class Package(object):
|
||||||
|
def __init__(self):
|
||||||
|
attr.required(self, 'homepage')
|
||||||
|
attr.required(self, 'url')
|
||||||
|
attr.required(self, 'md5')
|
||||||
|
|
||||||
|
# Name of package is just the classname lowercased
|
||||||
|
self.name = self.__class__.__name__.lower()
|
||||||
|
|
||||||
|
# Make sure URL is an allowed type
|
||||||
|
validate.url(self.url)
|
||||||
|
|
||||||
|
v = version.parse(self.url)
|
||||||
|
if not v:
|
||||||
|
tty.die("Couldn't extract version from '%s'. " +
|
||||||
|
"You must specify it explicitly for this URL." % self.url)
|
||||||
|
self.version = v
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stage(self):
|
||||||
|
return Stage(self.stage_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stage_name(self):
|
||||||
|
return "%s-%s" % (self.name, self.version)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prefix(self):
|
||||||
|
return new_path(install_path, self.stage_name)
|
||||||
|
|
||||||
|
def do_fetch(self):
|
||||||
|
"""Creates a stage directory and downloads the taball for this package.
|
||||||
|
Working directory will be set to the stage directory.
|
||||||
|
"""
|
||||||
|
stage = self.stage
|
||||||
|
stage.setup()
|
||||||
|
stage.chdir()
|
||||||
|
|
||||||
|
archive_file = os.path.basename(self.url)
|
||||||
|
if not os.path.exists(archive_file):
|
||||||
|
tty.msg("Fetching %s" % self.url)
|
||||||
|
|
||||||
|
# Run curl but grab the mime type from the http headers
|
||||||
|
headers = curl('-#', '-O', '-D', '-', self.url, return_output=True)
|
||||||
|
|
||||||
|
# output this if we somehow got an HTML file rather than the archive we
|
||||||
|
# asked for.
|
||||||
|
if re.search(r'Content-Type: text/html', headers):
|
||||||
|
tty.warn("The contents of '%s' look like HTML. The checksum will "+
|
||||||
|
"likely fail. Use 'spack clean %s' to delete this file. "
|
||||||
|
"The fix the gateway issue and install again." % (archive_file, self.name))
|
||||||
|
|
||||||
|
if not os.path.exists(archive_file):
|
||||||
|
tty.die("Failed to download '%s'!" % self.url)
|
||||||
|
else:
|
||||||
|
tty.msg("Already downloaded %s." % self.name)
|
||||||
|
|
||||||
|
archive_md5 = md5(archive_file)
|
||||||
|
if archive_md5 != self.md5:
|
||||||
|
tty.die("MD5 Checksum failed for %s. Expected %s but got %s."
|
||||||
|
% (self.name, self.md5, archive_md5))
|
||||||
|
|
||||||
|
return archive_file
|
||||||
|
|
||||||
|
def do_stage(self):
|
||||||
|
"""Unpacks the fetched tarball, then changes into the expanded tarball directory."""
|
||||||
|
archive_file = self.do_fetch()
|
||||||
|
stage = self.stage
|
||||||
|
|
||||||
|
archive_dir = stage.archive_path
|
||||||
|
if not archive_dir:
|
||||||
|
tty.msg("Staging archive: '%s'" % archive_file)
|
||||||
|
decompress = decompressor_for(archive_file)
|
||||||
|
decompress(archive_file)
|
||||||
|
else:
|
||||||
|
tty.msg("Alredy staged %s" % self.name)
|
||||||
|
|
||||||
|
stage.chdir_to_archive()
|
||||||
|
|
||||||
|
def do_install(self):
|
||||||
|
"""This class should call this version of the install method.
|
||||||
|
Package implementations should override install().
|
||||||
|
"""
|
||||||
|
if os.path.exists(self.prefix):
|
||||||
|
tty.msg("%s is already installed." % self.name)
|
||||||
|
tty.pkg(self.prefix)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.do_stage()
|
||||||
|
|
||||||
|
# Populate the module scope of install() with some useful functions.
|
||||||
|
# This makes things easier for package writers.
|
||||||
|
self.module.configure = which("configure", [self.stage.archive_path])
|
||||||
|
self.module.cmake = which("cmake")
|
||||||
|
|
||||||
|
self.install(self.prefix)
|
||||||
|
tty.msg("Successfully installed %s" % self.name)
|
||||||
|
tty.pkg(self.prefix)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def module(self):
|
||||||
|
"""Use this to add variables to the class's module's scope.
|
||||||
|
This lets us use custom syntax in the install method.
|
||||||
|
"""
|
||||||
|
return __import__(self.__class__.__module__,
|
||||||
|
fromlist=[self.__class__.__name__])
|
||||||
|
|
||||||
|
def install(self, prefix):
|
||||||
|
"""Package implementations override this with their own build configuration."""
|
||||||
|
tty.die("Packages must provide an install method!")
|
||||||
|
|
||||||
|
def do_uninstall(self):
|
||||||
|
self.uninstall(self.prefix)
|
||||||
|
tty.msg("Successfully uninstalled %s." % self.name)
|
||||||
|
|
||||||
|
def uninstall(self, prefix):
|
||||||
|
"""By default just blows the install dir away."""
|
||||||
|
shutil.rmtree(self.prefix, True)
|
||||||
|
|
||||||
|
def do_clean(self):
|
||||||
|
self.clean()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""By default just runs make clean. Override if this isn't good."""
|
||||||
|
stage = self.stage
|
||||||
|
if stage.archive_path:
|
||||||
|
stage.chdir_to_archive()
|
||||||
|
try:
|
||||||
|
make("clean")
|
||||||
|
tty.msg("Successfully cleaned %s" % self.name)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# Might not be configured. Ignore.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_clean_all(self):
|
||||||
|
if os.path.exists(self.stage.path):
|
||||||
|
self.stage.destroy()
|
||||||
|
tty.msg("Successfully cleaned %s" % self.name)
|
||||||
|
|
||||||
|
|
5
lib/spack/spack/__init__.py
Normal file
5
lib/spack/spack/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
from globals import *
|
||||||
|
from fileutils import *
|
||||||
|
|
||||||
|
from Package import Package, depends_on
|
8
lib/spack/spack/attr.py
Normal file
8
lib/spack/spack/attr.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import spack.tty as tty
|
||||||
|
|
||||||
|
def required(obj, attr_name):
|
||||||
|
"""Ensure that a class has a required attribute."""
|
||||||
|
if not hasattr(obj, attr_name):
|
||||||
|
tty.die("No required attribute '%s' in class '%s'"
|
||||||
|
% (attr_name, obj.__class__.__name__))
|
||||||
|
|
36
lib/spack/spack/cmd/__init__.py
Normal file
36
lib/spack/spack/cmd/__init__.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import spack
|
||||||
|
import spack.tty as tty
|
||||||
|
|
||||||
|
SETUP_PARSER = "setup_parser"
|
||||||
|
command_path = os.path.join(spack.lib_path, "spack", "cmd")
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
for file in os.listdir(command_path):
|
||||||
|
if file.endswith(".py") and not file == "__init__.py":
|
||||||
|
cmd = re.sub(r'.py$', '', file)
|
||||||
|
commands.append(cmd)
|
||||||
|
commands.sort()
|
||||||
|
|
||||||
|
def null_op(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_module(name):
|
||||||
|
"""Imports the module for a particular command name and returns it."""
|
||||||
|
module_name = "%s.%s" % (__name__, name)
|
||||||
|
module = __import__(module_name, fromlist=[name, SETUP_PARSER], level=0)
|
||||||
|
module.setup_parser = getattr(module, SETUP_PARSER, null_op)
|
||||||
|
|
||||||
|
if not hasattr(module, name):
|
||||||
|
tty.die("Command module %s (%s) must define function '%s'."
|
||||||
|
% (module.__name__, module.__file__, name))
|
||||||
|
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def get_command(name):
|
||||||
|
"""Imports the command's function from a module and returns it."""
|
||||||
|
return getattr(get_module(name), name)
|
12
lib/spack/spack/cmd/arch.py
Normal file
12
lib/spack/spack/cmd/arch.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from spack import *
|
||||||
|
import spack.version as version
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import platform
|
||||||
|
|
||||||
|
def arch(args):
|
||||||
|
print multiprocessing.cpu_count()
|
||||||
|
print platform.mac_ver()
|
||||||
|
|
||||||
|
|
||||||
|
print version.canonical(platform.mac_ver()[0])
|
16
lib/spack/spack/cmd/clean.py
Normal file
16
lib/spack/spack/cmd/clean.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import spack.packages as packages
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('name', help="name of package to clean")
|
||||||
|
subparser.add_argument('-a', "--all", action="store_true", dest="all",
|
||||||
|
help="delete the entire stage directory")
|
||||||
|
|
||||||
|
def clean(args):
|
||||||
|
package_class = packages.get(args.name)
|
||||||
|
package = package_class()
|
||||||
|
if args.all:
|
||||||
|
package.do_clean_all()
|
||||||
|
else:
|
||||||
|
package.do_clean()
|
||||||
|
|
||||||
|
|
57
lib/spack/spack/cmd/create.py
Normal file
57
lib/spack/spack/cmd/create.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import string
|
||||||
|
|
||||||
|
import spack
|
||||||
|
import spack.packages as packages
|
||||||
|
import spack.tty as tty
|
||||||
|
import spack.version
|
||||||
|
|
||||||
|
pacakge_tempate = string.Template("""\
|
||||||
|
from spack import *
|
||||||
|
|
||||||
|
class $name(Package):
|
||||||
|
homepage = "${homepage}"
|
||||||
|
url = "${url}"
|
||||||
|
md5 = "${md5}"
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
# Insert your installation code here.
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
|
||||||
|
def create_template(name):
|
||||||
|
class_name = name.capitalize()
|
||||||
|
return new_pacakge_tempate % class_name
|
||||||
|
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('url', nargs='?', help="url of package archive")
|
||||||
|
|
||||||
|
|
||||||
|
def create(args):
|
||||||
|
url = args.url
|
||||||
|
|
||||||
|
version = spack.version.parse(url)
|
||||||
|
if not version:
|
||||||
|
tty.die("Couldn't figure out a version string from '%s'." % url)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# By default open the directory where packages live.
|
||||||
|
if not name:
|
||||||
|
path = spack.packages_path
|
||||||
|
else:
|
||||||
|
path = packages.filename_for(name)
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
tty.die("Something's wrong. '%s' is not a file!" % path)
|
||||||
|
if not os.access(path, os.R_OK|os.W_OK):
|
||||||
|
tty.die("Insufficient permissions on '%s'!" % path)
|
||||||
|
else:
|
||||||
|
tty.msg("Editing new file: '%s'." % path)
|
||||||
|
file = open(path, "w")
|
||||||
|
file.write(create_template(name))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
# If everything checks out, go ahead and edit.
|
||||||
|
spack.editor(path)
|
50
lib/spack/spack/cmd/edit.py
Normal file
50
lib/spack/spack/cmd/edit.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import os
|
||||||
|
import spack
|
||||||
|
import spack.packages as packages
|
||||||
|
import spack.tty as tty
|
||||||
|
|
||||||
|
new_pacakge_tempate = """\
|
||||||
|
from spack import *
|
||||||
|
|
||||||
|
class %s(Package):
|
||||||
|
homepage = "https://www.example.com"
|
||||||
|
url = "https://www.example.com/download/example-1.0.tar.gz"
|
||||||
|
md5 = "nomd5"
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
# Insert your installation code here.
|
||||||
|
pass
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_template(name):
|
||||||
|
class_name = name.capitalize()
|
||||||
|
return new_pacakge_tempate % class_name
|
||||||
|
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument(
|
||||||
|
'name', nargs='?', default=None, help="name of package to edit")
|
||||||
|
|
||||||
|
def edit(args):
|
||||||
|
name = args.name
|
||||||
|
|
||||||
|
# By default open the directory where packages live.
|
||||||
|
if not name:
|
||||||
|
path = spack.packages_path
|
||||||
|
else:
|
||||||
|
path = packages.filename_for(name)
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
tty.die("Something's wrong. '%s' is not a file!" % path)
|
||||||
|
if not os.access(path, os.R_OK|os.W_OK):
|
||||||
|
tty.die("Insufficient permissions on '%s'!" % path)
|
||||||
|
else:
|
||||||
|
tty.msg("Editing new file: '%s'." % path)
|
||||||
|
file = open(path, "w")
|
||||||
|
file.write(create_template(name))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
# If everything checks out, go ahead and edit.
|
||||||
|
spack.editor(path)
|
9
lib/spack/spack/cmd/fetch.py
Normal file
9
lib/spack/spack/cmd/fetch.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import spack.packages as packages
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('name', help="name of package to fetch")
|
||||||
|
|
||||||
|
def fetch(args):
|
||||||
|
package_class = packages.get(args.name)
|
||||||
|
package = package_class()
|
||||||
|
package.do_fetch()
|
11
lib/spack/spack/cmd/install.py
Normal file
11
lib/spack/spack/cmd/install.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import spack.packages as packages
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('name', help="name of package to install")
|
||||||
|
|
||||||
|
def install(args):
|
||||||
|
package_class = packages.get(args.name)
|
||||||
|
package = package_class()
|
||||||
|
package.do_install()
|
||||||
|
|
||||||
|
|
9
lib/spack/spack/cmd/stage.py
Normal file
9
lib/spack/spack/cmd/stage.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import spack.packages as packages
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('name', help="name of package to stage")
|
||||||
|
|
||||||
|
def stage(args):
|
||||||
|
package_class = packages.get(args.name)
|
||||||
|
package = package_class()
|
||||||
|
package.do_stage()
|
9
lib/spack/spack/cmd/uninstall.py
Normal file
9
lib/spack/spack/cmd/uninstall.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import spack.packages as packages
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('name', help="name of package to uninstall")
|
||||||
|
|
||||||
|
def uninstall(args):
|
||||||
|
package_class = packages.get(args.name)
|
||||||
|
package = package_class()
|
||||||
|
package.do_uninstall()
|
112
lib/spack/spack/fileutils.py
Normal file
112
lib/spack/spack/fileutils.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from itertools import product
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
import tty
|
||||||
|
|
||||||
|
# Supported archvie extensions.
|
||||||
|
PRE_EXTS = ["tar"]
|
||||||
|
EXTS = ["gz", "bz2", "xz", "Z", "zip", "tgz"]
|
||||||
|
|
||||||
|
# Add EXTS last so that .tar.gz is matched *before* tar.gz
|
||||||
|
ALLOWED_ARCHIVE_TYPES = [".".join(l) for l in product(PRE_EXTS, EXTS)] + EXTS
|
||||||
|
|
||||||
|
|
||||||
|
def has_whitespace(string):
|
||||||
|
return re.search(r'\s', string)
|
||||||
|
|
||||||
|
|
||||||
|
def new_path(prefix, *args):
|
||||||
|
path=prefix
|
||||||
|
for elt in args:
|
||||||
|
path = os.path.join(path, elt)
|
||||||
|
|
||||||
|
if has_whitespace(path):
|
||||||
|
tty.die("Invalid path: '%s'. Use a path without whitespace.")
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def ancestor(dir, n=1):
|
||||||
|
"""Get the nth ancestor of a directory."""
|
||||||
|
parent = os.path.abspath(dir)
|
||||||
|
for i in range(n):
|
||||||
|
parent = os.path.dirname(parent)
|
||||||
|
return parent
|
||||||
|
|
||||||
|
|
||||||
|
class Executable(object):
|
||||||
|
"""Class representing a program that can be run on the command line."""
|
||||||
|
def __init__(self, name):
|
||||||
|
self.exe = name.split(' ')
|
||||||
|
|
||||||
|
def add_default_arg(self, arg):
|
||||||
|
self.exe.append(arg)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
"""Run the executable with subprocess.check_output, return output."""
|
||||||
|
return_output = kwargs.get("return_output", False)
|
||||||
|
|
||||||
|
quoted_args = [arg for arg in args if re.search(r'^"|^\'|"$|\'$', arg)]
|
||||||
|
if quoted_args:
|
||||||
|
tty.warn("Quotes in package command arguments can confuse shell scripts like configure.",
|
||||||
|
"The following arguments may cause problems when executed:",
|
||||||
|
str("\n".join([" "+arg for arg in quoted_args])),
|
||||||
|
"Quotes aren't needed because spack doesn't use a shell. Consider removing them")
|
||||||
|
|
||||||
|
cmd = self.exe + list(args)
|
||||||
|
tty.verbose(cmd)
|
||||||
|
|
||||||
|
if return_output:
|
||||||
|
return subprocess.check_output(cmd)
|
||||||
|
else:
|
||||||
|
return subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<exe: %s>" % self.exe
|
||||||
|
|
||||||
|
|
||||||
|
def which(name, path=None):
|
||||||
|
"""Finds an executable in the path like command-line which."""
|
||||||
|
if not path:
|
||||||
|
path = os.environ.get('PATH', '').split(os.pathsep)
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for dir in path:
|
||||||
|
exe = os.path.join(dir, name)
|
||||||
|
if os.access(exe, os.X_OK):
|
||||||
|
return Executable(exe)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def stem(path):
|
||||||
|
"""Get the part of a path that does not include its compressed
|
||||||
|
type extension."""
|
||||||
|
for type in ALLOWED_ARCHIVE_TYPES:
|
||||||
|
suffix = r'\.%s$' % type
|
||||||
|
if re.search(suffix, path):
|
||||||
|
return re.sub(suffix, "", path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def decompressor_for(path):
|
||||||
|
"""Get the appropriate decompressor for a path."""
|
||||||
|
if which("tar"):
|
||||||
|
return Executable("tar -xf")
|
||||||
|
else:
|
||||||
|
tty.die("spack requires tar. Make sure it's on your path.")
|
||||||
|
|
||||||
|
|
||||||
|
def md5(filename, block_size=2**20):
|
||||||
|
import hashlib
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
with closing(open(filename)) as file:
|
||||||
|
while True:
|
||||||
|
data = file.read(block_size)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
md5.update(data)
|
||||||
|
return md5.hexdigest()
|
42
lib/spack/spack/globals.py
Normal file
42
lib/spack/spack/globals.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import multiprocessing
|
||||||
|
from version import Version
|
||||||
|
|
||||||
|
import tty
|
||||||
|
from fileutils import *
|
||||||
|
|
||||||
|
# This lives in $prefix/lib/spac/spack/__file__
|
||||||
|
prefix = ancestor(__file__, 4)
|
||||||
|
|
||||||
|
# The spack script itself
|
||||||
|
spack_file = new_path(prefix, "bin", "spack")
|
||||||
|
|
||||||
|
# spack directory hierarchy
|
||||||
|
lib_path = new_path(prefix, "lib", "spack")
|
||||||
|
module_path = new_path(lib_path, "spack")
|
||||||
|
packages_path = new_path(module_path, "packages")
|
||||||
|
|
||||||
|
var_path = new_path(prefix, "var", "spack")
|
||||||
|
stage_path = new_path(var_path, "stage")
|
||||||
|
|
||||||
|
install_path = new_path(prefix, "opt")
|
||||||
|
|
||||||
|
# Version information
|
||||||
|
version = Version("0.1")
|
||||||
|
|
||||||
|
# User's editor from the environment
|
||||||
|
editor = Executable(os.environ.get("EDITOR", ""))
|
||||||
|
|
||||||
|
# Curl tool for fetching files.
|
||||||
|
curl = which("curl")
|
||||||
|
if not curl:
|
||||||
|
tty.die("spack requires curl. Make sure it is in your path.")
|
||||||
|
|
||||||
|
make = which("make")
|
||||||
|
make.add_default_arg("-j%d" % multiprocessing.cpu_count())
|
||||||
|
if not make:
|
||||||
|
tty.die("spack requires make. Make sure it is in your path.")
|
||||||
|
|
||||||
|
verbose = False
|
||||||
|
debug = False
|
35
lib/spack/spack/packages/__init__.py
Normal file
35
lib/spack/spack/packages/__init__.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import spack
|
||||||
|
from spack.fileutils import *
|
||||||
|
|
||||||
|
import re
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
|
def filename_for(package):
|
||||||
|
"""Get the filename where a package name should be stored."""
|
||||||
|
return new_path(spack.packages_path, "%s.py" % package.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def get(name):
|
||||||
|
file = filename_for(name)
|
||||||
|
|
||||||
|
if os.path.exists(file):
|
||||||
|
if not os.path.isfile(file):
|
||||||
|
tty.die("Something's wrong. '%s' is not a file!" % file)
|
||||||
|
if not os.access(file, os.R_OK):
|
||||||
|
tty.die("Cannot read '%s'!" % file)
|
||||||
|
|
||||||
|
class_name = name.capitalize()
|
||||||
|
try:
|
||||||
|
module_name = "%s.%s" % (__name__, name)
|
||||||
|
module = __import__(module_name, fromlist=[class_name])
|
||||||
|
except ImportError, e:
|
||||||
|
tty.die("Error while importing %s.%s:\n%s" % (name, class_name, e.message))
|
||||||
|
|
||||||
|
klass = getattr(module, class_name)
|
||||||
|
if not inspect.isclass(klass):
|
||||||
|
tty.die("%s.%s is not a class" % (name, class_name))
|
||||||
|
|
||||||
|
return klass
|
||||||
|
|
||||||
|
|
11
lib/spack/spack/packages/cmake.py
Normal file
11
lib/spack/spack/packages/cmake.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from spack import *
|
||||||
|
|
||||||
|
class Cmake(Package):
|
||||||
|
homepage = 'https://www.cmake.org'
|
||||||
|
url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz'
|
||||||
|
md5 = '097278785da7182ec0aea8769d06860c'
|
||||||
|
|
||||||
|
def install(self, prefix):
|
||||||
|
configure('--prefix=%s' % prefix)
|
||||||
|
make()
|
||||||
|
make('install')
|
68
lib/spack/spack/stage.py
Normal file
68
lib/spack/spack/stage.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import spack
|
||||||
|
import tty
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_access(dir=spack.stage_path):
|
||||||
|
if not os.access(dir, os.R_OK|os.W_OK):
|
||||||
|
tty.die("Insufficient permissions on directory '%s'" % dir)
|
||||||
|
|
||||||
|
|
||||||
|
class Stage(object):
|
||||||
|
def __init__(self, stage_name):
|
||||||
|
self.stage_name = stage_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return spack.new_path(spack.stage_path, self.stage_name)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
if os.path.exists(self.path):
|
||||||
|
if not os.path.isdir(self.path):
|
||||||
|
tty.die("Stage path '%s' is not a directory!" % self.path)
|
||||||
|
else:
|
||||||
|
os.makedirs(self.path)
|
||||||
|
|
||||||
|
ensure_access(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def archive_path(self):
|
||||||
|
""""Returns the path to the expanded archive directory if it's expanded;
|
||||||
|
None if the archive hasn't been expanded.
|
||||||
|
"""
|
||||||
|
for file in os.listdir(self.path):
|
||||||
|
archive_path = spack.new_path(self.path, file)
|
||||||
|
if os.path.isdir(archive_path):
|
||||||
|
return archive_path
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def chdir(self):
|
||||||
|
"""Changes directory to the stage path. Or dies if it is not set up."""
|
||||||
|
if os.path.isdir(self.path):
|
||||||
|
os.chdir(self.path)
|
||||||
|
else:
|
||||||
|
tty.die("Attempt to chdir to stage before setup.")
|
||||||
|
|
||||||
|
|
||||||
|
def chdir_to_archive(self):
|
||||||
|
"""Changes directory to the expanded archive directory if it exists.
|
||||||
|
Dies with an error otherwise.
|
||||||
|
"""
|
||||||
|
path = self.archive_path
|
||||||
|
if not path:
|
||||||
|
tty.die("Attempt to chdir before expanding archive.")
|
||||||
|
else:
|
||||||
|
os.chdir(path)
|
||||||
|
if not os.listdir(path):
|
||||||
|
tty.die("Archive was empty for '%s'" % self.name)
|
||||||
|
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
"""Blows away the stage directory. Can always call setup() again."""
|
||||||
|
if os.path.exists(self.path):
|
||||||
|
shutil.rmtree(self.path, True)
|
189
lib/spack/spack/test/test_versions.py
Executable file
189
lib/spack/spack/test/test_versions.py
Executable file
|
@ -0,0 +1,189 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""\
|
||||||
|
This file has a bunch of versions tests taken from the excellent version
|
||||||
|
detection in Homebrew.
|
||||||
|
"""
|
||||||
|
import spack.version as version
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class VersionTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def assert_not_detected(self, string):
|
||||||
|
self.assertIsNone(version.parse(string))
|
||||||
|
|
||||||
|
def assert_detected(self, v, string):
|
||||||
|
self.assertEqual(v, version.parse(string))
|
||||||
|
|
||||||
|
def test_wwwoffle_version(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'2.9h', 'http://www.gedanken.demon.co.uk/download-wwwoffle/wwwoffle-2.9h.tgz')
|
||||||
|
|
||||||
|
def test_version_sourceforge_download(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.21', 'http://sourceforge.net/foo_bar-1.21.tar.gz/download')
|
||||||
|
self.assert_detected(
|
||||||
|
'1.21', 'http://sf.net/foo_bar-1.21.tar.gz/download')
|
||||||
|
|
||||||
|
def test_no_version(self):
|
||||||
|
self.assert_not_detected('http://example.com/blah.tar')
|
||||||
|
self.assert_not_detected('foo')
|
||||||
|
|
||||||
|
def test_version_all_dots(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.14','http://example.com/foo.bar.la.1.14.zip')
|
||||||
|
|
||||||
|
def test_version_underscore_separator(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.1', 'http://example.com/grc_1.1.tar.gz')
|
||||||
|
|
||||||
|
def test_boost_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.39.0', 'http://example.com/boost_1_39_0.tar.bz2')
|
||||||
|
|
||||||
|
def test_erlang_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'R13B', 'http://erlang.org/download/otp_src_R13B.tar.gz')
|
||||||
|
|
||||||
|
def test_another_erlang_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'R15B01', 'https://github.com/erlang/otp/tarball/OTP_R15B01')
|
||||||
|
|
||||||
|
def test_yet_another_erlang_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'R15B03-1', 'https://github.com/erlang/otp/tarball/OTP_R15B03-1')
|
||||||
|
|
||||||
|
def test_p7zip_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'9.04',
|
||||||
|
'http://kent.dl.sourceforge.net/sourceforge/p7zip/p7zip_9.04_src_all.tar.bz2')
|
||||||
|
|
||||||
|
def test_new_github_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.1.4', 'https://github.com/sam-github/libnet/tarball/libnet-1.1.4')
|
||||||
|
|
||||||
|
def test_gloox_beta_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.0-beta7', 'http://camaya.net/download/gloox-1.0-beta7.tar.bz2')
|
||||||
|
|
||||||
|
def test_sphinx_beta_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.10-beta', 'http://sphinxsearch.com/downloads/sphinx-1.10-beta.tar.gz')
|
||||||
|
|
||||||
|
def test_astyle_verson_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.23', 'http://kent.dl.sourceforge.net/sourceforge/astyle/astyle_1.23_macosx.tar.gz')
|
||||||
|
|
||||||
|
def test_version_dos2unix(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'3.1', 'http://www.sfr-fresh.com/linux/misc/dos2unix-3.1.tar.gz')
|
||||||
|
|
||||||
|
def test_version_internal_dash(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.1-2', 'http://example.com/foo-arse-1.1-2.tar.gz')
|
||||||
|
|
||||||
|
def test_version_single_digit(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'45', 'http://example.com/foo_bar.45.tar.gz')
|
||||||
|
|
||||||
|
def test_noseparator_single_digit(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'45', 'http://example.com/foo_bar45.tar.gz')
|
||||||
|
|
||||||
|
def test_version_developer_that_hates_us_format(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.2.3', 'http://example.com/foo-bar-la.1.2.3.tar.gz')
|
||||||
|
|
||||||
|
def test_version_regular(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.21', 'http://example.com/foo_bar-1.21.tar.gz')
|
||||||
|
|
||||||
|
def test_version_github(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.0.5', 'http://github.com/lloyd/yajl/tarball/1.0.5')
|
||||||
|
|
||||||
|
def test_version_github_with_high_patch_number(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.2.34', 'http://github.com/lloyd/yajl/tarball/v1.2.34')
|
||||||
|
|
||||||
|
def test_yet_another_version(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'0.15.1b', 'http://example.com/mad-0.15.1b.tar.gz')
|
||||||
|
|
||||||
|
def test_lame_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'398-2', 'http://kent.dl.sourceforge.net/sourceforge/lame/lame-398-2.tar.gz')
|
||||||
|
|
||||||
|
def test_ruby_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.9.1-p243', 'ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p243.tar.gz')
|
||||||
|
|
||||||
|
def test_omega_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'0.80.2', 'http://www.alcyone.com/binaries/omega/omega-0.80.2-src.tar.gz')
|
||||||
|
|
||||||
|
def test_rc_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.2.2rc1', 'http://downloads.xiph.org/releases/vorbis/libvorbis-1.2.2rc1.tar.bz2')
|
||||||
|
|
||||||
|
def test_dash_rc_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.8.0-rc1', 'http://ftp.mozilla.org/pub/mozilla.org/js/js-1.8.0-rc1.tar.gz')
|
||||||
|
|
||||||
|
def test_angband_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'3.0.9b', 'http://rephial.org/downloads/3.0/angband-3.0.9b-src.tar.gz')
|
||||||
|
|
||||||
|
def test_stable_suffix(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.4.14b', 'http://www.monkey.org/~provos/libevent-1.4.14b-stable.tar.gz')
|
||||||
|
|
||||||
|
def test_debian_style_1(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'3.03', 'http://ftp.de.debian.org/debian/pool/main/s/sl/sl_3.03.orig.tar.gz')
|
||||||
|
|
||||||
|
def test_debian_style_2(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.01b', 'http://ftp.de.debian.org/debian/pool/main/m/mmv/mmv_1.01b.orig.tar.gz')
|
||||||
|
|
||||||
|
def test_imagemagick_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'6.7.5-7', 'http://downloads.sf.net/project/machomebrew/mirror/ImageMagick-6.7.5-7.tar.bz2')
|
||||||
|
|
||||||
|
def test_dash_version_dash_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'3.4', 'http://www.antlr.org/download/antlr-3.4-complete.jar')
|
||||||
|
|
||||||
|
def test_apache_version_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.2.0-rc2', 'http://www.apache.org/dyn/closer.cgi?path=/cassandra/1.2.0/apache-cassandra-1.2.0-rc2-bin.tar.gz')
|
||||||
|
|
||||||
|
def test_jpeg_style(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'8d', 'http://www.ijg.org/files/jpegsrc.v8d.tar.gz')
|
||||||
|
|
||||||
|
def test_more_versions(self):
|
||||||
|
self.assert_detected(
|
||||||
|
'1.4.1', 'http://pypy.org/download/pypy-1.4.1-osx.tar.bz2')
|
||||||
|
self.assert_detected(
|
||||||
|
'0.9.8s', 'http://www.openssl.org/source/openssl-0.9.8s.tar.gz')
|
||||||
|
self.assert_detected(
|
||||||
|
'1.5E', 'ftp://ftp.visi.com/users/hawkeyd/X/Xaw3d-1.5E.tar.gz')
|
||||||
|
self.assert_detected(
|
||||||
|
'2.1.0beta', 'http://downloads.sourceforge.net/project/fann/fann/2.1.0beta/fann-2.1.0beta.zip')
|
||||||
|
self.assert_detected(
|
||||||
|
'2.0.1', 'ftp://iges.org/grads/2.0/grads-2.0.1-bin-darwin9.8-intel.tar.gz')
|
||||||
|
self.assert_detected(
|
||||||
|
'2.08', 'http://haxe.org/file/haxe-2.08-osx.tar.gz')
|
||||||
|
self.assert_detected(
|
||||||
|
'2007f', 'ftp://ftp.cac.washington.edu/imap/imap-2007f.tar.gz')
|
||||||
|
self.assert_detected(
|
||||||
|
'3.3.12ga7', 'http://sourceforge.net/projects/x3270/files/x3270/3.3.12ga7/suite3270-3.3.12ga7-src.tgz')
|
||||||
|
self.assert_detected(
|
||||||
|
'1.3.6p2', 'http://synergy.googlecode.com/files/synergy-1.3.6p2-MacOSX-Universal.zip')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
63
lib/spack/spack/tty.py
Normal file
63
lib/spack/spack/tty.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import sys
|
||||||
|
import spack
|
||||||
|
|
||||||
|
indent = " "
|
||||||
|
|
||||||
|
def escape(s):
|
||||||
|
"""Returns a TTY escape code if stdout is a tty, otherwise empty string"""
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
return "\033[{}m".format(s)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def color(n):
|
||||||
|
return escape("0;{}".format(n))
|
||||||
|
|
||||||
|
def bold(n):
|
||||||
|
return escape("1;{}".format(n))
|
||||||
|
|
||||||
|
def underline(n):
|
||||||
|
return escape("4;{}".format(n))
|
||||||
|
|
||||||
|
blue = bold(34)
|
||||||
|
white = bold(39)
|
||||||
|
red = bold(31)
|
||||||
|
yellow = underline(33)
|
||||||
|
green = bold(92)
|
||||||
|
gray = bold(30)
|
||||||
|
em = underline(39)
|
||||||
|
reset = escape(0)
|
||||||
|
|
||||||
|
def msg(msg, *args, **kwargs):
|
||||||
|
color = kwargs.get("color", blue)
|
||||||
|
print "{}==>{} {}{}".format(color, white, str(msg), reset)
|
||||||
|
for arg in args: print indent + str(arg)
|
||||||
|
|
||||||
|
def verbose(*args):
|
||||||
|
if spack.verbose: msg(*args, color=green)
|
||||||
|
|
||||||
|
def debug(*args):
|
||||||
|
if spack.debug: msg(*args, color=red)
|
||||||
|
|
||||||
|
def error(msg, *args):
|
||||||
|
print "{}Error{}: {}".format(red, reset, str(msg))
|
||||||
|
for arg in args: print indent + str(arg)
|
||||||
|
|
||||||
|
def warn(msg, *args):
|
||||||
|
print "{}Warning{}: {}".format(yellow, reset, str(msg))
|
||||||
|
for arg in args: print indent + str(arg)
|
||||||
|
|
||||||
|
def die(msg):
|
||||||
|
error(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def pkg(msg):
|
||||||
|
"""Outputs a message with a package icon."""
|
||||||
|
import platform
|
||||||
|
from version import Version
|
||||||
|
|
||||||
|
mac_version = platform.mac_ver()[0]
|
||||||
|
if Version(mac_version) >= Version("10.7"):
|
||||||
|
print u"\U0001F4E6" + indent,
|
||||||
|
else:
|
||||||
|
print '[%] ',
|
||||||
|
print msg
|
13
lib/spack/spack/validate.py
Normal file
13
lib/spack/spack/validate.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import tty
|
||||||
|
from fileutils import ALLOWED_ARCHIVE_TYPES
|
||||||
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
ALLOWED_SCHEMES = ["http", "https", "ftp"]
|
||||||
|
|
||||||
|
def url(url_string):
|
||||||
|
url = urlparse(url_string)
|
||||||
|
if url.scheme not in ALLOWED_SCHEMES:
|
||||||
|
tty.die("Invalid protocol in URL: '%s'" % url_string)
|
||||||
|
|
||||||
|
if not any(url_string.endswith(t) for t in ALLOWED_ARCHIVE_TYPES):
|
||||||
|
tty.die("Invalid file type in URL: '%s'" % url_string)
|
126
lib/spack/spack/version.py
Normal file
126
lib/spack/spack/version.py
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import fileutils
|
||||||
|
|
||||||
|
class Version(object):
|
||||||
|
"""Class to represent versions"""
|
||||||
|
def __init__(self, version_string):
|
||||||
|
self.version_string = version_string
|
||||||
|
self.version = canonical(version_string)
|
||||||
|
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(self.version, other.version)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def major(self):
|
||||||
|
return self.component(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minor(self):
|
||||||
|
return self.component(1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patch(self):
|
||||||
|
return self.component(2)
|
||||||
|
|
||||||
|
def component(self, i):
|
||||||
|
"""Returns the ith version component"""
|
||||||
|
if len(self.version) > i:
|
||||||
|
return self.version[i]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.version_string
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.version_string
|
||||||
|
|
||||||
|
|
||||||
|
def canonical(v):
|
||||||
|
"""Get a "canonical" version of a version string, as a tuple."""
|
||||||
|
def intify(part):
|
||||||
|
try:
|
||||||
|
return int(part)
|
||||||
|
except:
|
||||||
|
return part
|
||||||
|
return tuple(intify(v) for v in re.split(r'[_.-]+', v))
|
||||||
|
|
||||||
|
|
||||||
|
def parse(spec):
|
||||||
|
"""Try to extract a version from a filename. This is taken largely from
|
||||||
|
Homebrew's Version class."""
|
||||||
|
|
||||||
|
if os.path.isdir(spec):
|
||||||
|
stem = os.path.basename(spec)
|
||||||
|
elif re.search(r'((?:sourceforge.net|sf.net)/.*)/download$', spec):
|
||||||
|
stem = fileutils.stem(os.path.dirname(spec))
|
||||||
|
else:
|
||||||
|
stem = fileutils.stem(spec)
|
||||||
|
|
||||||
|
version_types = [
|
||||||
|
# GitHub tarballs, e.g. v1.2.3
|
||||||
|
(r'github.com/.+/(?:zip|tar)ball/v?((\d+\.)+\d+)$', spec),
|
||||||
|
|
||||||
|
# e.g. https://github.com/sam-github/libnet/tarball/libnet-1.1.4
|
||||||
|
(r'github.com/.+/(?:zip|tar)ball/.*-((\d+\.)+\d+)$', spec),
|
||||||
|
|
||||||
|
# e.g. https://github.com/isaacs/npm/tarball/v0.2.5-1
|
||||||
|
(r'github.com/.+/(?:zip|tar)ball/v?((\d+\.)+\d+-(\d+))$', spec),
|
||||||
|
|
||||||
|
# e.g. https://github.com/petdance/ack/tarball/1.93_02
|
||||||
|
(r'github.com/.+/(?:zip|tar)ball/v?((\d+\.)+\d+_(\d+))$', spec),
|
||||||
|
|
||||||
|
# e.g. https://github.com/erlang/otp/tarball/OTP_R15B01 (erlang style)
|
||||||
|
(r'[-_](R\d+[AB]\d*(-\d+)?)', spec),
|
||||||
|
|
||||||
|
# e.g. boost_1_39_0
|
||||||
|
(r'((\d+_)+\d+)$', stem, lambda s: s.replace('_', '.')),
|
||||||
|
|
||||||
|
# e.g. foobar-4.5.1-1
|
||||||
|
# e.g. ruby-1.9.1-p243
|
||||||
|
(r'-((\d+\.)*\d\.\d+-(p|rc|RC)?\d+)(?:[-._](?:bin|dist|stable|src|sources))?$', stem),
|
||||||
|
|
||||||
|
# e.g. lame-398-1
|
||||||
|
(r'-((\d)+-\d)', stem),
|
||||||
|
|
||||||
|
# e.g. foobar-4.5.1
|
||||||
|
(r'-((\d+\.)*\d+)$', stem),
|
||||||
|
|
||||||
|
# e.g. foobar-4.5.1b
|
||||||
|
(r'-((\d+\.)*\d+([a-z]|rc|RC)\d*)$', stem),
|
||||||
|
|
||||||
|
# e.g. foobar-4.5.0-beta1, or foobar-4.50-beta
|
||||||
|
(r'-((\d+\.)*\d+-beta(\d+)?)$', stem),
|
||||||
|
|
||||||
|
# e.g. foobar4.5.1
|
||||||
|
(r'((\d+\.)*\d+)$', stem),
|
||||||
|
|
||||||
|
# e.g. foobar-4.5.0-bin
|
||||||
|
(r'-((\d+\.)+\d+[a-z]?)[-._](bin|dist|stable|src|sources?)$', stem),
|
||||||
|
|
||||||
|
# e.g. dash_0.5.5.1.orig.tar.gz (Debian style)
|
||||||
|
(r'_((\d+\.)+\d+[a-z]?)[.]orig$', stem),
|
||||||
|
|
||||||
|
# e.g. http://www.openssl.org/source/openssl-0.9.8s.tar.gz
|
||||||
|
(r'-([^-]+)', stem),
|
||||||
|
|
||||||
|
# e.g. astyle_1.23_macosx.tar.gz
|
||||||
|
(r'_([^_]+)', stem),
|
||||||
|
|
||||||
|
# e.g. http://mirrors.jenkins-ci.org/war/1.486/jenkins.war
|
||||||
|
(r'\/(\d\.\d+)\/', spec),
|
||||||
|
|
||||||
|
# e.g. http://www.ijg.org/files/jpegsrc.v8d.tar.gz
|
||||||
|
(r'\.v(\d+[a-z]?)', stem)]
|
||||||
|
|
||||||
|
for type in version_types:
|
||||||
|
regex, match_string = type[:2]
|
||||||
|
match = re.search(regex, match_string)
|
||||||
|
if match and match.group(1) is not None:
|
||||||
|
if type[2:]:
|
||||||
|
return Version(type[2](match.group(1)))
|
||||||
|
else:
|
||||||
|
return Version(match.group(1))
|
||||||
|
return None
|
Loading…
Reference in a new issue