[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