Commands take specs as input instead of names.
modified clean, create, fetch, install, and uninstall
This commit is contained in:
		| @@ -1,7 +1,9 @@ | |||||||
| import os | import os | ||||||
| import re | import re | ||||||
|  | import sys | ||||||
|  |  | ||||||
| import spack | import spack | ||||||
|  | import spack.spec | ||||||
| import spack.tty as tty | import spack.tty as tty | ||||||
| import spack.attr as attr | import spack.attr as attr | ||||||
|  |  | ||||||
| @@ -21,10 +23,6 @@ | |||||||
| commands.sort() | commands.sort() | ||||||
|  |  | ||||||
|  |  | ||||||
| def null_op(*args): |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_cmd_function_name(name): | def get_cmd_function_name(name): | ||||||
|     return name.replace("-", "_") |     return name.replace("-", "_") | ||||||
|  |  | ||||||
| @@ -36,7 +34,7 @@ def get_module(name): | |||||||
|         module_name, fromlist=[name, SETUP_PARSER, DESCRIPTION], |         module_name, fromlist=[name, SETUP_PARSER, DESCRIPTION], | ||||||
|         level=0) |         level=0) | ||||||
|  |  | ||||||
|     attr.setdefault(module, SETUP_PARSER, null_op) |     attr.setdefault(module, SETUP_PARSER, lambda *args: None) # null-op | ||||||
|     attr.setdefault(module, DESCRIPTION, "") |     attr.setdefault(module, DESCRIPTION, "") | ||||||
|  |  | ||||||
|     fn_name = get_cmd_function_name(name) |     fn_name = get_cmd_function_name(name) | ||||||
| @@ -50,3 +48,22 @@ def get_module(name): | |||||||
| def get_command(name): | def get_command(name): | ||||||
|     """Imports the command's function from a module and returns it.""" |     """Imports the command's function from a module and returns it.""" | ||||||
|     return getattr(get_module(name), get_cmd_function_name(name)) |     return getattr(get_module(name), get_cmd_function_name(name)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def parse_specs(args): | ||||||
|  |     """Convenience function for parsing arguments from specs.  Handles common | ||||||
|  |        exceptions and dies if there are errors. | ||||||
|  |     """ | ||||||
|  |     if type(args) == list: | ||||||
|  |         args = " ".join(args) | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         return spack.spec.parse(" ".join(args)) | ||||||
|  |  | ||||||
|  |     except spack.parse.ParseError, e: | ||||||
|  |         e.print_error(sys.stdout) | ||||||
|  |         sys.exit(1) | ||||||
|  |  | ||||||
|  |     except spack.spec.SpecError, e: | ||||||
|  |         tty.error(e.message) | ||||||
|  |         sys.exit(1) | ||||||
|   | |||||||
| @@ -1,3 +1,6 @@ | |||||||
|  | import argparse | ||||||
|  |  | ||||||
|  | import spack.cmd | ||||||
| import spack.packages as packages | import spack.packages as packages | ||||||
| import spack.tty as tty | import spack.tty as tty | ||||||
| import spack.stage as stage | import spack.stage as stage | ||||||
| @@ -5,21 +8,22 @@ | |||||||
| description = "Remove staged files for packages" | description = "Remove staged files for packages" | ||||||
|  |  | ||||||
| def setup_parser(subparser): | def setup_parser(subparser): | ||||||
|     subparser.add_argument('names', nargs='+', help="name(s) of package(s) to clean") |  | ||||||
|     subparser.add_argument('-c', "--clean", action="store_true", dest='clean', |     subparser.add_argument('-c', "--clean", action="store_true", dest='clean', | ||||||
|                            help="run make clean in the stage directory (default)") |                            help="run make clean in the stage directory (default)") | ||||||
|     subparser.add_argument('-w', "--work", action="store_true", dest='work', |     subparser.add_argument('-w', "--work", action="store_true", dest='work', | ||||||
|                            help="delete and re-expand the entire stage directory") |                            help="delete and re-expand the entire stage directory") | ||||||
|     subparser.add_argument('-d', "--dist", action="store_true", dest='dist', |     subparser.add_argument('-d', "--dist", action="store_true", dest='dist', | ||||||
|                            help="delete the downloaded archive.") |                            help="delete the downloaded archive.") | ||||||
|  |     subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to clean") | ||||||
|  |  | ||||||
|  |  | ||||||
| def clean(parser, args): | def clean(parser, args): | ||||||
|     if not args.names: |     if not args.packages: | ||||||
|         tty.die("spack clean requires at least one package name.") |         tty.die("spack clean requires at least one package argument") | ||||||
|  |  | ||||||
|     for name in args.names: |     specs = spack.cmd.parse_specs(args.packages) | ||||||
|         package = packages.get(name) |     for spec in specs: | ||||||
|  |         package = packages.get(spec.name) | ||||||
|         if args.dist: |         if args.dist: | ||||||
|             package.do_clean_dist() |             package.do_clean_dist() | ||||||
|         elif args.work: |         elif args.work: | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| import spack | import spack | ||||||
| import spack.packages as packages | import spack.packages as packages | ||||||
| import spack.tty as tty | import spack.tty as tty | ||||||
| import spack.version | import spack.url | ||||||
|  |  | ||||||
| from spack.stage import Stage | from spack.stage import Stage | ||||||
| from contextlib import closing | from contextlib import closing | ||||||
| @@ -38,7 +38,7 @@ def create(parser, args): | |||||||
|     url = args.url |     url = args.url | ||||||
|  |  | ||||||
|     # 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 | ||||||
|     name, version = spack.version.parse(url) |     name, version = spack.url.parse_name_and_version(url) | ||||||
|     if not name: |     if not name: | ||||||
|         print "Couldn't guess a name for this package." |         print "Couldn't guess a name for this package." | ||||||
|         while not name: |         while not name: | ||||||
|   | |||||||
| @@ -1,12 +1,18 @@ | |||||||
|  | import argparse | ||||||
|  | import spack.cmd | ||||||
| import spack.packages as packages | import spack.packages as packages | ||||||
|  |  | ||||||
| description = "Fetch archives for packages" | description = "Fetch archives for packages" | ||||||
|  |  | ||||||
| def setup_parser(subparser): | def setup_parser(subparser): | ||||||
|     subparser.add_argument('names', nargs='+', help="names of packages to fetch") |     subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to fetch") | ||||||
|  |  | ||||||
|  |  | ||||||
| def fetch(parser, args): | def fetch(parser, args): | ||||||
|     for name in args.names: |     if not args.packages: | ||||||
|         package = packages.get(name) |         tty.die("fetch requires at least one package argument") | ||||||
|  |  | ||||||
|  |     specs = spack.cmd.parse_specs(args.packages) | ||||||
|  |     for spec in specs: | ||||||
|  |         package = packages.get(spec.name) | ||||||
|         package.do_fetch() |         package.do_fetch() | ||||||
|   | |||||||
| @@ -1,19 +1,28 @@ | |||||||
|  | import sys | ||||||
|  | import argparse | ||||||
|  |  | ||||||
| import spack | import spack | ||||||
| import spack.packages as packages | import spack.packages as packages | ||||||
|  | import spack.cmd | ||||||
|  |  | ||||||
| description = "Build and install packages" | description = "Build and install packages" | ||||||
|  |  | ||||||
| def setup_parser(subparser): | def setup_parser(subparser): | ||||||
|     subparser.add_argument('names', nargs='+', help="names of packages to install") |  | ||||||
|     subparser.add_argument('-i', '--ignore-dependencies', |     subparser.add_argument('-i', '--ignore-dependencies', | ||||||
|                            action='store_true', dest='ignore_dependencies', |                            action='store_true', dest='ignore_dependencies', | ||||||
|                            help="Do not try to install dependencies of requested packages.") |                            help="Do not try to install dependencies of requested packages.") | ||||||
|     subparser.add_argument('-d', '--dirty', action='store_true', dest='dirty', |     subparser.add_argument('-d', '--dirty', action='store_true', dest='dirty', | ||||||
|                            help="Don't clean up partially completed build/installation on error.") |                            help="Don't clean up partially completed build/installation on error.") | ||||||
|  |     subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to install") | ||||||
|  |  | ||||||
|  |  | ||||||
| def install(parser, args): | def install(parser, args): | ||||||
|  |     if not args.packages: | ||||||
|  |         tty.die("install requires at least one package argument") | ||||||
|  |  | ||||||
|     spack.ignore_dependencies = args.ignore_dependencies |     spack.ignore_dependencies = args.ignore_dependencies | ||||||
|     for name in args.names: |     specs = spack.cmd.parse_specs(args.packages) | ||||||
|         package = packages.get(name) |     for spec in specs: | ||||||
|  |         package = packages.get(spec.name) | ||||||
|         package.dirty = args.dirty |         package.dirty = args.dirty | ||||||
|         package.do_install() |         package.do_install() | ||||||
|   | |||||||
| @@ -1,15 +1,22 @@ | |||||||
|  | import spack.cmd | ||||||
| import spack.packages as packages | import spack.packages as packages | ||||||
|  | import argparse | ||||||
|  |  | ||||||
| description="Remove an installed package" | description="Remove an installed package" | ||||||
|  |  | ||||||
| def setup_parser(subparser): | def setup_parser(subparser): | ||||||
|     subparser.add_argument('names', nargs='+', help="name(s) of package(s) to uninstall") |  | ||||||
|     subparser.add_argument('-f', '--force', action='store_true', dest='force', |     subparser.add_argument('-f', '--force', action='store_true', dest='force', | ||||||
|                            help="Ignore installed packages that depend on this one and remove it anyway.") |                            help="Ignore installed packages that depend on this one and remove it anyway.") | ||||||
|  |     subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall") | ||||||
|  |  | ||||||
| def uninstall(parser, args): | def uninstall(parser, args): | ||||||
|  |     if not args.packages: | ||||||
|  |         tty.die("uninstall requires at least one package argument.") | ||||||
|  |  | ||||||
|  |     specs = spack.cmd.parse_specs(args.packages) | ||||||
|  |  | ||||||
|     # get packages to uninstall as a list. |     # get packages to uninstall as a list. | ||||||
|     pkgs = [packages.get(name) for name in args.names] |     pkgs = [packages.get(spec.name) for spec in specs] | ||||||
|  |  | ||||||
|     # Sort packages to be uninstalled by the number of installed dependents |     # Sort packages to be uninstalled by the number of installed dependents | ||||||
|     # This ensures we do things in the right order |     # This ensures we do things in the right order | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| class Dependency(object): | class Dependency(object): | ||||||
|     """Represents a dependency from one package to another. |     """Represents a dependency from one package to another. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, name, version): |     def __init__(self, name): | ||||||
|         self.name = name |         self.name = name | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ | |||||||
| import tty | import tty | ||||||
| import attr | import attr | ||||||
| import validate | import validate | ||||||
| import version | import url | ||||||
| import arch | import arch | ||||||
|  |  | ||||||
| from multi_function import platform | from multi_function import platform | ||||||
| @@ -261,7 +261,7 @@ def __init__(self, sys_type = arch.sys_type()): | |||||||
|         validate.url(self.url) |         validate.url(self.url) | ||||||
|  |  | ||||||
|         # Set up version |         # Set up version | ||||||
|         attr.setdefault(self, 'version', version.parse_version(self.url)) |         attr.setdefault(self, 'version', url.parse_version(self.url)) | ||||||
|         if not self.version: |         if not self.version: | ||||||
|             tty.die("Couldn't extract version from %s. " + |             tty.die("Couldn't extract version from %s. " + | ||||||
|                     "You must specify it explicitly for this URL." % self.url) |                     "You must specify it explicitly for this URL." % self.url) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import re | import re | ||||||
| import spack.error as err | import spack.error as err | ||||||
|  | import spack.tty as tty | ||||||
| import itertools | import itertools | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -11,9 +12,7 @@ def __init__(self, message, string, pos): | |||||||
|         self.pos = pos |         self.pos = pos | ||||||
|  |  | ||||||
|     def print_error(self, out): |     def print_error(self, out): | ||||||
|         out.write(self.message + ":\n\n") |         tty.error(self.message, self.string, self.pos * " " + "^") | ||||||
|         out.write("    " + self.string + "\n") |  | ||||||
|         out.write("    " + self.pos * " " + "^\n\n") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LexError(ParseError): | class LexError(ParseError): | ||||||
| @@ -107,7 +106,7 @@ def expect(self, id): | |||||||
|             if self.next: |             if self.next: | ||||||
|                 self.unexpected_token() |                 self.unexpected_token() | ||||||
|             else: |             else: | ||||||
|                 self.next_token_error("Unexpected end of file") |                 self.next_token_error("Unexpected end of input") | ||||||
|             sys.exit(1) |             sys.exit(1) | ||||||
|  |  | ||||||
|     def parse(self, text): |     def parse(self, text): | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ class Mpileaks(Package): | |||||||
|         spack install mpileaks ^mvapich |         spack install mpileaks ^mvapich | ||||||
|         spack install mpileaks ^mpich |         spack install mpileaks ^mpich | ||||||
| """ | """ | ||||||
|  | import sys | ||||||
| from dependency import Dependency | from dependency import Dependency | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -126,6 +126,9 @@ def expanded_archive_path(self): | |||||||
|         """Returns the path to the expanded archive directory if it's expanded; |         """Returns the path to the expanded archive directory if it's expanded; | ||||||
|            None if the archive hasn't been expanded. |            None if the archive hasn't been expanded. | ||||||
|         """ |         """ | ||||||
|  |         if not self.archive_file: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|         for file in os.listdir(self.path): |         for file in os.listdir(self.path): | ||||||
|             archive_path = spack.new_path(self.path, file) |             archive_path = spack.new_path(self.path, file) | ||||||
|             if os.path.isdir(archive_path): |             if os.path.isdir(archive_path): | ||||||
|   | |||||||
| @@ -3,18 +3,19 @@ | |||||||
| detection in Homebrew. | detection in Homebrew. | ||||||
| """ | """ | ||||||
| import unittest | import unittest | ||||||
| import spack.version as ver | import spack.url as url | ||||||
| from pprint import pprint | from pprint import pprint | ||||||
|  |  | ||||||
|  |  | ||||||
| class UrlParseTest(unittest.TestCase): | class UrlParseTest(unittest.TestCase): | ||||||
|     def assert_not_detected(self, string): |     def assert_not_detected(self, string): | ||||||
|         self.assertRaises(ver.UndetectableVersionError, ver.parse, string) |         self.assertRaises( | ||||||
|  |             url.UndetectableVersionError, url.parse_name_and_version, string) | ||||||
|  |  | ||||||
|     def assert_detected(self, name, v, string): |     def assert_detected(self, name, v, string): | ||||||
|         parsed_name, parsed_v = ver.parse(string) |         parsed_name, parsed_v = url.parse_name_and_version(string) | ||||||
|         self.assertEqual(parsed_name, name) |         self.assertEqual(parsed_name, name) | ||||||
|         self.assertEqual(parsed_v, ver.Version(v)) |         self.assertEqual(parsed_v, url.Version(v)) | ||||||
|  |  | ||||||
|     def test_wwwoffle_version(self): |     def test_wwwoffle_version(self): | ||||||
|         self.assert_detected( |         self.assert_detected( | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
| from spack.version import * | from spack.version import * | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CompareVersionsTest(unittest.TestCase): | class VersionsTest(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     def assert_ver_lt(self, a, b): |     def assert_ver_lt(self, a, b): | ||||||
|         a, b = ver(a), ver(b) |         a, b = ver(a), ver(b) | ||||||
| @@ -129,7 +129,8 @@ def test_rpm_oddities(self): | |||||||
|         self.assert_ver_lt('1.fc17',  '1g.fc17') |         self.assert_ver_lt('1.fc17',  '1g.fc17') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     # Stuff below here is not taken from RPM's tests. |     # Stuff below here is not taken from RPM's tests and is | ||||||
|  |     # unique to spack | ||||||
|     def test_version_ranges(self): |     def test_version_ranges(self): | ||||||
|         self.assert_ver_lt('1.2:1.4', '1.6') |         self.assert_ver_lt('1.2:1.4', '1.6') | ||||||
|         self.assert_ver_gt('1.6', '1.2:1.4') |         self.assert_ver_gt('1.6', '1.2:1.4') | ||||||
							
								
								
									
										166
									
								
								lib/spack/spack/url.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								lib/spack/spack/url.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | |||||||
|  | """ | ||||||
|  | This module has methods for parsing names and versions of packages from URLs. | ||||||
|  | The idea is to allow package creators to supply nothing more than the | ||||||
|  | download location of the package, and figure out version and name information | ||||||
|  | from there. | ||||||
|  |  | ||||||
|  | Example: when spack is given the following URL: | ||||||
|  |  | ||||||
|  |     ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p243.tar.gz | ||||||
|  |  | ||||||
|  | It can figure out that the package name is ruby, and that it is at version | ||||||
|  | 1.9.1-p243.  This is useful for making the creation of packages simple: a user | ||||||
|  | just supplies a URL and skeleton code is generated automatically. | ||||||
|  |  | ||||||
|  | Spack can also figure out that it can most likely download 1.8.1 at this URL: | ||||||
|  |  | ||||||
|  |     ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.8.1.tar.gz | ||||||
|  |  | ||||||
|  | This is useful if a user asks for a package at a particular version number; | ||||||
|  | spack doesn't need anyone to tell it where to get the tarball even though | ||||||
|  | it's never been told about that version before. | ||||||
|  | """ | ||||||
|  | import os | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | import spack.error | ||||||
|  | import spack.utils | ||||||
|  | from spack.version import Version | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UrlParseError(spack.error.SpackError): | ||||||
|  |     """Raised when the URL module can't parse something correctly.""" | ||||||
|  |     def __init__(self, msg, spec): | ||||||
|  |         super(UrlParseError, self).__init__(msg) | ||||||
|  |         self.spec = spec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UndetectableVersionError(UrlParseError): | ||||||
|  |     """Raised when we can't parse a version from a string.""" | ||||||
|  |     def __init__(self, spec): | ||||||
|  |         super(UndetectableVersionError, self).__init__( | ||||||
|  |             "Couldn't detect version in: " + spec, spec) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UndetectableNameError(UrlParseError): | ||||||
|  |     """Raised when we can't parse a package name from a string.""" | ||||||
|  |     def __init__(self, spec): | ||||||
|  |         super(UndetectableNameError, self).__init__( | ||||||
|  |             "Couldn't parse package name in: " + spec) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def parse_version_string_with_indices(spec): | ||||||
|  |     """Try to extract a version string from a filename or URL.  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 = spack.utils.stem(os.path.dirname(spec)) | ||||||
|  |     else: | ||||||
|  |         stem = spack.utils.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), | ||||||
|  |  | ||||||
|  |         # 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 vtype in version_types: | ||||||
|  |         regex, match_string = vtype[:2] | ||||||
|  |         match = re.search(regex, match_string) | ||||||
|  |         if match and match.group(1) is not None: | ||||||
|  |             return match.group(1), match.start(1), match.end(1) | ||||||
|  |  | ||||||
|  |     raise UndetectableVersionError(spec) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def parse_version(spec): | ||||||
|  |     """Given a URL or archive name, extract a version from it and return | ||||||
|  |        a version object. | ||||||
|  |     """ | ||||||
|  |     ver, start, end = parse_version_string_with_indices(spec) | ||||||
|  |     return Version(ver) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def parse_name(spec, ver=None): | ||||||
|  |     if ver is None: | ||||||
|  |         ver = parse_version(spec) | ||||||
|  |  | ||||||
|  |     ntypes = (r'/sourceforge/([^/]+)/', | ||||||
|  |               r'/([^/]+)/(tarball|zipball)/', | ||||||
|  |               r'/([^/]+)[_.-](bin|dist|stable|src|sources)[_.-]%s' % ver, | ||||||
|  |               r'/([^/]+)[_.-]v?%s' % ver, | ||||||
|  |               r'/([^/]+)%s' % ver, | ||||||
|  |               r'^([^/]+)[_.-]v?%s' % ver, | ||||||
|  |               r'^([^/]+)%s' % ver) | ||||||
|  |  | ||||||
|  |     for nt in ntypes: | ||||||
|  |         match = re.search(nt, spec) | ||||||
|  |         if match: | ||||||
|  |             return match.group(1) | ||||||
|  |     raise UndetectableNameError(spec) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def parse_name_and_version(spec): | ||||||
|  |     ver = parse_version(spec) | ||||||
|  |     name = parse_name(spec, ver) | ||||||
|  |     return (name, ver) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_version_format(spec): | ||||||
|  |     """Given a URL or archive name, find the version and create a format string | ||||||
|  |        that will allow another version to be substituted. | ||||||
|  |     """ | ||||||
|  |     ver, start, end = parse_version_string_with_indices(spec) | ||||||
|  |     return spec[:start] + '%s' + spec[end:] | ||||||
| @@ -3,7 +3,10 @@ | |||||||
| from functools import total_ordering | from functools import total_ordering | ||||||
|  |  | ||||||
| import utils | import utils | ||||||
| import spack.error as serr | import spack.error | ||||||
|  |  | ||||||
|  | # Valid version characters | ||||||
|  | VALID_VERSION = r'[A-Za-z0-9_.-]' | ||||||
|  |  | ||||||
|  |  | ||||||
| def int_if_int(string): | def int_if_int(string): | ||||||
| @@ -26,28 +29,37 @@ def ver(string): | |||||||
| @total_ordering | @total_ordering | ||||||
| class Version(object): | class Version(object): | ||||||
|     """Class to represent versions""" |     """Class to represent versions""" | ||||||
|     def __init__(self, version_string): |     def __init__(self, string): | ||||||
|  |         if not re.match(VALID_VERSION, string): | ||||||
|  |             raise ValueError("Bad characters in version string: %s" % string) | ||||||
|  |  | ||||||
|         # preserve the original string |         # preserve the original string | ||||||
|         self.version_string = version_string |         self.string = string | ||||||
|  |  | ||||||
|         # Split version into alphabetical and numeric segments |         # Split version into alphabetical and numeric segments | ||||||
|         segments = re.findall(r'[a-zA-Z]+|[0-9]+', version_string) |         segment_regex = r'[a-zA-Z]+|[0-9]+' | ||||||
|  |         segments = re.findall(segment_regex, string) | ||||||
|         self.version = tuple(int_if_int(seg) for seg in segments) |         self.version = tuple(int_if_int(seg) for seg in segments) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def up_to(self, index): |     def up_to(self, index): | ||||||
|         """Return a version string up to the specified component, exclusive. |         """Return a version string up to the specified component, exclusive. | ||||||
|            e.g., if this is 10.8.2, self.up_to(2) will return '10.8'. |            e.g., if this is 10.8.2, self.up_to(2) will return '10.8'. | ||||||
|         """ |         """ | ||||||
|         return '.'.join(str(x) for x in self[:index]) |         return '.'.join(str(x) for x in self[:index]) | ||||||
|  |  | ||||||
|  |     def __iter__(self): | ||||||
|  |         for v in self.version: | ||||||
|  |             yield v | ||||||
|  |  | ||||||
|     def __getitem__(self, idx): |     def __getitem__(self, idx): | ||||||
|         return tuple(self.version[idx]) |         return tuple(self.version[idx]) | ||||||
|  |  | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return self.version_string |         return self.string | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.version_string |         return self.string | ||||||
|  |  | ||||||
|     def __lt__(self, other): |     def __lt__(self, other): | ||||||
|         """Version comparison is designed for consistency with the way RPM |         """Version comparison is designed for consistency with the way RPM | ||||||
| @@ -145,144 +157,3 @@ def __str__(self): | |||||||
|         if self.end: |         if self.end: | ||||||
|             out += str(self.end) |             out += str(self.end) | ||||||
|         return out |         return out | ||||||
|  |  | ||||||
|  |  | ||||||
| class VersionParseError(serr.SpackError): |  | ||||||
|     """Raised when the version module can't parse something.""" |  | ||||||
|     def __init__(self, msg, spec): |  | ||||||
|         super(VersionParseError, self).__init__(msg) |  | ||||||
|         self.spec = spec |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UndetectableVersionError(VersionParseError): |  | ||||||
|     """Raised when we can't parse a version from a string.""" |  | ||||||
|     def __init__(self, spec): |  | ||||||
|         super(UndetectableVersionError, self).__init__( |  | ||||||
|             "Couldn't detect version in: " + spec, spec) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UndetectableNameError(VersionParseError): |  | ||||||
|     """Raised when we can't parse a package name from a string.""" |  | ||||||
|     def __init__(self, spec): |  | ||||||
|         super(UndetectableNameError, self).__init__( |  | ||||||
|             "Couldn't parse package name in: " + spec) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_version_string_with_indices(spec): |  | ||||||
|     """Try to extract a version string from a filename or URL.  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 = utils.stem(os.path.dirname(spec)) |  | ||||||
|     else: |  | ||||||
|         stem = utils.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), |  | ||||||
|  |  | ||||||
|         # 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 vtype in version_types: |  | ||||||
|         regex, match_string = vtype[:2] |  | ||||||
|         match = re.search(regex, match_string) |  | ||||||
|         if match and match.group(1) is not None: |  | ||||||
|             return match.group(1), match.start(1), match.end(1) |  | ||||||
|  |  | ||||||
|     raise UndetectableVersionError(spec) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_version(spec): |  | ||||||
|     """Given a URL or archive name, extract a version from it and return |  | ||||||
|        a version object. |  | ||||||
|     """ |  | ||||||
|     ver, start, end = parse_version_string_with_indices(spec) |  | ||||||
|     return Version(ver) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_version_format(spec): |  | ||||||
|     """Given a URL or archive name, find the version and create a format string |  | ||||||
|        that will allow another version to be substituted. |  | ||||||
|     """ |  | ||||||
|     ver, start, end = parse_version_string_with_indices(spec) |  | ||||||
|     return spec[:start] + '%s' + spec[end:] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def replace_version(spec, new_version): |  | ||||||
|     version = create_version_format(spec) |  | ||||||
|     # TODO: finish this function. |  | ||||||
|  |  | ||||||
| def parse_name(spec, ver=None): |  | ||||||
|     if ver is None: |  | ||||||
|         ver = parse_version(spec) |  | ||||||
|  |  | ||||||
|     ntypes = (r'/sourceforge/([^/]+)/', |  | ||||||
|               r'/([^/]+)/(tarball|zipball)/', |  | ||||||
|               r'/([^/]+)[_.-](bin|dist|stable|src|sources)[_.-]%s' % ver, |  | ||||||
|               r'/([^/]+)[_.-]v?%s' % ver, |  | ||||||
|               r'/([^/]+)%s' % ver, |  | ||||||
|               r'^([^/]+)[_.-]v?%s' % ver, |  | ||||||
|               r'^([^/]+)%s' % ver) |  | ||||||
|  |  | ||||||
|     for nt in ntypes: |  | ||||||
|         match = re.search(nt, spec) |  | ||||||
|         if match: |  | ||||||
|             return match.group(1) |  | ||||||
|     raise UndetectableNameError(spec) |  | ||||||
|  |  | ||||||
| def parse(spec): |  | ||||||
|     ver = parse_version(spec) |  | ||||||
|     name = parse_name(spec, ver) |  | ||||||
|     return (name, ver) |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Todd Gamblin
					Todd Gamblin