From 7a45b9a0e2275a038db2945e1ac2c2e426e94344 Mon Sep 17 00:00:00 2001 From: Luke Campagnola <luke.campagnola@gmail.com> Date: Sun, 12 Jan 2014 10:35:31 -0500 Subject: [PATCH] Reorganized setup.py code Added "deb" setup command --- .gitignore | 3 + setup.py | 228 ++++++++++++++++++++----------------- tools/generateChangelog.py | 116 ++++++++++--------- tools/setupHelpers.py | 114 +++++++++++++++++++ 4 files changed, 300 insertions(+), 161 deletions(-) create mode 100644 tools/setupHelpers.py diff --git a/.gitignore b/.gitignore index 28ed45aa..b8e7af73 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ __pycache__ build *.pyc *.swp +MANIFEST +deb_build +dist diff --git a/setup.py b/setup.py index 826bb57c..761a090d 100644 --- a/setup.py +++ b/setup.py @@ -1,138 +1,156 @@ -from distutils.core import setup -import distutils.dir_util -import os, sys, re -from subprocess import check_output +DESCRIPTION = """\ +PyQtGraph is a pure-python graphics and GUI library built on PyQt4/PySide and +numpy. -## generate list of all sub-packages -path = os.path.abspath(os.path.dirname(__file__)) -n = len(path.split(os.path.sep)) -subdirs = [i[0].split(os.path.sep)[n:] for i in os.walk(os.path.join(path, 'pyqtgraph')) if '__init__.py' in i[2]] -all_packages = ['.'.join(p) for p in subdirs] + ['pyqtgraph.examples'] +It is intended for use in mathematics / scientific / engineering applications. +Despite being written entirely in python, the library is very fast due to its +heavy leverage of numpy for number crunching, Qt's GraphicsView framework for +2D display, and OpenGL for 3D display. +""" +setupOpts = dict( + name='pyqtgraph', + description='Scientific Graphics and GUI Library for Python', + long_description=DESCRIPTION, + license='MIT', + url='http://www.pyqtgraph.org', + author='Luke Campagnola', + author_email='luke.campagnola@gmail.com', + classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Environment :: Other Environment", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: User Interfaces", + ], +) -## Make sure build directory is clean before installing -buildPath = os.path.join(path, 'build') -if os.path.isdir(buildPath): - distutils.dir_util.remove_tree(buildPath) +from distutils.core import setup +import distutils.dir_util +import os, sys, re -## Determine current version string -initfile = os.path.join(path, 'pyqtgraph', '__init__.py') -init = open(initfile).read() -m = re.search(r'__version__ = (\S+)\n', init) -if m is None or len(m.groups()) != 1: - raise Exception("Cannot determine __version__ from init file: '%s'!" % initfile) -version = m.group(1).strip('\'\"') -initVersion = version +path = os.path.split(__file__)[0] +sys.path.insert(0, os.path.join(path, 'tools')) +import setupHelpers as helpers -# If this is a git checkout, try to generate a more decriptive version string -try: - if os.path.isdir(os.path.join(path, '.git')): - def gitCommit(name): - commit = check_output(['git', 'show', name], universal_newlines=True).split('\n')[0] - assert commit[:7] == 'commit ' - return commit[7:] - - # Find last tag matching "pyqtgraph-.*" - tagNames = check_output(['git', 'tag'], universal_newlines=True).strip().split('\n') - while True: - if len(tagNames) == 0: - raise Exception("Could not determine last tagged version.") - lastTagName = tagNames.pop() - if re.match(r'pyqtgraph-.*', lastTagName): - break - - # is this commit an unchanged checkout of the last tagged version? - lastTag = gitCommit(lastTagName) - head = gitCommit('HEAD') - if head != lastTag: - branch = re.search(r'\* (.*)', check_output(['git', 'branch'], universal_newlines=True)).group(1) - version = version + "-%s-%s" % (branch, head[:10]) - - # any uncommitted modifications? - modified = False - status = check_output(['git', 'status', '-s'], universal_newlines=True).strip().split('\n') - for line in status: - if line[:2] != '??': - modified = True - break - - if modified: - version = version + '+' - sys.stderr.write("Detected git commit; will use version string: '%s'\n" % version) -except: - version = initVersion - sys.stderr.write("This appears to be a git checkout, but an error occurred " - "while attempting to determine a version string for the " - "current commit.\nUsing the unmodified version string " - "instead: '%s'\n" % version) - sys.excepthook(*sys.exc_info()) +## generate list of all sub-packages +allPackages = helpers.listAllPackages(pkgroot='pyqtgraph') + ['pyqtgraph.examples'] + +## Decide what version string to use in the build +version, forcedVersion, gitVersion, initVersion = helpers.getVersionStrings(pkg='pyqtgraph') import distutils.command.build class Build(distutils.command.build.build): + """ + * Clear build path before building + * Set version string in __init__ after building + """ def run(self): + global path, version, initVersion, forcedVersion + global buildVersion + + ## Make sure build directory is clean + buildPath = os.path.join(path, self.build_lib) + if os.path.isdir(buildPath): + distutils.dir_util.remove_tree(buildPath) + ret = distutils.command.build.build.run(self) # If the version in __init__ is different from the automatically-generated # version string, then we will update __init__ in the build directory - global path, version, initVersion if initVersion == version: return ret - initfile = os.path.join(path, self.build_lib, 'pyqtgraph', '__init__.py') - if not os.path.isfile(initfile): - sys.stderr.write("Warning: setup detected a git install and attempted " - "to generate a descriptive version string; however, " - "the expected build file at %s was not found. " - "Installation will use the original version string " - "%s instead.\n" % (initfile, initVersion) - ) - else: + try: + initfile = os.path.join(buildPath, 'pyqtgraph', '__init__.py') data = open(initfile, 'r').read() open(initfile, 'w').write(re.sub(r"__version__ = .*", "__version__ = '%s'" % version, data)) + buildVersion = version + except: + if forcedVersion: + raise + buildVersion = initVersion + sys.stderr.write("Warning: Error occurred while setting version string in build path. " + "Installation will use the original version string " + "%s instead.\n" % (initVersion) + ) + sys.excepthook(*sys.exc_info()) return ret +from distutils.core import Command +import shutil, subprocess -setup(name='pyqtgraph', - version=version, - cmdclass={'build': Build}, - description='Scientific Graphics and GUI Library for Python', - long_description="""\ -PyQtGraph is a pure-python graphics and GUI library built on PyQt4/PySide and -numpy. +class DebCommand(Command): + description = "build .deb package" + user_options = [] + def initialize_options(self): + self.cwd = None + def finalize_options(self): + self.cwd = os.getcwd() + def run(self): + assert os.getcwd() == self.cwd, 'Must be in package root: %s' % self.cwd + global version + pkgName = "python-pyqtgraph-" + version + debDir = "deb_build" + if os.path.isdir(debDir): + raise Exception('DEB build dir already exists: "%s"' % debDir) + sdist = "dist/pyqtgraph-%s.tar.gz" % version + if not os.path.isfile(sdist): + raise Exception("No source distribution; run `setup.py sdist` first.") + + # copy sdist to build directory and extract + os.mkdir(debDir) + renamedSdist = 'python-pyqtgraph_%s.orig.tar.gz' % version + shutil.copy(sdist, os.path.join(debDir, renamedSdist)) + if os.system("cd %s; tar -xzf %s" % (debDir, renamedSdist)) != 0: + raise Exception("Error extracting source distribution.") + buildDir = '%s/pyqtgraph-%s' % (debDir, version) + + # copy debian control structure + shutil.copytree('tools/debian', buildDir+'/debian') + + # Write changelog + #chlog = subprocess.check_output([sys.executable, 'tools/generateChangelog.py', 'CHANGELOG']) + #open('%s/pyqtgraph-%s/debian/changelog', 'w').write(chlog) + if os.system('python tools/generateChangelog.py CHANGELOG %s > %s/debian/changelog' % (version, buildDir)) != 0: + raise Exception("Error writing debian/changelog") + + # build package + if os.system('cd %s; debuild -us -uc' % buildDir) != 0: + raise Exception("Error during debuild.") -It is intended for use in mathematics / scientific / engineering applications. -Despite being written entirely in python, the library is very fast due to its -heavy leverage of numpy for number crunching, Qt's GraphicsView framework for -2D display, and OpenGL for 3D display. -""", - license='MIT', - url='http://www.pyqtgraph.org', - author='Luke Campagnola', - author_email='luke.campagnola@gmail.com', - packages=all_packages, +class TestCommand(Command): + description = "" + user_options = [] + def initialize_options(self): + pass + def finalize_options(self): + pass + def run(self): + global cmd + cmd = self + +setup( + version=version, + cmdclass={'build': Build, 'deb': DebCommand, 'test': TestCommand}, + packages=allPackages, package_dir={'pyqtgraph.examples': 'examples'}, ## install examples along with the rest of the source #package_data={'pyqtgraph': ['graphicsItems/PlotItem/*.png']}, - classifiers = [ - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Development Status :: 4 - Beta", - "Environment :: Other Environment", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Scientific/Engineering :: Visualization", - "Topic :: Software Development :: User Interfaces", - ], install_requires = [ 'numpy', 'scipy', ], + **setupOpts ) diff --git a/tools/generateChangelog.py b/tools/generateChangelog.py index 0c8bf3e6..32c9ad2c 100644 --- a/tools/generateChangelog.py +++ b/tools/generateChangelog.py @@ -1,66 +1,70 @@ -from subprocess import check_output -import re, time +import re, time, sys +if len(sys.argv) < 3: + sys.stderr.write("Must specify changelog file and latest release!\n") + sys.exit(-1) -def run(cmd): - return check_output(cmd, shell=True) +### Convert CHANGELOG format like: +""" +pyqtgraph-0.9.1 2012-12-29 -tags = run('bzr tags') -versions = [] -for tag in tags.split('\n'): - if tag.strip() == '': - continue - ver, rev = re.split(r'\s+', tag) - if ver.startswith('pyqtgraph-'): - versions.append(ver) + - change + - change +""" -for i in range(len(versions)-1)[::-1]: - log = run('bzr log -r tag:%s..tag:%s' % (versions[i], versions[i+1])) - changes = [] - times = [] - inmsg = False - for line in log.split('\n'): - if line.startswith('message:'): - inmsg = True - continue - elif line.startswith('-----------------------'): - inmsg = False - continue - - if inmsg: - changes.append(line) - else: - m = re.match(r'timestamp:\s+(.*)$', line) - if m is not None: - times.append(m.groups()[0]) +### to debian changelog format: +""" +python-pyqtgraph (0.9.1-1) UNRELEASED; urgency=low - citime = time.strptime(times[0][:-6], '%a %Y-%m-%d %H:%M:%S') + * Initial release. - print "python-pyqtgraph (%s-1) UNRELEASED; urgency=low" % versions[i+1].split('-')[1] - print "" - for line in changes: - for n in range(len(line)): - if line[n] != ' ': - n += 1 - break + -- Luke <luke.campagnola@gmail.com> Sat, 29 Dec 2012 01:07:23 -0500 +""" - words = line.split(' ') - nextline = '' - for w in words: - if len(w) + len(nextline) > 79: - print nextline - nextline = (' '*n) + w - else: - nextline += ' ' + w - print nextline - #print '\n'.join(changes) - print "" - print " -- Luke <luke.campagnola@gmail.com> %s -0%d00" % (time.strftime('%a, %d %b %Y %H:%M:%S', citime), time.timezone/3600) - #print " -- Luke <luke.campagnola@gmail.com> %s -0%d00" % (times[0], time.timezone/3600) - print "" -print """python-pyqtgraph (0.9.0-1) UNRELEASED; urgency=low - * Initial release. +releases = [] +current_version = None +current_log = None +current_date = None +for line in open(sys.argv[1]).readlines(): + match = re.match(r'pyqtgraph-(\d+\.\d+\.\d+(\.\d+)?)\s*(\d+-\d+-\d+)\s*$', line) + if match is None: + if current_log is not None: + current_log.append(line) + else: + if current_log is not None: + releases.append((current_version, current_log, current_date)) + current_version, current_date = match.groups()[0], match.groups()[2] + #sys.stderr.write("Found release %s\n" % current_version) + current_log = [] + +if releases[0][0] != sys.argv[2]: + sys.stderr.write("Latest release in changelog (%s) does not match current release (%s)\n" % (releases[0][0], sys.argv[2])) + sys.exit(-1) + +for release, changes, date in releases: + date = time.strptime(date, '%Y-%m-%d') + changeset = [ + "python-pyqtgraph (%s-1) UNRELEASED; urgency=low\n" % release, + "\n"] + changes + [ + " -- Luke <luke.campagnola@gmail.com> %s -0%d00\n" % (time.strftime('%a, %d %b %Y %H:%M:%S', date), time.timezone/3600), + "\n" ] + + # remove consecutive blank lines except between releases + clean = "" + lastBlank = True + for line in changeset: + if line.strip() == '': + if lastBlank: + continue + else: + clean += line + lastBlank = True + else: + clean += line + lastBlank = False + + print clean + print "" - -- Luke <luke.campagnola@gmail.com> Thu, 27 Dec 2012 02:46:26 -0500""" diff --git a/tools/setupHelpers.py b/tools/setupHelpers.py new file mode 100644 index 00000000..216e6cc2 --- /dev/null +++ b/tools/setupHelpers.py @@ -0,0 +1,114 @@ +import os, sys, re +from subprocess import check_output + +def listAllPackages(pkgroot): + path = os.getcwd() + n = len(path.split(os.path.sep)) + subdirs = [i[0].split(os.path.sep)[n:] for i in os.walk(os.path.join(path, pkgroot)) if '__init__.py' in i[2]] + return ['.'.join(p) for p in subdirs] + + +def getInitVersion(pkgroot): + """Return the version string defined in __init__.py""" + path = os.getcwd() + initfile = os.path.join(path, pkgroot, '__init__.py') + init = open(initfile).read() + m = re.search(r'__version__ = (\S+)\n', init) + if m is None or len(m.groups()) != 1: + raise Exception("Cannot determine __version__ from init file: '%s'!" % initfile) + version = m.group(1).strip('\'\"') + return version + +def gitCommit(name): + """Return the commit ID for the given name.""" + commit = check_output(['git', 'show', name], universal_newlines=True).split('\n')[0] + assert commit[:7] == 'commit ' + return commit[7:] + +def getGitVersion(tagPrefix): + """Return a version string with information about this git checkout. + If the checkout is an unmodified, tagged commit, then return the tag version. + If this is not a tagged commit, return version-branch_name-commit_id. + If this checkout has been modified, append "+" to the version. + """ + path = os.getcwd() + if not os.path.isdir(os.path.join(path, '.git')): + return None + + # Find last tag matching "tagPrefix.*" + tagNames = check_output(['git', 'tag'], universal_newlines=True).strip().split('\n') + while True: + if len(tagNames) == 0: + raise Exception("Could not determine last tagged version.") + lastTagName = tagNames.pop() + if re.match(tagPrefix+r'\d+\.\d+.*', lastTagName): + break + gitVersion = lastTagName.replace(tagPrefix, '') + + # is this commit an unchanged checkout of the last tagged version? + lastTag = gitCommit(lastTagName) + head = gitCommit('HEAD') + if head != lastTag: + branch = re.search(r'\* (.*)', check_output(['git', 'branch'], universal_newlines=True)).group(1) + gitVersion = gitVersion + "-%s-%s" % (branch, head[:10]) + + # any uncommitted modifications? + modified = False + status = check_output(['git', 'status', '-s'], universal_newlines=True).strip().split('\n') + for line in status: + if line[:2] != '??': + modified = True + break + + if modified: + gitVersion = gitVersion + '+' + + return gitVersion + +def getVersionStrings(pkg): + """ + Returns 4 version strings: + + * the version string to use for this build, + * version string requested with --force-version (or None) + * version string that describes the current git checkout (or None). + * version string in the pkg/__init__.py, + + The first return value is (forceVersion or gitVersion or initVersion). + """ + + ## Determine current version string from __init__.py + initVersion = getInitVersion(pkgroot='pyqtgraph') + + ## If this is a git checkout, try to generate a more descriptive version string + try: + gitVersion = getGitVersion(tagPrefix='pyqtgraph-') + except: + gitVersion = None + sys.stderr.write("This appears to be a git checkout, but an error occurred " + "while attempting to determine a version string for the " + "current commit.\n") + sys.excepthook(*sys.exc_info()) + + # See whether a --force-version flag was given + forcedVersion = None + for i,arg in enumerate(sys.argv): + if arg.startswith('--force-version'): + if arg == '--force-version': + forcedVersion = sys.argv[i+1] + sys.argv.pop(i) + sys.argv.pop(i) + elif arg.startswith('--force-version='): + forcedVersion = sys.argv[i].replace('--force-version=', '') + sys.argv.pop(i) + + ## Finally decide on a version string to use: + if forcedVersion is not None: + version = forcedVersion + elif gitVersion is not None: + version = gitVersion + sys.stderr.write("Detected git commit; will use version string: '%s'\n" % version) + else: + version = initVersion + + return version, forcedVersion, gitVersion, initVersion \ No newline at end of file -- GitLab