Initial version of spack with one package: cmake

This commit is contained in:
Todd Gamblin 2013-02-13 17:50:44 -08:00
commit cc76c0f5f9
23 changed files with 1094 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.pyc
/opt/
/var/
*~

41
bin/spack Executable file
View 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
View 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)

View 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
View 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__))

View 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)

View 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])

View 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()

View 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)

View 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)

View 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()

View 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()

View 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()

View 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()

View 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()

View 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

View 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

View 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
View 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)

View 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
View 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

View 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
View 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