Merge d2to1 into the tree, complete with history.
This commit is contained in:
commit
295dbe1da3
pbr/d2to1
__init__.pycore.py
extern
tests
__init__.pytest_commands.pytest_core.pytest_hooks.py
util.pyzestreleaser.pytestpackage
CHANGES.txtLICENSE.txtMANIFEST.inREADME.txt
util.pyd2to1_testpackage
data_files
distribute_setup.pyextra-file.txtsetup.cfgsetup.pysrc
4
pbr/d2to1/__init__.py
Normal file
4
pbr/d2to1/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
try:
|
||||
__version__ = __import__('pkg_resources').get_distribution('d2to1').version
|
||||
except:
|
||||
__version__ = ''
|
82
pbr/d2to1/core.py
Normal file
82
pbr/d2to1/core.py
Normal file
@ -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))
|
0
pbr/d2to1/extern/__init__.py
vendored
Normal file
0
pbr/d2to1/extern/__init__.py
vendored
Normal file
386
pbr/d2to1/extern/six.py
vendored
Normal file
386
pbr/d2to1/extern/six.py
vendored
Normal file
@ -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,), {})
|
90
pbr/d2to1/tests/__init__.py
Normal file
90
pbr/d2to1/tests/__init__.py
Normal file
@ -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,)
|
13
pbr/d2to1/tests/test_commands.py
Normal file
13
pbr/d2to1/tests/test_commands.py
Normal file
@ -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
|
48
pbr/d2to1/tests/test_core.py
Normal file
48
pbr/d2to1/tests/test_core.py
Normal file
@ -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
|
52
pbr/d2to1/tests/test_hooks.py
Normal file
52
pbr/d2to1/tests/test_hooks.py
Normal file
@ -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
|
||||
|
||||
|
86
pbr/d2to1/tests/testpackage/CHANGES.txt
Normal file
86
pbr/d2to1/tests/testpackage/CHANGES.txt
Normal file
@ -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
|
29
pbr/d2to1/tests/testpackage/LICENSE.txt
Normal file
29
pbr/d2to1/tests/testpackage/LICENSE.txt
Normal file
@ -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.
|
||||
|
1
pbr/d2to1/tests/testpackage/MANIFEST.in
Normal file
1
pbr/d2to1/tests/testpackage/MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
include data_files/*
|
148
pbr/d2to1/tests/testpackage/README.txt
Normal file
148
pbr/d2to1/tests/testpackage/README.txt
Normal file
@ -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
|
@ -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')
|
0
pbr/d2to1/tests/testpackage/data_files/a.txt
Normal file
0
pbr/d2to1/tests/testpackage/data_files/a.txt
Normal file
0
pbr/d2to1/tests/testpackage/data_files/b.txt
Normal file
0
pbr/d2to1/tests/testpackage/data_files/b.txt
Normal file
0
pbr/d2to1/tests/testpackage/data_files/c.rst
Normal file
0
pbr/d2to1/tests/testpackage/data_files/c.rst
Normal file
485
pbr/d2to1/tests/testpackage/distribute_setup.py
Normal file
485
pbr/d2to1/tests/testpackage/distribute_setup.py
Normal file
@ -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:])
|
0
pbr/d2to1/tests/testpackage/extra-file.txt
Normal file
0
pbr/d2to1/tests/testpackage/extra-file.txt
Normal file
46
pbr/d2to1/tests/testpackage/setup.cfg
Normal file
46
pbr/d2to1/tests/testpackage/setup.cfg
Normal file
@ -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
|
12
pbr/d2to1/tests/testpackage/setup.py
Executable file
12
pbr/d2to1/tests/testpackage/setup.py
Executable file
@ -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,
|
||||
)
|
28
pbr/d2to1/tests/testpackage/src/testext.c
Normal file
28
pbr/d2to1/tests/testpackage/src/testext.c
Normal file
@ -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
|
35
pbr/d2to1/tests/util.py
Normal file
35
pbr/d2to1/tests/util.py
Normal file
@ -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)
|
582
pbr/d2to1/util.py
Normal file
582
pbr/d2to1/util.py
Normal file
@ -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)
|
159
pbr/d2to1/zestreleaser.py
Normal file
159
pbr/d2to1/zestreleaser.py
Normal file
@ -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'])
|
Loading…
x
Reference in New Issue
Block a user