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