summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMonty Taylor <mordred@inaugust.com>2013-07-06 14:16:17 -0400
committerMonty Taylor <mordred@inaugust.com>2013-07-06 14:16:17 -0400
commit295dbe1da3315d2f21acc02f0fce83e96224041e (patch)
treeb8d0606e313bdada68d1fd35172aa2f37c4528f8
parentce11c732ac2f59a7cd252f9fd10bb4c7c025518f (diff)
parent712f639977d5c229e70a6d404bc5912e700a270d (diff)
downloadpbr-295dbe1da3315d2f21acc02f0fce83e96224041e.tar.gz
Merge d2to1 into the tree, complete with history.
-rw-r--r--pbr/d2to1/__init__.py4
-rw-r--r--pbr/d2to1/core.py82
-rw-r--r--pbr/d2to1/extern/__init__.py0
-rw-r--r--pbr/d2to1/extern/six.py386
-rw-r--r--pbr/d2to1/tests/__init__.py90
-rw-r--r--pbr/d2to1/tests/test_commands.py13
-rw-r--r--pbr/d2to1/tests/test_core.py48
-rw-r--r--pbr/d2to1/tests/test_hooks.py52
-rw-r--r--pbr/d2to1/tests/testpackage/CHANGES.txt86
-rw-r--r--pbr/d2to1/tests/testpackage/LICENSE.txt29
-rw-r--r--pbr/d2to1/tests/testpackage/MANIFEST.in1
-rw-r--r--pbr/d2to1/tests/testpackage/README.txt148
-rw-r--r--pbr/d2to1/tests/testpackage/d2to1_testpackage/__init__.py0
-rw-r--r--pbr/d2to1/tests/testpackage/d2to1_testpackage/_setup_hooks.py25
-rw-r--r--pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/1.txt0
-rw-r--r--pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/2.txt0
-rw-r--r--pbr/d2to1/tests/testpackage/data_files/a.txt0
-rw-r--r--pbr/d2to1/tests/testpackage/data_files/b.txt0
-rw-r--r--pbr/d2to1/tests/testpackage/data_files/c.rst0
-rw-r--r--pbr/d2to1/tests/testpackage/distribute_setup.py485
-rw-r--r--pbr/d2to1/tests/testpackage/extra-file.txt0
-rw-r--r--pbr/d2to1/tests/testpackage/setup.cfg46
-rwxr-xr-xpbr/d2to1/tests/testpackage/setup.py12
-rw-r--r--pbr/d2to1/tests/testpackage/src/testext.c28
-rw-r--r--pbr/d2to1/tests/util.py35
-rw-r--r--pbr/d2to1/util.py582
-rw-r--r--pbr/d2to1/zestreleaser.py159
27 files changed, 2311 insertions, 0 deletions
diff --git a/pbr/d2to1/__init__.py b/pbr/d2to1/__init__.py
new file mode 100644
index 0000000..4089e8f
--- /dev/null
+++ b/pbr/d2to1/__init__.py
@@ -0,0 +1,4 @@
+try:
+ __version__ = __import__('pkg_resources').get_distribution('d2to1').version
+except:
+ __version__ = ''
diff --git a/pbr/d2to1/core.py b/pbr/d2to1/core.py
new file mode 100644
index 0000000..1c72eae
--- /dev/null
+++ b/pbr/d2to1/core.py
@@ -0,0 +1,82 @@
+import os
+import sys
+import warnings
+
+from distutils.core import Distribution as _Distribution
+from distutils.errors import DistutilsFileError, DistutilsSetupError
+from setuptools.dist import _get_unpatched
+
+from .extern import six
+from .util import DefaultGetDict, IgnoreDict, cfg_to_args
+
+
+_Distribution = _get_unpatched(_Distribution)
+
+
+def d2to1(dist, attr, value):
+ """Implements the actual d2to1 setup() keyword. When used, this should be
+ the only keyword in your setup() aside from `setup_requires`.
+
+ If given as a string, the value of d2to1 is assumed to be the relative path
+ to the setup.cfg file to use. Otherwise, if it evaluates to true, it
+ simply assumes that d2to1 should be used, and the default 'setup.cfg' is
+ used.
+
+ This works by reading the setup.cfg file, parsing out the supported
+ metadata and command options, and using them to rebuild the
+ `DistributionMetadata` object and set the newly added command options.
+
+ The reason for doing things this way is that a custom `Distribution` class
+ will not play nicely with setup_requires; however, this implementation may
+ not work well with distributions that do use a `Distribution` subclass.
+ """
+
+ if not value:
+ return
+ if isinstance(value, six.string_types):
+ path = os.path.abspath(value)
+ else:
+ path = os.path.abspath('setup.cfg')
+ if not os.path.exists(path):
+ raise DistutilsFileError(
+ 'The setup.cfg file %s does not exist.' % path)
+
+ # Converts the setup.cfg file to setup() arguments
+ try:
+ attrs = cfg_to_args(path)
+ except:
+ e = sys.exc_info()[1]
+ raise DistutilsSetupError(
+ 'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))
+
+ # Repeat some of the Distribution initialization code with the newly
+ # provided attrs
+ if attrs:
+ # Skips 'options' and 'licence' support which are rarely used; may add
+ # back in later if demanded
+ for key, val in six.iteritems(attrs):
+ if hasattr(dist.metadata, 'set_' + key):
+ getattr(dist.metadata, 'set_' + key)(val)
+ elif hasattr(dist.metadata, key):
+ setattr(dist.metadata, key, val)
+ elif hasattr(dist, key):
+ setattr(dist, key, val)
+ else:
+ msg = 'Unknown distribution option: %s' % repr(key)
+ warnings.warn(msg)
+
+ # Re-finalize the underlying Distribution
+ _Distribution.finalize_options(dist)
+
+ # This bit comes out of distribute/setuptools
+ if isinstance(dist.metadata.version, six.integer_types + (float,)):
+ # Some people apparently take "version number" too literally :)
+ dist.metadata.version = str(dist.metadata.version)
+
+ # This bit of hackery is necessary so that the Distribution will ignore
+ # normally unsupport command options (namely pre-hooks and post-hooks).
+ # dist.command_options is normally a dict mapping command names to dicts of
+ # their options. Now it will be a defaultdict that returns IgnoreDicts for
+ # the each command's options so we can pass through the unsupported options
+ ignore = ['pre_hook.*', 'post_hook.*']
+ dist.command_options = DefaultGetDict(lambda: IgnoreDict(ignore))
diff --git a/pbr/d2to1/extern/__init__.py b/pbr/d2to1/extern/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/extern/__init__.py
diff --git a/pbr/d2to1/extern/six.py b/pbr/d2to1/extern/six.py
new file mode 100644
index 0000000..0cdd1c7
--- /dev/null
+++ b/pbr/d2to1/extern/six.py
@@ -0,0 +1,386 @@
+# Copyright (c) 2010-2011 Benjamin Peterson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+import operator
+import sys
+import types
+
+__author__ = "Benjamin Peterson <benjamin@python.org>"
+__version__ = "1.2.0"
+
+
+# True if we are running on Python 3.
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ string_types = str,
+ integer_types = int,
+ class_types = type,
+ text_type = str
+ binary_type = bytes
+
+ MAXSIZE = sys.maxsize
+else:
+ string_types = basestring,
+ integer_types = (int, long)
+ class_types = (type, types.ClassType)
+ text_type = unicode
+ binary_type = str
+
+ if sys.platform == "java":
+ # Jython always uses 32 bits.
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+ class X(object):
+ def __len__(self):
+ return 1 << 31
+ try:
+ len(X())
+ except OverflowError:
+ # 32-bit
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # 64-bit
+ MAXSIZE = int((1 << 63) - 1)
+ del X
+
+
+def _add_doc(func, doc):
+ """Add documentation to a function."""
+ func.__doc__ = doc
+
+
+def _import_module(name):
+ """Import module, returning the module after the last dot."""
+ __import__(name)
+ return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+ def __init__(self, name):
+ self.name = name
+
+ def __get__(self, obj, tp):
+ result = self._resolve()
+ setattr(obj, self.name, result)
+ # This is a bit ugly, but it avoids running this again.
+ delattr(tp, self.name)
+ return result
+
+
+class MovedModule(_LazyDescr):
+
+ def __init__(self, name, old, new=None):
+ super(MovedModule, self).__init__(name)
+ if PY3:
+ if new is None:
+ new = name
+ self.mod = new
+ else:
+ self.mod = old
+
+ def _resolve(self):
+ return _import_module(self.mod)
+
+
+class MovedAttribute(_LazyDescr):
+
+ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+ super(MovedAttribute, self).__init__(name)
+ if PY3:
+ if new_mod is None:
+ new_mod = name
+ self.mod = new_mod
+ if new_attr is None:
+ if old_attr is None:
+ new_attr = name
+ else:
+ new_attr = old_attr
+ self.attr = new_attr
+ else:
+ self.mod = old_mod
+ if old_attr is None:
+ old_attr = name
+ self.attr = old_attr
+
+ def _resolve(self):
+ module = _import_module(self.mod)
+ return getattr(module, self.attr)
+
+
+
+class _MovedItems(types.ModuleType):
+ """Lazy loading of moved objects"""
+
+
+_moved_attributes = [
+ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+ MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+ MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+ MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+ MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
+ MovedAttribute("reduce", "__builtin__", "functools"),
+ MovedAttribute("StringIO", "StringIO", "io"),
+ MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+ MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+
+ MovedModule("builtins", "__builtin__"),
+ MovedModule("configparser", "ConfigParser"),
+ MovedModule("copyreg", "copy_reg"),
+ MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+ MovedModule("http_cookies", "Cookie", "http.cookies"),
+ MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+ MovedModule("html_parser", "HTMLParser", "html.parser"),
+ MovedModule("http_client", "httplib", "http.client"),
+ MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+ MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+ MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+ MovedModule("cPickle", "cPickle", "pickle"),
+ MovedModule("queue", "Queue"),
+ MovedModule("reprlib", "repr"),
+ MovedModule("socketserver", "SocketServer"),
+ MovedModule("tkinter", "Tkinter"),
+ MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+ MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+ MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+ MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+ MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+ MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+ MovedModule("tkinter_colorchooser", "tkColorChooser",
+ "tkinter.colorchooser"),
+ MovedModule("tkinter_commondialog", "tkCommonDialog",
+ "tkinter.commondialog"),
+ MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+ MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+ MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+ "tkinter.simpledialog"),
+ MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+ MovedModule("winreg", "_winreg"),
+]
+for attr in _moved_attributes:
+ setattr(_MovedItems, attr.name, attr)
+del attr
+
+moves = sys.modules["six.moves"] = _MovedItems("moves")
+
+
+def add_move(move):
+ """Add an item to six.moves."""
+ setattr(_MovedItems, move.name, move)
+
+
+def remove_move(name):
+ """Remove item from six.moves."""
+ try:
+ delattr(_MovedItems, name)
+ except AttributeError:
+ try:
+ del moves.__dict__[name]
+ except KeyError:
+ raise AttributeError("no such move, %r" % (name,))
+
+
+if PY3:
+ _meth_func = "__func__"
+ _meth_self = "__self__"
+
+ _func_code = "__code__"
+ _func_defaults = "__defaults__"
+
+ _iterkeys = "keys"
+ _itervalues = "values"
+ _iteritems = "items"
+else:
+ _meth_func = "im_func"
+ _meth_self = "im_self"
+
+ _func_code = "func_code"
+ _func_defaults = "func_defaults"
+
+ _iterkeys = "iterkeys"
+ _itervalues = "itervalues"
+ _iteritems = "iteritems"
+
+
+try:
+ advance_iterator = next
+except NameError:
+ def advance_iterator(it):
+ return it.next()
+next = advance_iterator
+
+
+if PY3:
+ def get_unbound_function(unbound):
+ return unbound
+
+ Iterator = object
+
+ def callable(obj):
+ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+else:
+ def get_unbound_function(unbound):
+ return unbound.im_func
+
+ class Iterator(object):
+
+ def next(self):
+ return type(self).__next__(self)
+
+ callable = callable
+_add_doc(get_unbound_function,
+ """Get the function out of a possibly unbound function""")
+
+
+get_method_function = operator.attrgetter(_meth_func)
+get_method_self = operator.attrgetter(_meth_self)
+get_function_code = operator.attrgetter(_func_code)
+get_function_defaults = operator.attrgetter(_func_defaults)
+
+
+def iterkeys(d):
+ """Return an iterator over the keys of a dictionary."""
+ return iter(getattr(d, _iterkeys)())
+
+def itervalues(d):
+ """Return an iterator over the values of a dictionary."""
+ return iter(getattr(d, _itervalues)())
+
+def iteritems(d):
+ """Return an iterator over the (key, value) pairs of a dictionary."""
+ return iter(getattr(d, _iteritems)())
+
+
+if PY3:
+ def b(s):
+ return s.encode("latin-1")
+ def u(s):
+ return s
+ if sys.version_info[1] <= 1:
+ def int2byte(i):
+ return bytes((i,))
+ else:
+ # This is about 2x faster than the implementation above on 3.2+
+ int2byte = operator.methodcaller("to_bytes", 1, "big")
+ import io
+ StringIO = io.StringIO
+ BytesIO = io.BytesIO
+else:
+ def b(s):
+ return s
+ def u(s):
+ return unicode(s, "unicode_escape")
+ int2byte = chr
+ import StringIO
+ StringIO = BytesIO = StringIO.StringIO
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+if PY3:
+ import builtins
+ exec_ = getattr(builtins, "exec")
+
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+
+
+ print_ = getattr(builtins, "print")
+ del builtins
+
+else:
+ def exec_(code, globs=None, locs=None):
+ """Execute code in a namespace."""
+ if globs is None:
+ frame = sys._getframe(1)
+ globs = frame.f_globals
+ if locs is None:
+ locs = frame.f_locals
+ del frame
+ elif locs is None:
+ locs = globs
+ exec("""exec code in globs, locs""")
+
+
+ exec_("""def reraise(tp, value, tb=None):
+ raise tp, value, tb
+""")
+
+
+ def print_(*args, **kwargs):
+ """The new-style print function."""
+ fp = kwargs.pop("file", sys.stdout)
+ if fp is None:
+ return
+ def write(data):
+ if not isinstance(data, basestring):
+ data = str(data)
+ fp.write(data)
+ want_unicode = False
+ sep = kwargs.pop("sep", None)
+ if sep is not None:
+ if isinstance(sep, unicode):
+ want_unicode = True
+ elif not isinstance(sep, str):
+ raise TypeError("sep must be None or a string")
+ end = kwargs.pop("end", None)
+ if end is not None:
+ if isinstance(end, unicode):
+ want_unicode = True
+ elif not isinstance(end, str):
+ raise TypeError("end must be None or a string")
+ if kwargs:
+ raise TypeError("invalid keyword arguments to print()")
+ if not want_unicode:
+ for arg in args:
+ if isinstance(arg, unicode):
+ want_unicode = True
+ break
+ if want_unicode:
+ newline = unicode("\n")
+ space = unicode(" ")
+ else:
+ newline = "\n"
+ space = " "
+ if sep is None:
+ sep = space
+ if end is None:
+ end = newline
+ for i, arg in enumerate(args):
+ if i:
+ write(sep)
+ write(arg)
+ write(end)
+
+_add_doc(reraise, """Reraise an exception.""")
+
+
+def with_metaclass(meta, base=object):
+ """Create a base class with a metaclass."""
+ return meta("NewBase", (base,), {})
diff --git a/pbr/d2to1/tests/__init__.py b/pbr/d2to1/tests/__init__.py
new file mode 100644
index 0000000..6146af2
--- /dev/null
+++ b/pbr/d2to1/tests/__init__.py
@@ -0,0 +1,90 @@
+from __future__ import with_statement
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import pkg_resources
+
+from .util import rmtree, open_config
+
+
+D2TO1_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir))
+
+
+def fake_d2to1_dist():
+ # Fake a d2to1 distribution from the d2to1 package that these tests reside
+ # in and make sure it's active on the path with the appropriate entry
+ # points installed
+
+ class _FakeProvider(pkg_resources.EmptyProvider):
+ """A fake metadata provider that does almost nothing except to return
+ entry point metadata.
+ """
+
+ def has_metadata(self, name):
+ return name == 'entry_points.txt'
+
+ def get_metadata(self, name):
+ if name == 'entry_points.txt':
+ return '[distutils.setup_keywords]\nd2to1 = d2to1.core:d2to1\n'
+ else:
+ return ''
+
+
+ sys.path.insert(0, D2TO1_DIR)
+ if 'd2to1' in sys.modules:
+ del sys.modules['d2to1']
+ if 'd2to1' in pkg_resources.working_set.by_key:
+ del pkg_resources.working_set.by_key['d2to1']
+ dist = pkg_resources.Distribution(location=D2TO1_DIR, project_name='d2to1',
+ metadata=_FakeProvider())
+ pkg_resources.working_set.add(dist)
+
+
+class D2to1TestCase(object):
+ def setup(self):
+ self.temp_dir = tempfile.mkdtemp(prefix='d2to1-test-')
+ self.package_dir = os.path.join(self.temp_dir, 'testpackage')
+ shutil.copytree(os.path.join(os.path.dirname(__file__), 'testpackage'),
+ self.package_dir)
+ self.oldcwd = os.getcwd()
+ os.chdir(self.package_dir)
+
+ def teardown(self):
+ os.chdir(self.oldcwd)
+ # Remove d2to1.testpackage from sys.modules so that it can be freshly
+ # re-imported by the next test
+ for k in list(sys.modules):
+ if (k == 'd2to1_testpackage' or
+ k.startswith('d2to1_testpackage.')):
+ del sys.modules[k]
+ rmtree(self.temp_dir)
+
+ def run_setup(self, *args):
+ cmd = ('-c',
+ 'import sys;sys.path.insert(0, %r);'
+ 'from d2to1.tests import fake_d2to1_dist;'
+ 'from d2to1.extern.six import exec_;'
+ 'fake_d2to1_dist();exec_(open("setup.py").read())' % D2TO1_DIR)
+ return self._run_cmd(sys.executable, cmd + args)
+
+ def run_svn(self, *args):
+ return self._run_cmd('svn', args)
+
+ def _run_cmd(self, cmd, args):
+ """
+ Runs a command, with the given argument list, in the root of the test
+ working copy--returns the stdout and stderr streams and the exit code
+ from the subprocess.
+ """
+
+ os.chdir(self.package_dir)
+ p = subprocess.Popen([cmd] + list(args), stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ streams = tuple(s.decode('latin1').strip() for s in p.communicate())
+ print(streams)
+ return (streams) + (p.returncode,)
diff --git a/pbr/d2to1/tests/test_commands.py b/pbr/d2to1/tests/test_commands.py
new file mode 100644
index 0000000..29342da
--- /dev/null
+++ b/pbr/d2to1/tests/test_commands.py
@@ -0,0 +1,13 @@
+from . import D2to1TestCase
+
+
+class TestCommands(D2to1TestCase):
+ def test_custom_build_py_command(self):
+ """
+ Test that a custom subclass of the build_py command runs when listed in
+ the commands [global] option, rather than the normal build command.
+ """
+
+ stdout, _, return_code = self.run_setup('build_py')
+ assert 'Running custom build_py command.' in stdout
+ assert return_code == 0
diff --git a/pbr/d2to1/tests/test_core.py b/pbr/d2to1/tests/test_core.py
new file mode 100644
index 0000000..d7962f4
--- /dev/null
+++ b/pbr/d2to1/tests/test_core.py
@@ -0,0 +1,48 @@
+import glob
+import os
+import tarfile
+
+from . import D2to1TestCase
+
+
+VERSION = '0.1.dev'
+
+
+class TestCore(D2to1TestCase):
+ def test_setup_py_version(self):
+ """
+ Test that the `./setup.py --version` command returns the correct
+ value without balking.
+ """
+
+ self.run_setup('egg_info')
+ stdout, _, _ = self.run_setup('--version')
+ assert stdout == VERSION
+
+ def test_setup_py_keywords(self):
+ """
+ Test that the `./setup.py --keywords` command returns the correct
+ value without balking.
+ """
+
+ self.run_setup('egg_info')
+ stdout, _, _ = self.run_setup('--keywords')
+ assert stdout == 'packaging,distutils,setuptools'
+
+ def test_sdist_extra_files(self):
+ """
+ Test that the extra files are correctly added.
+ """
+
+ stdout, _, return_code = self.run_setup('sdist', '--formats=gztar')
+
+ # There can be only one
+ try:
+ tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0]
+ except IndexError:
+ assert False, 'source dist not found'
+
+ tf = tarfile.open(tf_path)
+ names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()]
+
+ assert 'extra-file.txt' in names
diff --git a/pbr/d2to1/tests/test_hooks.py b/pbr/d2to1/tests/test_hooks.py
new file mode 100644
index 0000000..047e0b9
--- /dev/null
+++ b/pbr/d2to1/tests/test_hooks.py
@@ -0,0 +1,52 @@
+from __future__ import with_statement
+
+import os
+import textwrap
+
+from . import D2to1TestCase
+from .util import open_config
+
+
+class TestHooks(D2to1TestCase):
+ def setup(self):
+ super(TestHooks, self).setup()
+ with open_config(os.path.join(self.package_dir, 'setup.cfg')) as cfg:
+ cfg.set('global', 'setup-hooks',
+ 'd2to1_testpackage._setup_hooks.test_hook_1\n'
+ 'd2to1_testpackage._setup_hooks.test_hook_2')
+ cfg.set('build_ext', 'pre-hook.test_pre_hook',
+ 'd2to1_testpackage._setup_hooks.test_pre_hook')
+ cfg.set('build_ext', 'post-hook.test_post_hook',
+ 'd2to1_testpackage._setup_hooks.test_post_hook')
+
+ def test_global_setup_hooks(self):
+ """
+ Test that setup_hooks listed in the [global] section of setup.cfg are
+ executed in order.
+ """
+
+ stdout, _, return_code = self.run_setup('egg_info')
+ assert 'test_hook_1\ntest_hook_2' in stdout
+ assert return_code == 0
+
+ def test_command_hooks(self):
+ """
+ Simple test that the appropriate command hooks run at the
+ beginning/end of the appropriate command.
+ """
+
+ stdout, _, return_code = self.run_setup('egg_info')
+ assert 'build_ext pre-hook' not in stdout
+ assert 'build_ext post-hook' not in stdout
+ assert return_code == 0
+
+ stdout, _, return_code = self.run_setup('build_ext')
+ assert textwrap.dedent("""
+ running build_ext
+ running pre_hook d2to1_testpackage._setup_hooks.test_pre_hook for command build_ext
+ build_ext pre-hook
+ """) in stdout
+ assert stdout.endswith('build_ext post-hook')
+ assert return_code == 0
+
+
diff --git a/pbr/d2to1/tests/testpackage/CHANGES.txt b/pbr/d2to1/tests/testpackage/CHANGES.txt
new file mode 100644
index 0000000..709b9d4
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/CHANGES.txt
@@ -0,0 +1,86 @@
+Changelog
+===========
+
+0.3 (unreleased)
+------------------
+
+- The ``glob_data_files`` hook became a pre-command hook for the install_data
+ command instead of being a setup-hook. This is to support the additional
+ functionality of requiring data_files with relative destination paths to be
+ install relative to the package's install path (i.e. site-packages).
+
+- Dropped support for and deprecated the easier_install custom command.
+ Although it should still work, it probably won't be used anymore for
+ stsci_python packages.
+
+- Added support for the ``build_optional_ext`` command, which replaces/extends
+ the default ``build_ext`` command. See the README for more details.
+
+- Added the ``tag_svn_revision`` setup_hook as a replacement for the
+ setuptools-specific tag_svn_revision option to the egg_info command. This
+ new hook is easier to use than the old tag_svn_revision option: It's
+ automatically enabled by the presence of ``.dev`` in the version string, and
+ disabled otherwise.
+
+- The ``svn_info_pre_hook`` and ``svn_info_post_hook`` have been replaced with
+ ``version_pre_command_hook`` and ``version_post_command_hook`` respectively.
+ However, a new ``version_setup_hook``, which has the same purpose, has been
+ added. It is generally easier to use and will give more consistent results
+ in that it will run every time setup.py is run, regardless of which command
+ is used. ``stsci.distutils`` itself uses this hook--see the `setup.cfg` file
+ and `stsci/distutils/__init__.py` for example usage.
+
+- Instead of creating an `svninfo.py` module, the new ``version_`` hooks create
+ a file called `version.py`. In addition to the SVN info that was included
+ in `svninfo.py`, it includes a ``__version__`` variable to be used by the
+ package's `__init__.py`. This allows there to be a hard-coded
+ ``__version__`` variable included in the source code, rather than using
+ pkg_resources to get the version.
+
+- In `version.py`, the variables previously named ``__svn_version__`` and
+ ``__full_svn_info__`` are now named ``__svn_revision__`` and
+ ``__svn_full_info__``.
+
+- Fixed a bug when using stsci.distutils in the installation of other packages
+ in the ``stsci.*`` namespace package. If stsci.distutils was not already
+ installed, and was downloaded automatically by distribute through the
+ setup_requires option, then ``stsci.distutils`` would fail to import. This
+ is because the way the namespace package (nspkg) mechanism currently works,
+ all packages belonging to the nspkg *must* be on the import path at initial
+ import time.
+
+ So when installing stsci.tools, for example, if ``stsci.tools`` is imported
+ from within the source code at install time, but before ``stsci.distutils``
+ is downloaded and added to the path, the ``stsci`` package is already
+ imported and can't be extended to include the path of ``stsci.distutils``
+ after the fact. The easiest way of dealing with this, it seems, is to
+ delete ``stsci`` from ``sys.modules``, which forces it to be reimported, now
+ the its ``__path__`` extended to include ``stsci.distutil``'s path.
+
+
+0.2.2 (2011-11-09)
+------------------
+
+- Fixed check for the issue205 bug on actual setuptools installs; before it
+ only worked on distribute. setuptools has the issue205 bug prior to version
+ 0.6c10.
+
+- Improved the fix for the issue205 bug, especially on setuptools.
+ setuptools, prior to 0.6c10, did not back of sys.modules either before
+ sandboxing, which causes serious problems. In fact, it's so bad that it's
+ not enough to add a sys.modules backup to the current sandbox: It's in fact
+ necessary to monkeypatch setuptools.sandbox.run_setup so that any subsequent
+ calls to it also back up sys.modules.
+
+
+0.2.1 (2011-09-02)
+------------------
+
+- Fixed the dependencies so that setuptools is requirement but 'distribute'
+ specifically. Previously installation could fail if users had plain
+ setuptools installed and not distribute
+
+0.2 (2011-08-23)
+------------------
+
+- Initial public release
diff --git a/pbr/d2to1/tests/testpackage/LICENSE.txt b/pbr/d2to1/tests/testpackage/LICENSE.txt
new file mode 100644
index 0000000..7e8019a
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/LICENSE.txt
@@ -0,0 +1,29 @@
+Copyright (C) 2005 Association of Universities for Research in Astronomy (AURA)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ 3. The name of AURA and its representatives may not be used to
+ endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
diff --git a/pbr/d2to1/tests/testpackage/MANIFEST.in b/pbr/d2to1/tests/testpackage/MANIFEST.in
new file mode 100644
index 0000000..cdc95ea
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/MANIFEST.in
@@ -0,0 +1 @@
+include data_files/*
diff --git a/pbr/d2to1/tests/testpackage/README.txt b/pbr/d2to1/tests/testpackage/README.txt
new file mode 100644
index 0000000..4f00d32
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/README.txt
@@ -0,0 +1,148 @@
+Introduction
+============
+This package contains utilities used to package some of STScI's Python
+projects; specifically those projects that comprise stsci_python_ and
+Astrolib_.
+
+It currently consists mostly of some setup_hook scripts meant for use with
+`distutils2/packaging`_ and/or d2to1_, and a customized easy_install command
+meant for use with distribute_.
+
+This package is not meant for general consumption, though it might be worth
+looking at for examples of how to do certain things with your own packages, but
+YMMV.
+
+Features
+========
+
+Hook Scripts
+------------
+Currently the main features of this package are a couple of setup_hook scripts.
+In distutils2, a setup_hook is a script that runs at the beginning of any
+pysetup command, and can modify the package configuration read from setup.cfg.
+There are also pre- and post-command hooks that only run before/after a
+specific setup command (eg. build_ext, install) is run.
+
+stsci.distutils.hooks.use_packages_root
+'''''''''''''''''''''''''''''''''''''''
+If using the ``packages_root`` option under the ``[files]`` section of
+setup.cfg, this hook will add that path to ``sys.path`` so that modules in your
+package can be imported and used in setup. This can be used even if
+``packages_root`` is not specified--in this case it adds ``''`` to
+``sys.path``.
+
+stsci.distutils.hooks.version_setup_hook
+''''''''''''''''''''''''''''''''''''''''
+Creates a Python module called version.py which currently contains four
+variables:
+
+* ``__version__`` (the release version)
+* ``__svn_revision__`` (the SVN revision info as returned by the ``svnversion``
+ command)
+* ``__svn_full_info__`` (as returned by the ``svn info`` command)
+* ``__setup_datetime__`` (the date and time that setup.py was last run).
+
+These variables can be imported in the package's `__init__.py` for degugging
+purposes. The version.py module will *only* be created in a package that
+imports from the version module in its `__init__.py`. It should be noted that
+this is generally preferable to writing these variables directly into
+`__init__.py`, since this provides more control and is less likely to
+unexpectedly break things in `__init__.py`.
+
+stsci.distutils.hooks.version_pre_command_hook
+''''''''''''''''''''''''''''''''''''''''''''''
+Identical to version_setup_hook, but designed to be used as a pre-command
+hook.
+
+stsci.distutils.hooks.version_post_command_hook
+'''''''''''''''''''''''''''''''''''''''''''''''
+The complement to version_pre_command_hook. This will delete any version.py
+files created during a build in order to prevent them from cluttering an SVN
+working copy (note, however, that version.py is *not* deleted from the build/
+directory, so a copy of it is still preserved). It will also not be deleted
+if the current directory is not an SVN working copy. For example, if source
+code extracted from a source tarball it will be preserved.
+
+stsci.distutils.hooks.tag_svn_revision
+''''''''''''''''''''''''''''''''''''''
+A setup_hook to add the SVN revision of the current working copy path to the
+package version string, but only if the version ends in .dev.
+
+For example, ``mypackage-1.0.dev`` becomes ``mypackage-1.0.dev1234``. This is
+in accordance with the version string format standardized by PEP 386.
+
+This should be used as a replacement for the ``tag_svn_revision`` option to
+the egg_info command. This hook is more compatible with packaging/distutils2,
+which does not include any VCS support. This hook is also more flexible in
+that it turns the revision number on/off depending on the presence of ``.dev``
+in the version string, so that it's not automatically added to the version in
+final releases.
+
+This hook does require the ``svnversion`` command to be available in order to
+work. It does not examine the working copy metadata directly.
+
+stsci.distutils.hooks.numpy_extension_hook
+''''''''''''''''''''''''''''''''''''''''''
+This is a pre-command hook for the build_ext command. To use it, add a
+``[build_ext]`` section to your setup.cfg, and add to it::
+
+ pre-hook.numpy-extension-hook = stsci.distutils.hooks.numpy_extension_hook
+
+This hook must be used to build extension modules that use Numpy. The primary
+side-effect of this hook is to add the correct numpy include directories to
+`include_dirs`. To use it, add 'numpy' to the 'include-dirs' option of each
+extension module that requires numpy to build. The value 'numpy' will be
+replaced with the actual path to the numpy includes.
+
+stsci.distutils.hooks.is_display_option
+'''''''''''''''''''''''''''''''''''''''
+This is not actually a hook, but is a useful utility function that can be used
+in writing other hooks. Basically, it returns ``True`` if setup.py was run
+with a "display option" such as --version or --help. This can be used to
+prevent your hook from running in such cases.
+
+stsci.distutils.hooks.glob_data_files
+'''''''''''''''''''''''''''''''''''''
+A pre-command hook for the install_data command. Allows filename wildcards as
+understood by ``glob.glob()`` to be used in the data_files option. This hook
+must be used in order to have this functionality since it does not normally
+exist in distutils.
+
+This hook also ensures that data files are installed relative to the package
+path. data_files shouldn't normally be installed this way, but the
+functionality is required for a few special cases.
+
+
+Commands
+--------
+build_optional_ext
+''''''''''''''''''
+This serves as an optional replacement for the default built_ext command,
+which compiles C extension modules. Its purpose is to allow extension modules
+to be *optional*, so that if their build fails the rest of the package is
+still allowed to be built and installed. This can be used when an extension
+module is not definitely required to use the package.
+
+To use this custom command, add::
+
+ commands = stsci.distutils.command.build_optional_ext.build_optional_ext
+
+under the ``[global]`` section of your package's setup.cfg. Then, to mark
+an individual extension module as optional, under the setup.cfg section for
+that extension add::
+
+ optional = True
+
+Optionally, you may also add a custom failure message by adding::
+
+ fail_message = The foobar extension module failed to compile.
+ This could be because you lack such and such headers.
+ This package will still work, but such and such features
+ will be disabled.
+
+
+.. _stsci_python: http://www.stsci.edu/resources/software_hardware/pyraf/stsci_python
+.. _Astrolib: http://www.scipy.org/AstroLib/
+.. _distutils2/packaging: http://distutils2.notmyidea.org/
+.. _d2to1: http://pypi.python.org/pypi/d2to1
+.. _distribute: http://pypi.python.org/pypi/distribute
diff --git a/pbr/d2to1/tests/testpackage/d2to1_testpackage/__init__.py b/pbr/d2to1/tests/testpackage/d2to1_testpackage/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/d2to1_testpackage/__init__.py
diff --git a/pbr/d2to1/tests/testpackage/d2to1_testpackage/_setup_hooks.py b/pbr/d2to1/tests/testpackage/d2to1_testpackage/_setup_hooks.py
new file mode 100644
index 0000000..77005b2
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/d2to1_testpackage/_setup_hooks.py
@@ -0,0 +1,25 @@
+from distutils.command.build_py import build_py
+
+
+def test_hook_1(config):
+ print('test_hook_1')
+
+
+def test_hook_2(config):
+ print('test_hook_2')
+
+
+class test_command(build_py):
+ command_name = 'build_py'
+
+ def run(self):
+ print('Running custom build_py command.')
+ return build_py.run(self)
+
+
+def test_pre_hook(cmdobj):
+ print('build_ext pre-hook')
+
+
+def test_post_hook(cmdobj):
+ print('build_ext post-hook')
diff --git a/pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/1.txt b/pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/1.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/1.txt
diff --git a/pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/2.txt b/pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/2.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/d2to1_testpackage/package_data/2.txt
diff --git a/pbr/d2to1/tests/testpackage/data_files/a.txt b/pbr/d2to1/tests/testpackage/data_files/a.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/data_files/a.txt
diff --git a/pbr/d2to1/tests/testpackage/data_files/b.txt b/pbr/d2to1/tests/testpackage/data_files/b.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/data_files/b.txt
diff --git a/pbr/d2to1/tests/testpackage/data_files/c.rst b/pbr/d2to1/tests/testpackage/data_files/c.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/data_files/c.rst
diff --git a/pbr/d2to1/tests/testpackage/distribute_setup.py b/pbr/d2to1/tests/testpackage/distribute_setup.py
new file mode 100644
index 0000000..bbb6f3c
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/distribute_setup.py
@@ -0,0 +1,485 @@
+#!python
+"""Bootstrap distribute installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from distribute_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import os
+import sys
+import time
+import fnmatch
+import tempfile
+import tarfile
+from distutils import log
+
+try:
+ from site import USER_SITE
+except ImportError:
+ USER_SITE = None
+
+try:
+ import subprocess
+
+ def _python_cmd(*args):
+ args = (sys.executable,) + args
+ return subprocess.call(args) == 0
+
+except ImportError:
+ # will be used for python 2.3
+ def _python_cmd(*args):
+ args = (sys.executable,) + args
+ # quoting arguments if windows
+ if sys.platform == 'win32':
+ def quote(arg):
+ if ' ' in arg:
+ return '"%s"' % arg
+ return arg
+ args = [quote(arg) for arg in args]
+ return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
+
+DEFAULT_VERSION = "0.6.19"
+DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
+SETUPTOOLS_FAKED_VERSION = "0.6c11"
+
+SETUPTOOLS_PKG_INFO = """\
+Metadata-Version: 1.0
+Name: setuptools
+Version: %s
+Summary: xxxx
+Home-page: xxx
+Author: xxx
+Author-email: xxx
+License: xxx
+Description: xxx
+""" % SETUPTOOLS_FAKED_VERSION
+
+
+def _install(tarball):
+ # extracting the tarball
+ tmpdir = tempfile.mkdtemp()
+ log.warn('Extracting in %s', tmpdir)
+ old_wd = os.getcwd()
+ try:
+ os.chdir(tmpdir)
+ tar = tarfile.open(tarball)
+ _extractall(tar)
+ tar.close()
+
+ # going in the directory
+ subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ os.chdir(subdir)
+ log.warn('Now working in %s', subdir)
+
+ # installing
+ log.warn('Installing Distribute')
+ if not _python_cmd('setup.py', 'install'):
+ log.warn('Something went wrong during the installation.')
+ log.warn('See the error message above.')
+ finally:
+ os.chdir(old_wd)
+
+
+def _build_egg(egg, tarball, to_dir):
+ # extracting the tarball
+ tmpdir = tempfile.mkdtemp()
+ log.warn('Extracting in %s', tmpdir)
+ old_wd = os.getcwd()
+ try:
+ os.chdir(tmpdir)
+ tar = tarfile.open(tarball)
+ _extractall(tar)
+ tar.close()
+
+ # going in the directory
+ subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ os.chdir(subdir)
+ log.warn('Now working in %s', subdir)
+
+ # building an egg
+ log.warn('Building a Distribute egg in %s', to_dir)
+ _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
+
+ finally:
+ os.chdir(old_wd)
+ # returning the result
+ log.warn(egg)
+ if not os.path.exists(egg):
+ raise IOError('Could not build the egg.')
+
+
+def _do_download(version, download_base, to_dir, download_delay):
+ egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
+ % (version, sys.version_info[0], sys.version_info[1]))
+ if not os.path.exists(egg):
+ tarball = download_setuptools(version, download_base,
+ to_dir, download_delay)
+ _build_egg(egg, tarball, to_dir)
+ sys.path.insert(0, egg)
+ import setuptools
+ setuptools.bootstrap_install_from = egg
+
+
+def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=os.curdir, download_delay=15, no_fake=True):
+ # making sure we use the absolute path
+ to_dir = os.path.abspath(to_dir)
+ was_imported = 'pkg_resources' in sys.modules or \
+ 'setuptools' in sys.modules
+ try:
+ try:
+ import pkg_resources
+ if not hasattr(pkg_resources, '_distribute'):
+ if not no_fake:
+ _fake_setuptools()
+ raise ImportError
+ except ImportError:
+ return _do_download(version, download_base, to_dir, download_delay)
+ try:
+ pkg_resources.require("distribute>="+version)
+ return
+ except pkg_resources.VersionConflict:
+ e = sys.exc_info()[1]
+ if was_imported:
+ sys.stderr.write(
+ "The required version of distribute (>=%s) is not available,\n"
+ "and can't be installed while this script is running. Please\n"
+ "install a more recent version first, using\n"
+ "'easy_install -U distribute'."
+ "\n\n(Currently using %r)\n" % (version, e.args[0]))
+ sys.exit(2)
+ else:
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return _do_download(version, download_base, to_dir,
+ download_delay)
+ except pkg_resources.DistributionNotFound:
+ return _do_download(version, download_base, to_dir,
+ download_delay)
+ finally:
+ if not no_fake:
+ _create_fake_setuptools_pkg_info(to_dir)
+
+def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=os.curdir, delay=15):
+ """Download distribute from a specified location and return its filename
+
+ `version` should be a valid distribute version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download
+ attempt.
+ """
+ # making sure we use the absolute path
+ to_dir = os.path.abspath(to_dir)
+ try:
+ from urllib.request import urlopen
+ except ImportError:
+ from urllib2 import urlopen
+ tgz_name = "distribute-%s.tar.gz" % version
+ url = download_base + tgz_name
+ saveto = os.path.join(to_dir, tgz_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ log.warn("Downloading %s", url)
+ src = urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = src.read()
+ dst = open(saveto, "wb")
+ dst.write(data)
+ finally:
+ if src:
+ src.close()
+ if dst:
+ dst.close()
+ return os.path.realpath(saveto)
+
+def _no_sandbox(function):
+ def __no_sandbox(*args, **kw):
+ try:
+ from setuptools.sandbox import DirectorySandbox
+ if not hasattr(DirectorySandbox, '_old'):
+ def violation(*args):
+ pass
+ DirectorySandbox._old = DirectorySandbox._violation
+ DirectorySandbox._violation = violation
+ patched = True
+ else:
+ patched = False
+ except ImportError:
+ patched = False
+
+ try:
+ return function(*args, **kw)
+ finally:
+ if patched:
+ DirectorySandbox._violation = DirectorySandbox._old
+ del DirectorySandbox._old
+
+ return __no_sandbox
+
+def _patch_file(path, content):
+ """Will backup the file then patch it"""
+ existing_content = open(path).read()
+ if existing_content == content:
+ # already patched
+ log.warn('Already patched.')
+ return False
+ log.warn('Patching...')
+ _rename_path(path)
+ f = open(path, 'w')
+ try:
+ f.write(content)
+ finally:
+ f.close()
+ return True
+
+_patch_file = _no_sandbox(_patch_file)
+
+def _same_content(path, content):
+ return open(path).read() == content
+
+def _rename_path(path):
+ new_name = path + '.OLD.%s' % time.time()
+ log.warn('Renaming %s into %s', path, new_name)
+ os.rename(path, new_name)
+ return new_name
+
+def _remove_flat_installation(placeholder):
+ if not os.path.isdir(placeholder):
+ log.warn('Unkown installation at %s', placeholder)
+ return False
+ found = False
+ for file in os.listdir(placeholder):
+ if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
+ found = True
+ break
+ if not found:
+ log.warn('Could not locate setuptools*.egg-info')
+ return
+
+ log.warn('Removing elements out of the way...')
+ pkg_info = os.path.join(placeholder, file)
+ if os.path.isdir(pkg_info):
+ patched = _patch_egg_dir(pkg_info)
+ else:
+ patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
+
+ if not patched:
+ log.warn('%s already patched.', pkg_info)
+ return False
+ # now let's move the files out of the way
+ for element in ('setuptools', 'pkg_resources.py', 'site.py'):
+ element = os.path.join(placeholder, element)
+ if os.path.exists(element):
+ _rename_path(element)
+ else:
+ log.warn('Could not find the %s element of the '
+ 'Setuptools distribution', element)
+ return True
+
+_remove_flat_installation = _no_sandbox(_remove_flat_installation)
+
+def _after_install(dist):
+ log.warn('After install bootstrap.')
+ placeholder = dist.get_command_obj('install').install_purelib
+ _create_fake_setuptools_pkg_info(placeholder)
+
+def _create_fake_setuptools_pkg_info(placeholder):
+ if not placeholder or not os.path.exists(placeholder):
+ log.warn('Could not find the install location')
+ return
+ pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
+ setuptools_file = 'setuptools-%s-py%s.egg-info' % \
+ (SETUPTOOLS_FAKED_VERSION, pyver)
+ pkg_info = os.path.join(placeholder, setuptools_file)
+ if os.path.exists(pkg_info):
+ log.warn('%s already exists', pkg_info)
+ return
+
+ log.warn('Creating %s', pkg_info)
+ f = open(pkg_info, 'w')
+ try:
+ f.write(SETUPTOOLS_PKG_INFO)
+ finally:
+ f.close()
+
+ pth_file = os.path.join(placeholder, 'setuptools.pth')
+ log.warn('Creating %s', pth_file)
+ f = open(pth_file, 'w')
+ try:
+ f.write(os.path.join(os.curdir, setuptools_file))
+ finally:
+ f.close()
+
+_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
+
+def _patch_egg_dir(path):
+ # let's check if it's already patched
+ pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ if os.path.exists(pkg_info):
+ if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
+ log.warn('%s already patched.', pkg_info)
+ return False
+ _rename_path(path)
+ os.mkdir(path)
+ os.mkdir(os.path.join(path, 'EGG-INFO'))
+ pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ f = open(pkg_info, 'w')
+ try:
+ f.write(SETUPTOOLS_PKG_INFO)
+ finally:
+ f.close()
+ return True
+
+_patch_egg_dir = _no_sandbox(_patch_egg_dir)
+
+def _before_install():
+ log.warn('Before install bootstrap.')
+ _fake_setuptools()
+
+
+def _under_prefix(location):
+ if 'install' not in sys.argv:
+ return True
+ args = sys.argv[sys.argv.index('install')+1:]
+ for index, arg in enumerate(args):
+ for option in ('--root', '--prefix'):
+ if arg.startswith('%s=' % option):
+ top_dir = arg.split('root=')[-1]
+ return location.startswith(top_dir)
+ elif arg == option:
+ if len(args) > index:
+ top_dir = args[index+1]
+ return location.startswith(top_dir)
+ if arg == '--user' and USER_SITE is not None:
+ return location.startswith(USER_SITE)
+ return True
+
+
+def _fake_setuptools():
+ log.warn('Scanning installed packages')
+ try:
+ import pkg_resources
+ except ImportError:
+ # we're cool
+ log.warn('Setuptools or Distribute does not seem to be installed.')
+ return
+ ws = pkg_resources.working_set
+ try:
+ setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
+ replacement=False))
+ except TypeError:
+ # old distribute API
+ setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
+
+ if setuptools_dist is None:
+ log.warn('No setuptools distribution found')
+ return
+ # detecting if it was already faked
+ setuptools_location = setuptools_dist.location
+ log.warn('Setuptools installation detected at %s', setuptools_location)
+
+ # if --root or --preix was provided, and if
+ # setuptools is not located in them, we don't patch it
+ if not _under_prefix(setuptools_location):
+ log.warn('Not patching, --root or --prefix is installing Distribute'
+ ' in another location')
+ return
+
+ # let's see if its an egg
+ if not setuptools_location.endswith('.egg'):
+ log.warn('Non-egg installation')
+ res = _remove_flat_installation(setuptools_location)
+ if not res:
+ return
+ else:
+ log.warn('Egg installation')
+ pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
+ if (os.path.exists(pkg_info) and
+ _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
+ log.warn('Already patched.')
+ return
+ log.warn('Patching...')
+ # let's create a fake egg replacing setuptools one
+ res = _patch_egg_dir(setuptools_location)
+ if not res:
+ return
+ log.warn('Patched done.')
+ _relaunch()
+
+
+def _relaunch():
+ log.warn('Relaunching...')
+ # we have to relaunch the process
+ # pip marker to avoid a relaunch bug
+ if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
+ sys.argv[0] = 'setup.py'
+ args = [sys.executable] + sys.argv
+ sys.exit(subprocess.call(args))
+
+
+def _extractall(self, path=".", members=None):
+ """Extract all members from the archive to the current working
+ directory and set owner, modification time and permissions on
+ directories afterwards. `path' specifies a different directory
+ to extract to. `members' is optional and must be a subset of the
+ list returned by getmembers().
+ """
+ import copy
+ import operator
+ from tarfile import ExtractError
+ directories = []
+
+ if members is None:
+ members = self
+
+ for tarinfo in members:
+ if tarinfo.isdir():
+ # Extract directories with a safe mode.
+ directories.append(tarinfo)
+ tarinfo = copy.copy(tarinfo)
+ tarinfo.mode = 448 # decimal for oct 0700
+ self.extract(tarinfo, path)
+
+ # Reverse sort directories.
+ if sys.version_info < (2, 4):
+ def sorter(dir1, dir2):
+ return cmp(dir1.name, dir2.name)
+ directories.sort(sorter)
+ directories.reverse()
+ else:
+ directories.sort(key=operator.attrgetter('name'), reverse=True)
+
+ # Set correct owner, mtime and filemode on directories.
+ for tarinfo in directories:
+ dirpath = os.path.join(path, tarinfo.name)
+ try:
+ self.chown(tarinfo, dirpath)
+ self.utime(tarinfo, dirpath)
+ self.chmod(tarinfo, dirpath)
+ except ExtractError:
+ e = sys.exc_info()[1]
+ if self.errorlevel > 1:
+ raise
+ else:
+ self._dbg(1, "tarfile: %s" % e)
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ tarball = download_setuptools()
+ _install(tarball)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/pbr/d2to1/tests/testpackage/extra-file.txt b/pbr/d2to1/tests/testpackage/extra-file.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/extra-file.txt
diff --git a/pbr/d2to1/tests/testpackage/setup.cfg b/pbr/d2to1/tests/testpackage/setup.cfg
new file mode 100644
index 0000000..a200616
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/setup.cfg
@@ -0,0 +1,46 @@
+[metadata]
+name = d2to1_testpackage
+version = 0.1.dev
+author = Erik M. Bray
+author-email = embray@stsci.edu
+home-page = http://www.stsci.edu/resources/software_hardware/stsci_python
+summary = Test package for testing d2to1
+description-file =
+ README.txt
+ CHANGES.txt
+requires-python = >=2.5
+
+requires-dist =
+ setuptools
+
+classifier =
+ Development Status :: 3 - Alpha
+ Intended Audience :: Developers
+ License :: OSI Approved :: BSD License
+ Programming Language :: Python
+ Topic :: Scientific/Engineering
+ Topic :: Software Development :: Build Tools
+ Topic :: Software Development :: Libraries :: Python Modules
+ Topic :: System :: Archiving :: Packaging
+
+keywords = packaging, distutils, setuptools
+
+[files]
+packages = d2to1_testpackage
+package-data = testpackage = package_data/*.txt
+data-files = testpackage/data_files = data_files/*.txt
+extra-files = extra-file.txt
+
+[extension=d2to1_testpackage.testext]
+sources = src/testext.c
+optional = True
+
+[global]
+#setup-hooks =
+# d2to1_testpackage._setup_hooks.test_hook_1
+# d2to1_testpackage._setup_hooks.test_hook_2
+commands = d2to1_testpackage._setup_hooks.test_command
+
+[build_ext]
+#pre-hook.test_pre_hook = d2to1_testpackage._setup_hooks.test_pre_hook
+#post-hook.test_post_hook = d2to1_testpackage._setup_hooks.test_post_hook
diff --git a/pbr/d2to1/tests/testpackage/setup.py b/pbr/d2to1/tests/testpackage/setup.py
new file mode 100755
index 0000000..dbaba47
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/setup.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+try:
+ from setuptools import setup
+except ImportError:
+ from distribute_setup import use_setuptools
+ use_setuptools()
+ from setuptools import setup
+
+setup(
+ setup_requires=['d2to1'],
+ d2to1=True,
+)
diff --git a/pbr/d2to1/tests/testpackage/src/testext.c b/pbr/d2to1/tests/testpackage/src/testext.c
new file mode 100644
index 0000000..872d43c
--- /dev/null
+++ b/pbr/d2to1/tests/testpackage/src/testext.c
@@ -0,0 +1,28 @@
+#include <Python.h>
+
+
+static PyMethodDef TestextMethods[] = {
+ {NULL, NULL, 0, NULL}
+};
+
+
+#if PY_MAJOR_VERSION >=3
+static struct PyModuleDef testextmodule = {
+ PyModuleDef_HEAD_INIT,
+ "testext",
+ -1,
+ TestextMethods
+};
+
+PyObject*
+PyInit_testext(void)
+{
+ return PyModule_Create(&testextmodule);
+}
+#else
+PyMODINIT_FUNC
+inittestext(void)
+{
+ Py_InitModule("testext", TestextMethods);
+}
+#endif
diff --git a/pbr/d2to1/tests/util.py b/pbr/d2to1/tests/util.py
new file mode 100644
index 0000000..fa55587
--- /dev/null
+++ b/pbr/d2to1/tests/util.py
@@ -0,0 +1,35 @@
+from __future__ import with_statement
+
+import contextlib
+import os
+import shutil
+import stat
+
+
+from ..extern.six import moves as m
+ConfigParser = m.configparser.ConfigParser
+
+
+@contextlib.contextmanager
+def open_config(filename):
+ cfg = ConfigParser()
+ cfg.read(filename)
+ yield cfg
+ with open(filename, 'w') as fp:
+ cfg.write(fp)
+
+
+def rmtree(path):
+ """
+ shutil.rmtree() with error handler for 'access denied' from trying to
+ delete read-only files.
+ """
+
+ def onerror(func, path, exc_info):
+ if not os.access(path, os.W_OK):
+ os.chmod(path, stat.S_IWUSR)
+ func(path)
+ else:
+ raise
+
+ return shutil.rmtree(path, onerror=onerror)
diff --git a/pbr/d2to1/util.py b/pbr/d2to1/util.py
new file mode 100644
index 0000000..9d67f80
--- /dev/null
+++ b/pbr/d2to1/util.py
@@ -0,0 +1,582 @@
+"""The code in this module is mostly copy/pasted out of the distutils2 source
+code, as recommended by Tarek Ziade. As such, it may be subject to some change
+as distutils2 development continues, and will have to be kept up to date.
+
+I didn't want to use it directly from distutils2 itself, since I do not want it
+to be an installation dependency for our packages yet--it is still too unstable
+(the latest version on PyPI doesn't even install).
+"""
+
+# These first two imports are not used, but are needed to get around an
+# irritating Python bug that can crop up when using ./setup.py test.
+# See: http://www.eby-sarna.com/pipermail/peak/2010-May/003355.html
+try:
+ import multiprocessing
+except ImportError:
+ pass
+import logging
+
+import os
+import re
+import sys
+import traceback
+
+from collections import defaultdict
+
+import distutils.ccompiler
+
+from distutils import log
+from distutils.errors import (DistutilsOptionError, DistutilsModuleError,
+ DistutilsFileError)
+from setuptools.command.egg_info import manifest_maker
+from setuptools.dist import Distribution
+from setuptools.extension import Extension
+
+from .extern.six import moves as m
+RawConfigParser = m.configparser.RawConfigParser
+
+
+# A simplified RE for this; just checks that the line ends with version
+# predicates in ()
+_VERSION_SPEC_RE = re.compile(r'\s*(.*?)\s*\((.*)\)\s*$')
+
+
+# Mappings from setup() keyword arguments to setup.cfg options;
+# The values are (section, option) tuples, or simply (section,) tuples if
+# the option has the same name as the setup() argument
+D1_D2_SETUP_ARGS = {
+ "name": ("metadata",),
+ "version": ("metadata",),
+ "author": ("metadata",),
+ "author_email": ("metadata",),
+ "maintainer": ("metadata",),
+ "maintainer_email": ("metadata",),
+ "url": ("metadata", "home_page"),
+ "description": ("metadata", "summary"),
+ "keywords": ("metadata",),
+ "long_description": ("metadata", "description"),
+ "download-url": ("metadata",),
+ "classifiers": ("metadata", "classifier"),
+ "platforms": ("metadata", "platform"), # **
+ "license": ("metadata",),
+ # Use setuptools install_requires, not
+ # broken distutils requires
+ "install_requires": ("metadata", "requires_dist"),
+ "setup_requires": ("metadata", "setup_requires_dist"),
+ "provides": ("metadata", "provides_dist"), # **
+ "obsoletes": ("metadata", "obsoletes_dist"), # **
+ "package_dir": ("files", 'packages_root'),
+ "packages": ("files",),
+ "package_data": ("files",),
+ "namespace_packages": ("files",),
+ "data_files": ("files",),
+ "scripts": ("files",),
+ "py_modules": ("files", "modules"), # **
+ "cmdclass": ("global", "commands"),
+ # Not supported in distutils2, but provided for
+ # backwards compatibility with setuptools
+ "use_2to3": ("backwards_compat", "use_2to3"),
+ "zip_safe": ("backwards_compat", "zip_safe"),
+ "tests_require": ("backwards_compat", "tests_require"),
+ "dependency_links": ("backwards_compat",),
+ "include_package_data": ("backwards_compat",),
+}
+
+# setup() arguments that can have multiple values in setup.cfg
+MULTI_FIELDS = ("classifiers",
+ "platforms",
+ "install_requires",
+ "provides",
+ "obsoletes",
+ "namespace_packages",
+ "packages",
+ "package_data",
+ "data_files",
+ "scripts",
+ "py_modules",
+ "dependency_links",
+ "setup_requires",
+ "tests_require",
+ "cmdclass")
+
+# setup() arguments that contain boolean values
+BOOL_FIELDS = ("use_2to3", "zip_safe", "include_package_data")
+
+
+CSV_FIELDS = ("keywords",)
+
+
+log.set_verbosity(log.INFO)
+
+
+def resolve_name(name):
+ """Resolve a name like ``module.object`` to an object and return it.
+
+ Raise ImportError if the module or name is not found.
+ """
+
+ parts = name.split('.')
+ cursor = len(parts) - 1
+ module_name = parts[:cursor]
+ attr_name = parts[-1]
+
+ while cursor > 0:
+ try:
+ ret = __import__('.'.join(module_name), fromlist=[attr_name])
+ break
+ except ImportError:
+ if cursor == 0:
+ raise
+ cursor -= 1
+ module_name = parts[:cursor]
+ attr_name = parts[cursor]
+ ret = ''
+
+ for part in parts[cursor:]:
+ try:
+ ret = getattr(ret, part)
+ except AttributeError:
+ raise ImportError(name)
+
+ return ret
+
+
+def cfg_to_args(path='setup.cfg'):
+ """ Distutils2 to distutils1 compatibility util.
+
+ This method uses an existing setup.cfg to generate a dictionary of
+ keywords that can be used by distutils.core.setup(kwargs**).
+
+ :param file:
+ The setup.cfg path.
+ :raises DistutilsFileError:
+ When the setup.cfg file is not found.
+
+ """
+
+ # The method source code really starts here.
+ parser = RawConfigParser()
+ if not os.path.exists(path):
+ raise DistutilsFileError("file '%s' does not exist" %
+ os.path.abspath(path))
+ parser.read(path)
+ config = {}
+ for section in parser.sections():
+ config[section] = dict(parser.items(section))
+
+ # Run setup_hooks, if configured
+ setup_hooks = has_get_option(config, 'global', 'setup_hooks')
+ package_dir = has_get_option(config, 'files', 'packages_root')
+
+ # Add the source package directory to sys.path in case it contains
+ # additional hooks, and to make sure it's on the path before any existing
+ # installations of the package
+ if package_dir:
+ package_dir = os.path.abspath(package_dir)
+ sys.path.insert(0, package_dir)
+
+ try:
+ if setup_hooks:
+ setup_hooks = split_multiline(setup_hooks)
+ for hook in setup_hooks:
+ hook_fn = resolve_name(hook)
+ try :
+ hook_fn(config)
+ except SystemExit:
+ log.error('setup hook %s terminated the installation')
+ except:
+ e = sys.exc_info()[1]
+ log.error('setup hook %s raised exception: %s\n' %
+ (hook, e))
+ log.error(traceback.format_exc())
+ sys.exit(1)
+
+ kwargs = setup_cfg_to_setup_kwargs(config)
+
+ register_custom_compilers(config)
+
+ ext_modules = get_extension_modules(config)
+ if ext_modules:
+ kwargs['ext_modules'] = ext_modules
+
+ entry_points = get_entry_points(config)
+ if entry_points:
+ kwargs['entry_points'] = entry_points
+
+ wrap_commands(kwargs)
+
+ # Handle the [files]/extra_files option
+ extra_files = has_get_option(config, 'files', 'extra_files')
+ if extra_files:
+ extra_files = split_multiline(extra_files)
+ # Let's do a sanity check
+ for filename in extra_files:
+ if not os.path.exists(filename):
+ raise DistutilsFileError(
+ '%s from the extra_files option in setup.cfg does not '
+ 'exist' % filename)
+ # Unfortunately the only really sensible way to do this is to
+ # monkey-patch the manifest_maker class
+ @monkeypatch_method(manifest_maker)
+ def add_defaults(self, extra_files=extra_files, log=log):
+ log.info('[d2to1] running patched manifest_maker command '
+ 'with extra_files support')
+ add_defaults._orig(self)
+ self.filelist.extend(extra_files)
+
+ finally:
+ # Perform cleanup if any paths were added to sys.path
+ if package_dir:
+ sys.path.pop(0)
+
+ return kwargs
+
+
+def setup_cfg_to_setup_kwargs(config):
+ """Processes the setup.cfg options and converts them to arguments accepted
+ by setuptools' setup() function.
+ """
+
+ kwargs = {}
+
+ for arg in D1_D2_SETUP_ARGS:
+ if len(D1_D2_SETUP_ARGS[arg]) == 2:
+ # The distutils field name is different than distutils2's.
+ section, option = D1_D2_SETUP_ARGS[arg]
+
+ elif len(D1_D2_SETUP_ARGS[arg]) == 1:
+ # The distutils field name is the same thant distutils2's.
+ section = D1_D2_SETUP_ARGS[arg][0]
+ option = arg
+
+ in_cfg_value = has_get_option(config, section, option)
+ if not in_cfg_value:
+ # There is no such option in the setup.cfg
+ if arg == "long_description":
+ in_cfg_value = has_get_option(config, section,
+ "description_file")
+ if in_cfg_value:
+ in_cfg_value = split_multiline(in_cfg_value)
+ value = ''
+ for filename in in_cfg_value:
+ description_file = open(filename)
+ try:
+ value += description_file.read().strip() + '\n\n'
+ finally:
+ description_file.close()
+ in_cfg_value = value
+ else:
+ continue
+
+ if arg in CSV_FIELDS:
+ in_cfg_value = split_csv(in_cfg_value)
+ if arg in MULTI_FIELDS:
+ in_cfg_value = split_multiline(in_cfg_value)
+ elif arg in BOOL_FIELDS:
+ # Provide some flexibility here...
+ if in_cfg_value.lower() in ('true', 't', '1', 'yes', 'y'):
+ in_cfg_value = True
+ else:
+ in_cfg_value = False
+
+ if in_cfg_value:
+ if arg in ('install_requires', 'tests_require'):
+ # Replaces PEP345-style version specs with the sort expected by
+ # setuptools
+ in_cfg_value = [_VERSION_SPEC_RE.sub(r'\1\2', pred)
+ for pred in in_cfg_value]
+ elif arg == 'package_dir':
+ in_cfg_value = {'': in_cfg_value}
+ elif arg in ('package_data', 'data_files'):
+ data_files = {}
+ firstline = True
+ prev = None
+ for line in in_cfg_value:
+ if '=' in line:
+ key, value = line.split('=', 1)
+ key, value = (key.strip(), value.strip())
+ if key in data_files:
+ # Multiple duplicates of the same package name;
+ # this is for backwards compatibility of the old
+ # format prior to d2to1 0.2.6.
+ prev = data_files[key]
+ prev.extend(value.split())
+ else:
+ prev = data_files[key.strip()] = value.split()
+ elif firstline:
+ raise DistutilsOptionError(
+ 'malformed package_data first line %r (misses '
+ '"=")' % line)
+ else:
+ prev.extend(line.strip().split())
+ firstline = False
+ if arg == 'data_files':
+ # the data_files value is a pointlessly different structure
+ # from the package_data value
+ data_files = data_files.items()
+ in_cfg_value = data_files
+ elif arg == 'cmdclass':
+ cmdclass = {}
+ dist = Distribution()
+ for cls in in_cfg_value:
+ cls = resolve_name(cls)
+ cmd = cls(dist)
+ cmdclass[cmd.get_command_name()] = cls
+ in_cfg_value = cmdclass
+
+ kwargs[arg] = in_cfg_value
+
+ return kwargs
+
+
+def register_custom_compilers(config):
+ """Handle custom compilers; this has no real equivalent in distutils, where
+ additional compilers could only be added programmatically, so we have to
+ hack it in somehow.
+ """
+
+ compilers = has_get_option(config, 'global', 'compilers')
+ if compilers:
+ compilers = split_multiline(compilers)
+ for compiler in compilers:
+ compiler = resolve_name(compiler)
+
+ # In distutils2 compilers these class attributes exist; for
+ # distutils1 we just have to make something up
+ if hasattr(compiler, 'name'):
+ name = compiler.name
+ else:
+ name = compiler.__name__
+ if hasattr(compiler, 'description'):
+ desc = compiler.description
+ else:
+ desc = 'custom compiler %s' % name
+
+ module_name = compiler.__module__
+ # Note; this *will* override built in compilers with the same name
+ # TODO: Maybe display a warning about this?
+ cc = distutils.ccompiler.compiler_class
+ cc[name] = (module_name, compiler.__name__, desc)
+
+ # HACK!!!! Distutils assumes all compiler modules are in the
+ # distutils package
+ sys.modules['distutils.' + module_name] = sys.modules[module_name]
+
+
+def get_extension_modules(config):
+ """Handle extension modules"""
+
+ EXTENSION_FIELDS = ("sources",
+ "include_dirs",
+ "define_macros",
+ "undef_macros",
+ "library_dirs",
+ "libraries",
+ "runtime_library_dirs",
+ "extra_objects",
+ "extra_compile_args",
+ "extra_link_args",
+ "export_symbols",
+ "swig_opts",
+ "depends")
+
+ ext_modules = []
+ for section in config:
+ if ':' in section:
+ labels = section.split(':', 1)
+ else:
+ # Backwards compatibility for old syntax; don't use this though
+ labels = section.split('=', 1)
+ labels = [l.strip() for l in labels]
+ if (len(labels) == 2) and (labels[0] == 'extension'):
+ ext_args = {}
+ for field in EXTENSION_FIELDS:
+ value = has_get_option(config, section, field)
+ # All extension module options besides name can have multiple
+ # values
+ if not value:
+ continue
+ value = split_multiline(value)
+ if field == 'define_macros':
+ macros = []
+ for macro in value:
+ macro = macro.split('=', 1)
+ if len(macro) == 1:
+ macro = (macro[0].strip(), None)
+ else:
+ macro = (macro[0].strip(), macro[1].strip())
+ macros.append(macro)
+ value = macros
+ ext_args[field] = value
+ if ext_args:
+ if 'name' not in ext_args:
+ ext_args['name'] = labels[1]
+ ext_modules.append(Extension(ext_args.pop('name'),
+ **ext_args))
+ return ext_modules
+
+
+def get_entry_points(config):
+ """Process the [entry_points] section of setup.cfg to handle setuptools
+ entry points. This is, of course, not a standard feature of
+ distutils2/packaging, but as there is not currently a standard alternative
+ in packaging, we provide support for them.
+ """
+
+ if not 'entry_points' in config:
+ return {}
+
+ return dict((option, split_multiline(value))
+ for option, value in config['entry_points'].items())
+
+
+def wrap_commands(kwargs):
+ dist = Distribution()
+
+ # This should suffice to get the same config values and command classes
+ # that the actual Distribution will see (not counting cmdclass, which is
+ # handled below)
+ dist.parse_config_files()
+
+ for cmd, _ in dist.get_command_list():
+ hooks = {}
+ for opt, val in dist.get_option_dict(cmd).items():
+ val = val[1]
+ if opt.startswith('pre_hook.') or opt.startswith('post_hook.'):
+ hook_type, alias = opt.split('.', 1)
+ hook_dict = hooks.setdefault(hook_type, {})
+ hook_dict[alias] = val
+ if not hooks:
+ continue
+
+ if 'cmdclass' in kwargs and cmd in kwargs['cmdclass']:
+ cmdclass = kwargs['cmdclass'][cmd]
+ else:
+ cmdclass = dist.get_command_class(cmd)
+
+ new_cmdclass = wrap_command(cmd, cmdclass, hooks)
+ kwargs.setdefault('cmdclass', {})[cmd] = new_cmdclass
+
+
+def wrap_command(cmd, cmdclass, hooks):
+ def run(self, cmdclass=cmdclass):
+ self.run_command_hooks('pre_hook')
+ cmdclass.run(self)
+ self.run_command_hooks('post_hook')
+
+ return type(cmd, (cmdclass, object),
+ {'run': run, 'run_command_hooks': run_command_hooks,
+ 'pre_hook': hooks.get('pre_hook'),
+ 'post_hook': hooks.get('post_hook')})
+
+
+def run_command_hooks(cmd_obj, hook_kind):
+ """Run hooks registered for that command and phase.
+
+ *cmd_obj* is a finalized command object; *hook_kind* is either
+ 'pre_hook' or 'post_hook'.
+ """
+
+ if hook_kind not in ('pre_hook', 'post_hook'):
+ raise ValueError('invalid hook kind: %r' % hook_kind)
+
+ hooks = getattr(cmd_obj, hook_kind, None)
+
+ if hooks is None:
+ return
+
+ for hook in hooks.values():
+ if isinstance(hook, str):
+ try:
+ hook_obj = resolve_name(hook)
+ except ImportError:
+ err = sys.exc_info()[1] # For py3k
+ raise DistutilsModuleError('cannot find hook %s: %s' %
+ (hook,err))
+ else:
+ hook_obj = hook
+
+ if not hasattr(hook_obj, '__call__'):
+ raise DistutilsOptionError('hook %r is not callable' % hook)
+
+ log.info('running %s %s for command %s',
+ hook_kind, hook, cmd_obj.get_command_name())
+
+ try :
+ hook_obj(cmd_obj)
+ except:
+ e = sys.exc_info()[1]
+ log.error('hook %s raised exception: %s\n' % (hook, e))
+ log.error(traceback.format_exc())
+ sys.exit(1)
+
+
+def has_get_option(config, section, option):
+ if section in config and option in config[section]:
+ return config[section][option]
+ elif section in config and option.replace('_', '-') in config[section]:
+ return config[section][option.replace('_', '-')]
+ else:
+ return False
+
+
+def split_multiline(value):
+ """Special behaviour when we have a multi line options"""
+
+ value = [element for element in
+ (line.strip() for line in value.split('\n'))
+ if element]
+ return value
+
+
+def split_csv(value):
+ """Special behaviour when we have a comma separated options"""
+
+ value = [element for element in
+ (chunk.strip() for chunk in value.split(','))
+ if element]
+ return value
+
+
+def monkeypatch_method(cls):
+ """A function decorator to monkey-patch a method of the same name on the
+ given class.
+ """
+
+ def wrapper(func):
+ orig = getattr(cls, func.__name__, None)
+ if orig and not hasattr(orig, '_orig'): # Already patched
+ setattr(func, '_orig', orig)
+ setattr(cls, func.__name__, func)
+ return func
+
+ return wrapper
+
+
+# The following classes are used to hack Distribution.command_options a bit
+class DefaultGetDict(defaultdict):
+ """Like defaultdict, but the get() method also sets and returns the default
+ value.
+ """
+
+ def get(self, key, default=None):
+ if default is None:
+ default = self.default_factory()
+ return super(DefaultGetDict, self).setdefault(key, default)
+
+
+class IgnoreDict(dict):
+ """A dictionary that ignores any insertions in which the key is a string
+ matching any string in `ignore`. The ignore list can also contain wildcard
+ patterns using '*'.
+ """
+
+ def __init__(self, ignore):
+ self.__ignore = re.compile(r'(%s)' % ('|'.join(
+ [pat.replace('*', '.*')
+ for pat in ignore])))
+
+ def __setitem__(self, key, val):
+ if self.__ignore.match(key):
+ return
+ super(IgnoreDict, self).__setitem__(key, val)
diff --git a/pbr/d2to1/zestreleaser.py b/pbr/d2to1/zestreleaser.py
new file mode 100644
index 0000000..326baca
--- /dev/null
+++ b/pbr/d2to1/zestreleaser.py
@@ -0,0 +1,159 @@
+"""zest.releaser entry points to support projects using distutils2-like
+setup.cfg files. The only actual functionality this adds is to update the
+version option in a setup.cfg file, if it exists. If setup.cfg does not exist,
+or does not contain a version option, then this does nothing.
+
+TODO: d2to1 theoretically supports using a different filename for setup.cfg;
+this does not support that. We could hack in support, though I'm not sure how
+useful the original functionality is to begin with (and it might be removed) so
+we ignore that for now.
+
+TODO: There exists a proposal
+(http://mail.python.org/pipermail/distutils-sig/2011-March/017628.html) to add
+a 'version-from-file' option (or something of the like) to distutils2; if this
+is added then support for it should be included here as well.
+"""
+
+
+import logging
+import os
+
+from .extern.six import moves as m
+ConfigParser = m.configparser.ConfigParser
+
+
+logger = logging.getLogger(__name__)
+
+
+
+def update_setupcfg_version(filename, version):
+ """Opens the given setup.cfg file, locates the version option in the
+ [metadata] section, updates it to the new version.
+ """
+
+ setup_cfg = open(filename).readlines()
+ current_section = None
+ updated = False
+
+ for idx, line in enumerate(setup_cfg):
+ m = ConfigParser.SECTCRE.match(line)
+ if m:
+ if current_section == 'metadata':
+ # We already parsed the entire metadata section without finding
+ # a version line, and are now moving into a new section
+ break
+ current_section = m.group('header')
+ continue
+
+ if '=' not in line:
+ continue
+
+ opt, val = line.split('=', 1)
+ opt, val = opt.strip(), val.strip()
+ if current_section == 'metadata' and opt == 'version':
+ setup_cfg[idx] = 'version = %s\n' % version
+ updated = True
+ break
+
+ if updated:
+ open(filename, 'w').writelines(setup_cfg)
+ logger.info("Set %s's version to %r" % (os.path.basename(filename),
+ version))
+
+
+def prereleaser_middle(data):
+ filename = os.path.join(data['workingdir'], 'setup.cfg')
+ if os.path.exists(filename):
+ update_setupcfg_version(filename, data['new_version'])
+
+
+def releaser_middle(data):
+ """
+ releaser.middle hook to monkey-patch zest.releaser to support signed
+ tagging--currently this is the only way to do this. Also monkey-patches to
+ disable an annoyance where zest.releaser only creates .zip source
+ distributions. This is supposedly a workaround for a bug in Python 2.4,
+ but we don't care about Python 2.4.
+ """
+
+ import os
+ import sys
+
+ from zest.releaser.git import Git
+ from zest.releaser.release import Releaser
+
+ # Copied verbatim from zest.releaser, but with the cmd string modified to
+ # use the -s option to create a signed tag
+ def _my_create_tag(self, version):
+ msg = "Tagging %s" % (version,)
+ cmd = 'git tag -s %s -m "%s"' % (version, msg)
+ if os.path.isdir('.git/svn'):
+ print "\nEXPERIMENTAL support for git-svn tagging!\n"
+ cur_branch = open('.git/HEAD').read().strip().split('/')[-1]
+ print "You are on branch %s." % (cur_branch,)
+ if cur_branch != 'master':
+ print "Only the master branch is supported for git-svn tagging."
+ print "Please tag yourself."
+ print "'git tag' needs to list tag named %s." % (version,)
+ sys.exit()
+ cmd = [cmd]
+ local_head = open('.git/refs/heads/master').read()
+ trunk = open('.git/refs/remotes/trunk').read()
+ if local_head != trunk:
+ print "Your local master diverges from trunk.\n"
+ # dcommit before local tagging
+ cmd.insert(0, 'git svn dcommit')
+ # create tag in svn
+ cmd.append('git svn tag -m "%s" %s' % (msg, version))
+ return cmd
+
+ # Similarly copied from zer.releaser to support use of 'v' in front
+ # of the version number
+ def _my_make_tag(self):
+ from zest.releaser import utils
+ from os import system
+
+ if self.data['tag_already_exists']:
+ return
+ cmds = self.vcs.cmd_create_tag(self.data['version'])
+ if not isinstance(cmds, list):
+ cmds = [cmds]
+ if len(cmds) == 1:
+ print "Tag needed to proceed, you can use the following command:"
+ for cmd in cmds:
+ print cmd
+ if utils.ask("Run this command"):
+ print system(cmd)
+ else:
+ # all commands are needed in order to proceed normally
+ print "Please create a tag for %s yourself and rerun." % \
+ (self.data['version'],)
+ sys.exit()
+ if not self.vcs.tag_exists('v' + self.data['version']):
+ print "\nFailed to create tag %s!" % (self.data['version'],)
+ sys.exit()
+
+ # Normally all this does is to return '--formats=zip', which is currently
+ # hard-coded as an option to always add to the sdist command; they ought to
+ # make this actually optional
+ def _my_sdist_options(self):
+ return ''
+
+ Git.cmd_create_tag = _my_create_tag
+ Releaser._make_tag = _my_make_tag
+ Releaser._sdist_options = _my_sdist_options
+
+
+def postreleaser_before(data):
+ """
+ Fix the irritating .dev0 default appended to new development versions by
+ zest.releaser to just append ".dev" without the "0".
+ """
+
+ data['dev_version_template'] = '%(new_version)s.dev'
+
+
+def postreleaser_middle(data):
+ filename = os.path.join(data['workingdir'], 'setup.cfg')
+ if os.path.exists(filename):
+ update_setupcfg_version(filename, data['dev_version'])