[tor-commits] [nyx/master] Rewrite our setup.py

atagar at torproject.org atagar at torproject.org
Sun Jun 7 20:44:26 UTC 2015


commit 3a745a32f8323bbdf323e35eb4c7f0be84d9c6d6
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Jun 7 13:39:58 2015 -0700

    Rewrite our setup.py
    
    So much better. This borrows tricks from...
    
      https://the-hitchhikers-guide-to-packaging.readthedocs.org/en/latest/specification.html
    
    ... to make a custom installer class rather than piling hacks on distutils as
    we previously did. In particular...
    
      * Installer works once again (ongoing rewrite broke it).
    
      * We can now install using python3. The codebase though isn't quite python3
        compatible just yet.
    
      * We now support --man-page and --sample-path arguments for customizing those
        locations.
    
      * Test coverage for installation. This is similar to a test I recently wrote
        for Stem.
---
 install              |   15 ----
 nyx/__init__.py      |   12 ++-
 nyx/starter.py       |   12 +--
 nyx/util/__init__.py |    2 +-
 setup.py             |  225 ++++++++++++++++++++++++--------------------------
 test/installation.py |   39 +++++++++
 6 files changed, 162 insertions(+), 143 deletions(-)

diff --git a/install b/install
deleted file mode 100755
index be1f054..0000000
--- a/install
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-python src/prereq.py
-
-if [ $? = 0 ]; then
-  python setup.py -q install
-  
-  # provide notice if we installed successfully
-  if [ $? = 0 ]; then
-    echo "installed to /usr/share/nyx"
-  fi
-  
-  # cleans up the automatically built temporary files
-  rm -rf ./build
-fi
-
diff --git a/nyx/__init__.py b/nyx/__init__.py
index c38b4f7..65e3c71 100644
--- a/nyx/__init__.py
+++ b/nyx/__init__.py
@@ -2,8 +2,12 @@
 Tor curses monitoring application.
 """
 
-__version__ = '1.4.6_dev'
+__version__ = '1.4.6-dev'
 __release_date__ = 'April 28, 2011'
+__author__ = 'Damian Johnson'
+__contact__ = 'atagar at torproject.org'
+__url__ = 'http://www.atagar.com/arm/'
+__license__ = 'GPLv3'
 
 __all__ = [
   'arguments',
@@ -33,15 +37,15 @@ def main():
       else:
         advice = ', you can find it at https://stem.torproject.org/download.html'
 
-      print 'nyx requires stem' + advice
+      print('nyx requires stem' + advice)
     elif exc.message == 'No module named curses':
       if distutils.spawn.find_executable('apt-get') is not None:
         advice = ", try running 'sudo apt-get install python-curses'"
       else:
         advice = ''  # not sure what to do for other platforms
 
-      print 'nyx requires curses' + advice
+      print('nyx requires curses' + advice)
     else:
-      print 'Unable to start nyx: %s' % exc
+      print('Unable to start nyx: %s' % exc)
 
     sys.exit(1)
diff --git a/nyx/starter.py b/nyx/starter.py
index 87937ed..d541730 100644
--- a/nyx/starter.py
+++ b/nyx/starter.py
@@ -35,22 +35,22 @@ def main(config):
     args = nyx.arguments.parse(sys.argv[1:])
     config.set('startup.events', args.logged_events)
   except ValueError as exc:
-    print exc
+    print(exc)
     sys.exit(1)
 
   if args.print_help:
-    print nyx.arguments.get_help()
+    print(nyx.arguments.get_help())
     sys.exit()
   elif args.print_version:
-    print nyx.arguments.get_version()
+    print(nyx.arguments.get_version())
     sys.exit()
 
   if args.debug_path is not None:
     try:
       _setup_debug_logging(args)
-      print msg('debug.saving_to_path', path = args.debug_path)
+      print(msg('debug.saving_to_path', path = args.debug_path))
     except IOError as exc:
-      print msg('debug.unable_to_write_file', path = args.debug_path, error = exc.strerror)
+      print(msg('debug.unable_to_write_file', path = args.debug_path, error = exc.strerror))
       sys.exit(1)
 
   _load_user_nyxrc(args.config)
@@ -91,7 +91,7 @@ def main(config):
     curses.wrapper(nyx.controller.start_nyx)
   except UnboundLocalError as exc:
     if os.environ['TERM'] != 'xterm':
-      print msg('setup.unknown_term', term = os.environ['TERM'])
+      print(msg('setup.unknown_term', term = os.environ['TERM']))
     else:
       raise exc
   except KeyboardInterrupt:
diff --git a/nyx/util/__init__.py b/nyx/util/__init__.py
index 58e739c..13cb6db 100644
--- a/nyx/util/__init__.py
+++ b/nyx/util/__init__.py
@@ -28,7 +28,7 @@ TESTING = False
 try:
   uses_settings = stem.util.conf.uses_settings('nyx', os.path.join(BASE_DIR, 'config'), lazy_load = False)
 except IOError as exc:
-  print "Unable to load nyx's internal configurations: %s" % exc
+  print("Unable to load nyx's internal configurations: %s" % exc)
   sys.exit(1)
 
 
diff --git a/setup.py b/setup.py
index c484ea1..a54ff89 100644
--- a/setup.py
+++ b/setup.py
@@ -1,125 +1,116 @@
 #!/usr/bin/env python
-import os
-import sys
+# Copyright 2015, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
 import gzip
-import tempfile
-from nyx.version import VERSION
+import os
+import shutil
+import stat
+
+import nyx
+
+from distutils import log
 from distutils.core import setup
+from distutils.command.install import install
 
-def getResources(dst, sourceDir):
-  """
-  Provides a list of tuples of the form...
-  [(destination, (file1, file2...)), ...]
+DEFAULT_MAN_PAGE_PATH = '/usr/share/man/man1/nyx.1.gz'
+DEFAULT_SAMPLE_PATH = '/usr/share/doc/nyx/nyxrc.sample'
+
+
+def mkdir_for(path):
+  path_dir = os.path.dirname(path)
+
+  if not os.path.exists(path_dir):
+    try:
+      os.makedirs(path_dir)
+    except OSError as exc:
+      raise OSError(None, "unable to make directory %s (%s)" % (path_dir, exc.strerror.lower()))
+
+
+def install_man_page(source, dest):
+  if not os.path.exists(source):
+    raise OSError(None, "man page doesn't exist at '%s'" % source)
 
