From: Zooko O'Whielacronx Date: Thu, 16 Aug 2007 21:09:30 +0000 (-0700) Subject: import version class and make-version script from pyutil -- fixes win32 build, improv... X-Git-Url: https://git.rkrishnan.org/FOOURL?a=commitdiff_plain;h=094b687d6e785c19877497c12779eac90483f0e6;p=tahoe-lafs%2Ftahoe-lafs.git import version class and make-version script from pyutil -- fixes win32 build, improves error handling, and eliminates unused features --- diff --git a/Makefile b/Makefile index f984f87d..e73293fb 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ PP=PYTHONPATH=$(PYTHONPATH) .PHONY: make-version build make-version: - $(PYTHON) misc/make-version.py + $(PYTHON) misc/make-version.py "allmydata-tahoe" "src/allmydata/_version.py" build: make-version build-zfec build-Crypto build-foolscap build-simplejson $(PP) $(PYTHON) ./setup.py $(EXTRA_SETUP_ARGS) install --prefix="$(INSTDIR)" --install-lib="$(INSTDIR)/lib" --install-scripts="$(INSTDIR)/bin" diff --git a/misc/make-version.py b/misc/make-version.py index 36da1717..5dc0af01 100644 --- a/misc/make-version.py +++ b/misc/make-version.py @@ -1,35 +1,26 @@ #! /usr/bin/env python -""" -Create src/allmydata/version.py, based upon the latest darcs release tag. +import os, sys -If your source tree is coming from darcs (i.e. there exists a _darcs -directory), this tool will determine the most recent release tag, count the -patches that have been applied since then, and compute a version number to be -written into version.py . This version number will be available by doing: +""" +Create _version.py, based upon the latest darcs release tag. - from allmydata import __version__ +If your source tree is coming from darcs (i.e. it is in a darcs repository), +this tool will determine the most recent release tag, count the patches that +have been applied since then, and compute a version number to be written into +_version.py . This version number will be available by doing: -Source trees that do not come from darcs (release tarballs, nightly tarballs) -do not have a _darcs directory. Instead, they should have a version.py that -was generated before the tarball was produced. In this case, this script will -quietly exit without modifying the existing version.py . + from your_package_name import __version__ -FYI, src/allmydata/__init__.py will attempt to import version.py and use the -version number therein. If it cannot, it will announce a version of -'UNKNOWN'. This should only happen if someone manages to get hold of a -non-_darcs/ source tree. +Source trees that do not come from darcs (e.g. release tarballs, nightly +tarballs) and are not within a darcs repository should instead, come with a +_version.py that was generated before the tarball was produced. In this case, +this script will quietly exit without modifying the existing _version.py . -'release tags' are tags in the tahoe source tree that match the following +'release tags' are tags in the source repository that match the following regexp: - ^allmydata-tahoe-\d+\.\d+\.\d+\w*$ - -This excludes zfec tags (which start with 'zfec '). It also excludes -'developer convenience tags', which look like 'hoping to fix bug -warner'. -(the original goal was to use release tags that lacked the 'allmydata-tahoe-' -prefix, but it turns out to be more efficient to keep it in, because I can't -get 'darcs changes --from-tag=' to accept real regexps). + ^your_package_name-\d+\.\d+(\.\d+)?((a|b|c)(\d+)?)?\w*$ """ @@ -37,6 +28,16 @@ import os, sys, re import xml.dom.minidom from subprocess import Popen, PIPE +try: + # If we can import allmydata.util.version_class then use its regex. + from allmydata.util import version_class + VERSION_BASE_RE_STR = version_class.VERSION_BASE_RE_STR +except ImportError: + # Else (perhaps a bootstrapping problem),then we'll use this + # regex, which was copied from the pyutil source code on + # 2007-08-11. + VERSION_BASE_RE_STR="(\d+)\.(\d+)(\.(\d+))?((a|b|c)(\d+))?" + def get_text(nodelist): rc = "" for node in nodelist: @@ -45,55 +46,53 @@ def get_text(nodelist): return rc VERSION_BODY = ''' -from util.version import Version +from allmydata.util.version_class import Version -# This is the version of this tree, as created by misc/make-version.py from +# This is the version of this tree, as created by scripts/make-version.py from # the Darcs patch information: the main version number is taken from the most # recent release tag. If some patches have been added since the last release, # this will have a -NN "build number" suffix. Please see -# allmydata.util.version for a description of what the different fields mean. +# allmydata.util.version_class for a description of what the different fields +# mean. verstr = "%s" __version__ = Version(verstr) ''' -def write_version_py(verstr): - f = open("src/allmydata/version.py", "wt") +def write_version_py(verstr, outfname): + f = open(outfname, "wt+") f.write(VERSION_BODY % (verstr,)) f.close() -def update(): - if not os.path.exists("_darcs") or not os.path.isdir("_darcs"): - if os.path.exists("src/allmydata/version.py"): - print "no _darcs/ and version.py exists, leaving it alone" - return 0 - print "no _darcs/ but no version.py either: how did you get this tree?" - return 0 - cmd = ["darcs", "changes", "--from-tag=^allmydata-tahoe", "--xml-output"] +def update(pkgname, verfilename): + rc = -1 + cmd = ["darcs", "changes", "--from-tag=^%s" % (pkgname,), "--xml-output"] try: p = Popen(cmd, stdout=PIPE) + except: + pass + else: output = p.communicate()[0] rc = p.returncode - except EnvironmentError, le: - output = "There was an environment error: %s" % (le,) - rc = -1 - if rc != 0: - print "unable to run 'darcs changes':" - print output - print "so I'm leaving version.py alone" - return 0 - - try: - doc = xml.dom.minidom.parseString(output) - except xml.parsers.expat.ExpatError: - print "unable to parse darcs XML output:" - print output - raise + cmd = ["realdarcs.exe", "changes", "--from-tag=^%s" % (pkgname,), "--xml-output"] + p = Popen(cmd, stdout=PIPE) + output = p.communicate()[0] + rc = p.returncode + if rc != 0: + if os.path.exists(verfilename): + print "Failure from attempt to find version tags with 'darcs changes', and %s already exists, so leaving it alone." % (verfilename,) + return 0 + else: + print "Failure from attempt to find version tags with 'darcs changes', and %s doesn't exist." % (verfilename,) + return rc + + doc = xml.dom.minidom.parseString(output) changelog = doc.getElementsByTagName("changelog")[0] patches = changelog.getElementsByTagName("patch") count = 0 - version_re = re.compile("^TAG allmydata-tahoe-(\d+\.\d+\.\d+\w*)$") + regexstr = "^TAG %s-(%s)" % (pkgname, VERSION_BASE_RE_STR,) + version_re = re.compile(regexstr) for patch in patches: name = get_text(patch.getElementsByTagName("name")[0].childNodes) m = version_re.match(name) @@ -103,23 +102,33 @@ def update(): break count += 1 else: - print "unable to find a matching tag" - print output - print "so I'm leaving version.py alone" + print "I'm unable to find a tag in the darcs history matching \"%s\", so I'm leaving %s alone." % (regexstr, verfilename,) return 0 if count: # this is an interim version - verstr = "%s-%d" % (last_tag, count) + verstr = "%s-dev-r%d" % (last_tag, count) else: # this is a release verstr = last_tag - write_version_py(verstr) - print "wrote '%s' into src/allmydata/version.py" % (verstr,) + write_version_py(verstr, verfilename) + print "wrote '%s' into %s" % (verstr, verfilename,) return 0 if __name__ == '__main__': - rc = update() + if len(sys.argv) >= 2: + pkgname = sys.argv[1] + else: + pkgname = os.path.basename(os.getcwd()) + print "You didn't pass a pkg-name on the command-line, so I'm going to take the name of the current working directory: \"%s\"" % (pkgname,) + + if len(sys.argv) >= 3: + verfilename = sys.argv[2] + else: + verfilename = os.path.join(pkgname, "_version.py") + print "You didn't pass a verfilename on the command-line, so I'm going to build one from the name of the package: \"%s\"" % (verfilename,) + + rc = update(pkgname=pkgname, verfilename=verfilename) sys.exit(rc) diff --git a/src/allmydata/__init__.py b/src/allmydata/__init__.py index 0ad8d293..349c7e2e 100644 --- a/src/allmydata/__init__.py +++ b/src/allmydata/__init__.py @@ -9,7 +9,7 @@ community web site: U{http://allmydata.org/} __version__ = "unknown" try: - from allmydata.version import __version__ + from _version import __version__ except ImportError: # we're running in a tree that hasn't run misc/make-version.py, so we # don't know what our version is. This should not happen very often. diff --git a/src/allmydata/test/test_client.py b/src/allmydata/test/test_client.py index 016be9bd..f5cec184 100644 --- a/src/allmydata/test/test_client.py +++ b/src/allmydata/test/test_client.py @@ -6,7 +6,7 @@ from twisted.internet import reactor, defer import allmydata from allmydata import client, introducer -from allmydata.util import version +from allmydata.util import version_class from foolscap.eventual import flushEventualQueue class MyIntroducerClient(introducer.IntroducerClient): @@ -107,7 +107,7 @@ class Basic(unittest.TestCase): open(os.path.join(basedir, "vdrive.furl"), "w").write("") c = client.Client(basedir) mine, oldest = c.remote_get_versions() - self.failUnlessEqual(version.Version(mine), allmydata.__version__) + self.failUnlessEqual(version_class.Version(mine), allmydata.__version__) def flush_but_dont_ignore(res): d = flushEventualQueue() diff --git a/src/allmydata/util/version.py b/src/allmydata/util/version.py deleted file mode 100644 index e495ff6d..00000000 --- a/src/allmydata/util/version.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright (c) 2004-2007 Bryce "Zooko" Wilcox-O'Hearn -# mailto:zooko@zooko.com -# http://zooko.com/repos/pyutil -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this work to deal in this work without restriction (including the rights -# to use, modify, distribute, sublicense, and/or sell copies). - -""" -extended version number class -""" - -from distutils import version - -# End users see version strings like this: - -# "1.0.0" -# ^ ^ ^ -# | | | -# | | '- micro version number -# | '- minor version number -# '- major version number - -# The first number is "major version number". The second number is the "minor -# version number" -- it gets bumped whenever we make a new release that adds or -# changes functionality. The third version is the "micro version number" -- it -# gets bumped whenever we make a new release that doesn't add or change -# functionality, but just fixes bugs (including performance issues). - -# Early-adopter end users see version strings like this: - -# "1.0.0a1" -# ^ ^ ^^^ -# | | ||| -# | | ||'- release number -# | | |'- alpha or beta (or none) -# | | '- micro version number -# | '- minor version number -# '- major version number - -# The optional "a" or "b" stands for "alpha release" or "beta release" -# respectively. The number after "a" or "b" gets bumped every time we -# make a new alpha or beta release. This has the same form and the same -# meaning as version numbers of releases of Python. - -# Developers see "full version strings", like this: - -# "1.0.0a1-55" -# ^ ^ ^^^ ^ -# | | ||| | -# | | ||| | -# | | ||| '- nano version number -# | | ||'- release number -# | | |'- alpha or beta (or none) -# | | '- micro version number -# | '- minor version number -# '- major version number - -# The next number is the "nano version number". It is meaningful only to -# developers. It gets bumped whenever a developer changes anything that another -# developer might care about. - -class Tag(str): - def __cmp__(t1, t2): - if t1 == t2: - return 0 - if t1 == "UNSTABLE" and t2 == "STABLE": - return 1 - if t1 == "STABLE" and t2 == "UNSTABLE": - return -1 - return -2 # who knows - -class Version: - def __init__(self, vstring=None): - self.major = None - self.minor = None - self.micro = None - self.prereleasetag = None - self.nano = None - self.tags = None - if vstring: - self.parse(vstring) - - def parse(self, vstring): - i = vstring.find('-') - if i != -1: - svstring = vstring[:i] - estring = vstring[i+1:] - else: - svstring = vstring - estring = None - - self.strictversion = version.StrictVersion(svstring) - - self.nanovernum = None - self.tags = [] - if estring: - if '-' in estring: - (self.nano, tags,) = estring.split('-') - else: - self.nano = estring - - self.fullstr = str(self.strictversion) - if self.nano is not None: - self.fullstr += "-" + str(self.nano) - if self.tags: - self.fullstr += '_'.join(self.tags) - - def tags(self): - return self.tags - - def user_str(self): - return self.strictversion.__str__() - - def full_str(self): - return self.fullstr - - def __str__(self): - return self.full_str() - - def __repr__(self): - return self.__str__() - - def __cmp__ (self, other): - if isinstance(other, basestring): - other = Version(other) - - res = cmp(self.strictversion, other.strictversion) - if res != 0: - return res - - res = cmp(self.nano, other.nano) - if res != 0: - return res - - return cmp(self.tags, other.tags) diff --git a/src/allmydata/util/version_class.py b/src/allmydata/util/version_class.py new file mode 100644 index 00000000..dbab3b87 --- /dev/null +++ b/src/allmydata/util/version_class.py @@ -0,0 +1,133 @@ +# Copyright (c) 2004-2007 Bryce "Zooko" Wilcox-O'Hearn +# mailto:zooko@zooko.com +# http://zooko.com/repos/pyutil +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this work to deal in this work without restriction (including the rights +# to use, modify, distribute, sublicense, and/or sell copies). + +""" +extended version number class +""" + +# from setuptools, but intended to be included in future version of Python Standard Library (PEP 365) +import pkg_resources + +# bindann, by Nathan Wilcox (needed only for debugging) +try: + import bindann + bindann.install_exception_handler() +except ImportError: + pass + +# Python Standard Library +import re + +# End users see version strings like this: + +# "1.0.0" +# ^ ^ ^ +# | | | +# | | '- micro version number +# | '- minor version number +# '- major version number + +# The first number is "major version number". The second number is the "minor +# version number" -- it gets bumped whenever we make a new release that adds or +# changes functionality. The third version is the "micro version number" -- it +# gets bumped whenever we make a new release that doesn't add or change +# functionality, but just fixes bugs (including performance issues). + +# Early-adopter end users see version strings like this: + +# "1.0.0a1" +# ^ ^ ^^^ +# | | ||| +# | | ||'- release number +# | | |'- a=alpha, b=beta, c=release candidate, or none +# | | '- micro version number +# | '- minor version number +# '- major version number + +# The optional "a" or "b" stands for "alpha release" or "beta release" +# respectively. The number after "a" or "b" gets bumped every time we +# make a new alpha or beta release. This has the same form and the same +# meaning as version numbers of releases of Python. + +# Developers see "full version strings", like this: + +# "1.0.0a1-dev-r55" +# ^ ^ ^^^ ^ ^ +# | | ||| | | +# | | ||| | '- nano version number +# | | ||| '- "dev" if this is a development version (not a release version) +# | | ||'- release number +# | | |'- a=alpha, b=beta, c=release candidate or none +# | | '- micro version number +# | '- minor version number +# '- major version number + +# The presence of "-dev" means that this is a development version. There are +# no guarantees about compatibility, etc. This version is considered to be +# more recent than the version without this field (e.g. "1.0.0a1"). + +# The next number is the "nano version number". It is meaningful only to +# developers. It gets generated automatically from darcs revision control +# history by "make-version-from-darcs-history.py". It is the count of patches +# that have been applied since the last version number tag was applied. + +VERSION_BASE_RE_STR="(\d+)\.(\d+)(\.(\d+))?((a|b|c)(\d+))?" +VERSION_RE_STR=VERSION_BASE_RE_STR + "(-dev-r(\d+))?" +VERSION_RE=re.compile("^" + VERSION_RE_STR + "$") + +class Version(object): + def __init__(self, vstring=None): + self.major = None + self.minor = None + self.micro = None + self.prereleasetag = None + self.prerelease = None + self.nano = None + self.leftovers = '' + if vstring: + try: + self.parse(vstring) + except ValueError, le: + le.args = tuple(le.args + ('vstring:', vstring,)) + raise + + def parse(self, vstring): + mo = VERSION_RE.search(vstring) + if not mo: + raise ValueError, "Not a valid version string for allmydata.util.version_class.Version(): %r" % (vstring,) + + self.major = int(mo.group(1)) + self.minor = int(mo.group(2)) + self.micro = int(mo.group(4)) + reltag = mo.group(5) + if reltag: + reltagnum = int(mo.group(6)) + self.prereleasetag = reltag + self.prerelease = reltagnum + + if mo.group(7): + self.nano = int(mo.group(8)) + + self.fullstr = "%d.%d.%d%s%s" % (self.major, self.minor, self.micro, self.prereleasetag and "%s%d" % (self.prereleasetag, self.prerelease,) or "", self.nano and "-dev-r%d" % (self.nano,) or "",) + + def user_str(self): + return self.strictversion.__str__() + + def full_str(self): + if hasattr(self, 'fullstr'): + return self.fullstr + else: + return 'None' + + def __str__(self): + return self.full_str() + + def __repr__(self): + return self.__str__() + + def __cmp__ (self, other): + return cmp(pkg_resources.parse_version(str(self)), pkg_resources.parse_version(str(other)))