-  for the given contents of the nyx directory (that's right, distutils isn't
-  smart enough to know how to copy directories).
+  mkdir_for(dest)
+  open_func = gzip.open if dest.endswith('.gz') else open
+
+  with open(source, 'rb') as source_file:
+    with open_func(dest, 'wb') as dest_file:
+      dest_file.write(source_file.read())
+      log.info("installed man page to '%s'" % dest)
+
+
+def install_sample(source, dest):
+  if not os.path.exists(source):
+    raise OSError(None, "nyxrc sample doesn't exist at '%s'" % source)
+
+  mkdir_for(dest)
+  shutil.copyfile(source, dest)
+  log.info("installed sample nyxrc to '%s'" % dest)
+
+
+class NyxInstaller(install):
   """
+  Nyx installer. This adds the following additional options...
+
+    --man-page [path]
+    --sample-path [path]
+
+  If the man page path ends in '.gz' it will be compressed. Empty paths such
+  as...
 
-  results = []
-
-  for root, _, files in os.walk(os.path.join('nyx', sourceDir)):
-    if files:
-      fileListing = tuple([os.path.join(root, file) for file in files])
-      results.append((os.path.join(dst, root[4:]), fileListing))
-
-  return results
-
-# Use 'tor-nyx' instead of 'nyx' in the path for the sample nyxrc if we're
-# building for debian.
-
-isDebInstall = False
-for arg in sys.argv:
-  if 'tor-nyx' in arg or 'release_deb' in arg:
-    isDebInstall = True
-    break
-
-docPath = '/usr/share/doc/%s' % ('tor-nyx' if isDebInstall else 'nyx')
-
-# Allow the docPath to be overridden via a '--docPath' argument. This is to
-# support custom documentation locations on Gentoo, as discussed in:
-# https://bugs.gentoo.org/349792
-
-try:
-  docPathFlagIndex = sys.argv.index('--docPath')
-  if docPathFlagIndex < len(sys.argv) - 1:
-    docPath = sys.argv[docPathFlagIndex + 1]
-
-    # remove the custom --docPath argument (otherwise the setup call will
-    # complain about them)
-    del sys.argv[docPathFlagIndex:docPathFlagIndex + 3]
-  else:
-    print 'No path provided for --docPath'
-    sys.exit(1)
-except ValueError: pass # --docPath flag not found
-
-# Provides the configuration option to install to '/usr/share' rather than as a
-# python module. Alternatives are to either provide this as an input argument
-# (not an option for deb/rpm builds) or add a setup.cfg with:
-#   [install]
-#   install-purelib=/usr/share
-# which would mean a bit more unnecessary clutter.
-
-manFilename = 'nyx/resoureces/nyx.1'
-if 'install' in sys.argv:
-  sys.argv += ['--install-purelib', '/usr/share']
-
-  # Compresses the man page. This is a temporary file that we'll install. If
-  # something goes wrong then we'll print the issue and use the uncompressed man
-  # page instead.
-
-  try:
-    manInputFile = open('nyx/resources/nyx.1', 'r')
-    manContents = manInputFile.read()
-    manInputFile.close()
-
-    # temporary destination for the man page guarenteed to be unoccupied (to
-    # avoid conflicting with files that are already there)
-    tmpFilename = tempfile.mktemp('/nyx.1.gz')
-
-    # make dir if the path doesn't already exist
-    baseDir = os.path.dirname(tmpFilename)
-    if not os.path.exists(baseDir): os.makedirs(baseDir)
-
-    manOutputFile = gzip.open(tmpFilename, 'wb')
-    manOutputFile.write(manContents)
-    manOutputFile.close()
-
-    # places in tmp rather than a relative path to avoid having this copy appear
-    # in the deb and rpm builds
-    manFilename = tmpFilename
-  except IOError, exc:
-    print 'Unable to compress man page: %s' % exc
-
-installPackages = ['nyx', 'nyx.cli', 'nyx.cli.graphing', 'nyx.cli.connections', 'nyx.cli.menu', 'nyx.util', 'nyx.stem']
-
-setup(name='nyx',
-      version=VERSION,
-      description='Terminal tor status monitor',
-      license='GPL v3',
-      author='Damian Johnson',
-      author_email='atagar at torproject.org',
-      url='http://www.atagar.com/nyx/',
-      packages=installPackages,
-      package_dir={'nyx': 'nyx'},
-      data_files=[('/usr/bin', ['run_nyx']),
-                  ('/usr/share/man/man1', [manFilename]),
-                  (docPath, ['nyxrc.sample']),
-                  ('/usr/share/nyx/gui', ['nyx/gui/nyx.xml']),
-                  ('/usr/share/nyx', ['nyx/settings.cfg', 'nyx/uninstall'])] +
-                  getResources('/usr/share/nyx', 'resources'),
-     )
-
-# Cleans up the temporary compressed man page.
-if manFilename != 'nyx/resoureces/nyx.1' and os.path.isfile(manFilename):
-  if '-q' not in sys.argv: print 'Removing %s' % manFilename
-  os.remove(manFilename)
-
-# Removes the egg_info file. Apparently it is not optional during setup
-# (hardcoded in distutils/command/install.py), nor are there any arguments to
-# bypass its creation. The deb build removes this as part of its rules script.
-eggPath = '/usr/share/nyx-%s.egg-info' % VERSION
-
-if not isDebInstall and os.path.isfile(eggPath):
-  if '-q' not in sys.argv: print 'Removing %s' % eggPath
-  os.remove(eggPath)
+    % python setup.py install --man-page ''
+
+  ... will cause that resource to be omitted.
+  """
 
+  user_options = install.user_options + [
+    ('man-page=', None, 'man page location (default: %s)' % DEFAULT_MAN_PAGE_PATH),
+    ('sample-path=', None, 'example nyxrc location (default: %s)' % DEFAULT_SAMPLE_PATH),
+  ]
+
+  def initialize_options(self):
+    install.initialize_options(self)
+    self.man_page = DEFAULT_MAN_PAGE_PATH
+    self.sample_path = DEFAULT_SAMPLE_PATH
+
+  def run(self):
+    install.run(self)
+
+    # Install our bin script. We do this ourselves rather than with the setup()
+    # method's scripts argument because we want to call the script 'nyx' rather
+    # than 'run_nyx'.
+
+    bin_dest = os.path.join(self.install_scripts, 'nyx')
+    mkdir_for(bin_dest)
+    shutil.copyfile('run_nyx', bin_dest)
+    mode = ((os.stat(bin_dest)[stat.ST_MODE]) | 0o555) & 0o7777
+    os.chmod(bin_dest, mode)
+    log.info("installed bin script to '%s'" % bin_dest)
+
+    if self.man_page:
+      install_man_page(os.path.join('nyx', 'resources', 'nyx.1'), self.man_page)
+
+    if self.sample_path:
+      install_sample('nyxrc.sample', self.sample_path)
+
+
+# installation requires us to be in our setup.py's directory
+
+setup_dir = os.path.dirname(os.path.join(os.getcwd(), __file__))
+os.chdir(setup_dir)
+
+setup(
+  name = 'nyx',
+  version = nyx.__version__,
+  description = 'Terminal status monitor for Tor <https://www.torproject.org/>',
+  license = nyx.__license__,
+  author = nyx.__author__,
+  author_email = nyx.__contact__,
+  url = nyx.__url__,
+  packages = ['nyx', 'nyx.connections', 'nyx.menu', 'nyx.util'],
+  keywords = 'tor onion controller',
+  install_requires = ['stem>=1.4.1'],
+  package_data = {'nyx': ['config/*', 'resources/*']},
+  cmdclass = {'install': NyxInstaller},
+)
diff --git a/test/installation.py b/test/installation.py
new file mode 100644
index 0000000..e233d67
--- /dev/null
+++ b/test/installation.py
@@ -0,0 +1,39 @@
+import glob
+import os
+import shutil
+import subprocess
+import sys
+import unittest
+
+import nyx
+import stem.util.system
+
+
+class TestInstallation(unittest.TestCase):
+  def test_installing_stem(self):
+    base_directory = os.path.sep.join(__file__.split(os.path.sep)[:-2])
+
+    if not os.path.exists(os.path.sep.join([base_directory, 'setup.py'])):
+      self.skipTest('(only for git checkout)')
+
+    original_cwd = os.getcwd()
+
+    try:
+      os.chdir(base_directory)
+      stem.util.system.call('python setup.py install --prefix /tmp/nyx_test --man-page /tmp/nyx_test/nyx.1.gz --sample-path /tmp/nyx_test/nyxrc.sample')
+      stem.util.system.call('python setup.py clean --all')  # tidy up the build directory
+      site_packages_paths = glob.glob('/tmp/nyx_test/lib*/*/site-packages')
+
+      if len(site_packages_paths) != 1:
+        self.fail('We should only have a single site-packages directory, but instead had: %s' % site_packages_paths)
+
+      self.assertEqual(nyx.__version__, stem.util.system.call(['python', '-c', "import sys;sys.path.insert(0, '%s');import nyx;print(nyx.__version__)" % site_packages_paths[0]])[0])
+
+      process_path = sys.path + ['/tmp/nyx_test/lib/python2.7/site-packages']
+      process = subprocess.Popen(['/tmp/nyx_test/bin/nyx', '--help'], stdout = subprocess.PIPE, env = {'PYTHONPATH': ':'.join(process_path)})
+      stdout = process.communicate()[0]
+
+      self.assertTrue(stdout.startswith('Usage nyx [OPTION]'))
+    finally:
+      shutil.rmtree('/tmp/nyx_test')
+      os.chdir(original_cwd)



More information about the tor-commits mailing list