diff options
29 files changed, 781 insertions, 274 deletions
@@ -37,3 +37,5 @@ de44acab3cfce1f5bc811d6c0fa1a88ca0e9533f 0.6.21 b69f072c000237435e17b8bbb304ba6f957283eb 0.6.26 469c3b948e41ef28752b3cdf3c7fb9618355ebf5 0.6.27 fc379e63586ad3c6838e1bda216548ba8270b8f0 0.6.28 +4f82563d0f5d1af1fb215c0ac87f38b16bb5c42d 0.6.29 +7464fc916fa4d8308e34e45a1198512fe04c97b4 0.6.30 diff --git a/CHANGES.txt b/CHANGES.txt index b59a0b00..2b376104 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,10 +3,48 @@ CHANGES ======= ------ +0.6.31 +------ + +* Issue #303: Make sure the manifest only ever contains UTF-8 in Python 3. +* Issue #329: Properly close files created by tests for compatibility with + Jython. +* Work around Jython bugs `#1980 <http://bugs.jython.org/issue1980>`_ and + `#1981 <http://bugs.jython.org/issue1981>`_. +* Issue #334: Provide workaround for packages that reference `sys.__stdout__` + such as numpy does. This change should address + `virtualenv #359 <https://github.com/pypa/virtualenv/issues/359>`_ as long + as the system encoding is UTF-8 or the IO encoding is specified in the + environment, i.e.:: + + PYTHONIOENCODING=utf8 pip install numpy + +* Fix for encoding issue when installing from Windows executable on Python 3. + +------ +0.6.30 +------ + +* Issue #328: Clean up temporary directories in distribute_setup.py. +* Fix fatal bug in distribute_setup.py. + +------ 0.6.29 ------ -* Issue #283: Reenable scanning of *.pyc / *.pyo files on Python 3.3. +* Pull Request #14: Honor file permissions in zip files. +* Issue #327: Merged pull request #24 to fix a dependency problem with pip. +* Merged pull request #23 to fix https://github.com/pypa/virtualenv/issues/301. +* If Sphinx is installed, the `upload_docs` command now runs `build_sphinx` + to produce uploadable documentation. +* Issue #326: `upload_docs` provided mangled auth credentials under Python 3. +* Issue #320: Fix check for "createable" in distribute_setup.py. +* Issue #305: Remove a warning that was triggered during normal operations. +* Issue #311: Print metadata in UTF-8 independent of platform. +* Issue #303: Read manifest file with UTF-8 encoding under Python 3. +* Issue #301: Allow to run tests of namespace packages when using 2to3. +* Issue #304: Prevent import loop in site.py under Python 3.3. +* Issue #283: Reenable scanning of `*.pyc` / `*.pyo` files on Python 3.3. * Issue #299: The develop command didn't work on Python 3, when using 2to3, as the egg link would go to the Python 2 source. Linking to the 2to3'd code in build/lib makes it work, although you will have to rebuild the module @@ -29,7 +67,8 @@ CHANGES * Issue #294: setup.py can now be invoked from any directory. * Scripts are now installed honoring the umask. * Added support for .dist-info directories. -* Issue #283: Fix and disable scanning of *.pyc / *.pyo files on Python 3.3. +* Issue #283: Fix and disable scanning of `*.pyc` / `*.pyo` files on + Python 3.3. ------ 0.6.27 @@ -286,11 +325,10 @@ CHANGES ----- * Added the generation of `distribute_setup_3k.py` during the release. - This close http://bitbucket.org/tarek/distribute/issue/52. + This closes issue #52. * Added an upload_docs command to easily upload project documentation to - PyPI's http://packages.python.org. - This close http://bitbucket.org/tarek/distribute/issue/56. + PyPI's http://packages.python.org. This close issue #56. * Fixed a bootstrap bug on the use_setuptools() API. @@ -319,7 +357,7 @@ setuptools This closes http://bugs.python.org/setuptools/issue39. * Added option to run 2to3 automatically when installing on Python 3. - This closes http://bitbucket.org/tarek/distribute/issue/31. + This closes issue #31. * Fixed invalid usage of requirement.parse, that broke develop -d. This closes http://bugs.python.org/setuptools/issue44. @@ -333,11 +371,9 @@ setuptools bootstrapping ============= -* Fixed bootstrap not working on Windows. - This closes http://bitbucket.org/tarek/distribute/issue/49. +* Fixed bootstrap not working on Windows. This closes issue #49. -* Fixed 2.6 dependencies. - This closes http://bitbucket.org/tarek/distribute/issue/50. +* Fixed 2.6 dependencies. This closes issue #50. * Make sure setuptools is patched when running through easy_install This closes http://bugs.python.org/setuptools/issue40. @@ -350,16 +386,14 @@ setuptools ========== * package_index.urlopen now catches BadStatusLine and malformed url errors. - This closes http://bitbucket.org/tarek/distribute/issue/16 and - http://bitbucket.org/tarek/distribute/issue/18. + This closes issue #16 and issue #18. * zip_ok is now False by default. This closes http://bugs.python.org/setuptools/issue33. * Fixed invalid URL error catching. http://bugs.python.org/setuptools/issue20. -* Fixed invalid bootstraping with easy_install installation - http://bitbucket.org/tarek/distribute/issue/40. +* Fixed invalid bootstraping with easy_install installation (issue #40). Thanks to Florian Schulze for the help. * Removed buildout/bootstrap.py. A new repository will create a specific @@ -371,7 +405,7 @@ bootstrapping * The boostrap process leave setuptools alone if detected in the system and --root or --prefix is provided, but is not in the same location. - This closes http://bitbucket.org/tarek/distribute/issue/10. + This closes issue #10. --- 0.6 @@ -381,45 +415,38 @@ setuptools ========== * Packages required at build time where not fully present at install time. - This closes http://bitbucket.org/tarek/distribute/issue/12. + This closes issue #12. -* Protected against failures in tarfile extraction. This closes - http://bitbucket.org/tarek/distribute/issue/10. +* Protected against failures in tarfile extraction. This closes issue #10. -* Made Jython api_tests.txt doctest compatible. This closes - http://bitbucket.org/tarek/distribute/issue/7. +* Made Jython api_tests.txt doctest compatible. This closes issue #7. * sandbox.py replaced builtin type file with builtin function open. This - closes http://bitbucket.org/tarek/distribute/issue/6. + closes issue #6. -* Immediately close all file handles. This closes - http://bitbucket.org/tarek/distribute/issue/3. +* Immediately close all file handles. This closes issue #3. -* Added compatibility with Subversion 1.6. This references - http://bitbucket.org/tarek/distribute/issue/1. +* Added compatibility with Subversion 1.6. This references issue #1. pkg_resources ============= * Avoid a call to /usr/bin/sw_vers on OSX and use the official platform API - instead. Based on a patch from ronaldoussoren. This closes - http://bitbucket.org/tarek/distribute/issue/5. + instead. Based on a patch from ronaldoussoren. This closes issue #5. * Fixed a SandboxViolation for mkdir that could occur in certain cases. - This closes http://bitbucket.org/tarek/distribute/issue/13. + This closes issue #13. * Allow to find_on_path on systems with tight permissions to fail gracefully. - This closes http://bitbucket.org/tarek/distribute/issue/9. + This closes issue #9. * Corrected inconsistency between documentation and code of add_entry. - This closes http://bitbucket.org/tarek/distribute/issue/8. + This closes issue #8. -* Immediately close all file handles. This closes - http://bitbucket.org/tarek/distribute/issue/3. +* Immediately close all file handles. This closes issue #3. easy_install ============ -* Immediately close all file handles. This closes - http://bitbucket.org/tarek/distribute/issue/3. +* Immediately close all file handles. This closes issue #3. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 85266239..22c90aba 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -12,14 +12,17 @@ Contributors * Jannis Leidel * Jason R. Coombs * Jim Fulton +* Jonathan Lange * Justin Azoff * Lennart Regebro * Marc Abramowitz * Martin von Löwis * Noufal Ibrahim +* Pete Hollobon * Philip Jenvey * Reinout van Rees * Robert Myers +* Stefan H. Holek * Tarek Ziadé * Toshio Kuratomi diff --git a/MANIFEST.in b/MANIFEST.in index 461cfd4f..9837747a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ recursive-include setuptools *.py *.txt *.exe recursive-include tests *.py *.c *.pyx *.txt recursive-include setuptools/tests *.html recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html +recursive-include _markerlib *.py include *.py include *.txt include MANIFEST.in @@ -99,9 +99,9 @@ Source installation Download the source tarball, uncompress it, then run the install command:: - $ curl -O http://pypi.python.org/packages/source/d/distribute/distribute-0.6.29.tar.gz - $ tar -xzvf distribute-0.6.29.tar.gz - $ cd distribute-0.6.29 + $ curl -O http://pypi.python.org/packages/source/d/distribute/distribute-0.6.31.tar.gz + $ tar -xzvf distribute-0.6.31.tar.gz + $ cd distribute-0.6.31 $ python setup.py install --------------------------- diff --git a/_markerlib/__init__.py b/_markerlib/__init__.py index 9c37bb9a..e2b237b1 100644 --- a/_markerlib/__init__.py +++ b/_markerlib/__init__.py @@ -1,2 +1,16 @@ -"""Used by pkg_resources to interpret PEP 345 environment markers.""" -from _markerlib.markers import default_environment, compile, interpret +try: + import ast + from _markerlib.markers import default_environment, compile, interpret +except ImportError: + if 'ast' in globals(): + raise + def default_environment(): + return {} + def compile(marker): + def marker_fn(environment=None, override=None): + # 'empty markers are True' heuristic won't install extra deps. + return not marker.strip() + marker_fn.__doc__ = marker + return marker_fn + def interpret(marker, environment=None, override=None): + return compile(marker)() diff --git a/_markerlib/markers.py b/_markerlib/markers.py index 54c2828a..23091e64 100644 --- a/_markerlib/markers.py +++ b/_markerlib/markers.py @@ -17,9 +17,7 @@ where EXPR belongs to any of those: __all__ = ['default_environment', 'compile', 'interpret'] -from ast import Compare, BoolOp, Attribute, Name, Load, Str, cmpop, boolop -from ast import parse, copy_location, NodeTransformer - +import ast import os import platform import sys @@ -46,27 +44,31 @@ def default_environment(): """Return copy of default PEP 385 globals dictionary.""" return dict(_VARS) -class ASTWhitelist(NodeTransformer): +class ASTWhitelist(ast.NodeTransformer): def __init__(self, statement): self.statement = statement # for error messages - - ALLOWED = (Compare, BoolOp, Attribute, Name, Load, Str, cmpop, boolop) - + + ALLOWED = (ast.Compare, ast.BoolOp, ast.Attribute, ast.Name, ast.Load, ast.Str) + # Bool operations + ALLOWED += (ast.And, ast.Or) + # Comparison operations + ALLOWED += (ast.Eq, ast.Gt, ast.GtE, ast.In, ast.Is, ast.IsNot, ast.Lt, ast.LtE, ast.NotEq, ast.NotIn) + def visit(self, node): """Ensure statement only contains allowed nodes.""" if not isinstance(node, self.ALLOWED): raise SyntaxError('Not allowed in environment markers.\n%s\n%s' % - (self.statement, + (self.statement, (' ' * node.col_offset) + '^')) - return NodeTransformer.visit(self, node) - + return ast.NodeTransformer.visit(self, node) + def visit_Attribute(self, node): """Flatten one level of attribute access.""" - new_node = Name("%s.%s" % (node.value.id, node.attr), node.ctx) - return copy_location(new_node, node) + new_node = ast.Name("%s.%s" % (node.value.id, node.attr), node.ctx) + return ast.copy_location(new_node, node) def parse_marker(marker): - tree = parse(marker, mode='eval') + tree = ast.parse(marker, mode='eval') new_tree = ASTWhitelist(marker).generic_visit(tree) return new_tree diff --git a/distribute.egg-info/entry_points.txt b/distribute.egg-info/entry_points.txt deleted file mode 100644 index 663882d6..00000000 --- a/distribute.egg-info/entry_points.txt +++ /dev/null @@ -1,62 +0,0 @@ -[distutils.commands] -bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm -rotate = setuptools.command.rotate:rotate -develop = setuptools.command.develop:develop -setopt = setuptools.command.setopt:setopt -build_py = setuptools.command.build_py:build_py -saveopts = setuptools.command.saveopts:saveopts -egg_info = setuptools.command.egg_info:egg_info -register = setuptools.command.register:register -upload_docs = setuptools.command.upload_docs:upload_docs -install_egg_info = setuptools.command.install_egg_info:install_egg_info -alias = setuptools.command.alias:alias -easy_install = setuptools.command.easy_install:easy_install -install_scripts = setuptools.command.install_scripts:install_scripts -bdist_wininst = setuptools.command.bdist_wininst:bdist_wininst -bdist_egg = setuptools.command.bdist_egg:bdist_egg -install = setuptools.command.install:install -test = setuptools.command.test:test -install_lib = setuptools.command.install_lib:install_lib -build_ext = setuptools.command.build_ext:build_ext -sdist = setuptools.command.sdist:sdist - -[egg_info.writers] -dependency_links.txt = setuptools.command.egg_info:overwrite_arg -requires.txt = setuptools.command.egg_info:write_requirements -PKG-INFO = setuptools.command.egg_info:write_pkg_info -eager_resources.txt = setuptools.command.egg_info:overwrite_arg -top_level.txt = setuptools.command.egg_info:write_toplevel_names -namespace_packages.txt = setuptools.command.egg_info:overwrite_arg -entry_points.txt = setuptools.command.egg_info:write_entries -depends.txt = setuptools.command.egg_info:warn_depends_obsolete - -[console_scripts] -easy_install = setuptools.command.easy_install:main -easy_install-2.7 = setuptools.command.easy_install:main - -[setuptools.file_finders] -svn_cvs = setuptools.command.sdist:_default_revctrl - -[distutils.setup_keywords] -dependency_links = setuptools.dist:assert_string_list -entry_points = setuptools.dist:check_entry_points -extras_require = setuptools.dist:check_extras -use_2to3_exclude_fixers = setuptools.dist:assert_string_list -package_data = setuptools.dist:check_package_data -install_requires = setuptools.dist:check_requirements -use_2to3 = setuptools.dist:assert_bool -use_2to3_fixers = setuptools.dist:assert_string_list -include_package_data = setuptools.dist:assert_bool -exclude_package_data = setuptools.dist:check_package_data -namespace_packages = setuptools.dist:check_nsp -test_suite = setuptools.dist:check_test_suite -eager_resources = setuptools.dist:assert_string_list -zip_safe = setuptools.dist:assert_bool -test_loader = setuptools.dist:check_importable -packages = setuptools.dist:check_packages -convert_2to3_doctests = setuptools.dist:assert_string_list -tests_require = setuptools.dist:check_requirements - -[setuptools.installation] -eggsecutable = setuptools.command.easy_install:bootstrap - diff --git a/distribute_setup.py b/distribute_setup.py index 95ba23c5..f3e85a1e 100644 --- a/distribute_setup.py +++ b/distribute_setup.py @@ -14,6 +14,7 @@ the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import os +import shutil import sys import time import fnmatch @@ -48,7 +49,7 @@ except ImportError: args = [quote(arg) for arg in args] return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 -DEFAULT_VERSION = "0.6.29" +DEFAULT_VERSION = "0.6.31" DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" SETUPTOOLS_FAKED_VERSION = "0.6c11" @@ -86,8 +87,11 @@ def _install(tarball, install_args=()): if not _python_cmd('setup.py', 'install', *install_args): log.warn('Something went wrong during the installation.') log.warn('See the error message above.') + # exitcode will be 2 + return 2 finally: os.chdir(old_wd) + shutil.rmtree(tmpdir) def _build_egg(egg, tarball, to_dir): @@ -112,6 +116,7 @@ def _build_egg(egg, tarball, to_dir): finally: os.chdir(old_wd) + shutil.rmtree(tmpdir) # returning the result log.warn(egg) if not os.path.exists(egg): @@ -257,7 +262,7 @@ def _same_content(path, content): def _rename_path(path): new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s into %s', path, new_name) + log.warn('Renaming %s to %s', path, new_name) os.rename(path, new_name) return new_name @@ -275,7 +280,7 @@ def _remove_flat_installation(placeholder): log.warn('Could not locate setuptools*.egg-info') return - log.warn('Removing elements out of the way...') + log.warn('Moving elements out of the way...') pkg_info = os.path.join(placeholder, file) if os.path.isdir(pkg_info): patched = _patch_egg_dir(pkg_info) @@ -316,12 +321,12 @@ def _create_fake_setuptools_pkg_info(placeholder): log.warn('%s already exists', pkg_info) return - if not os.access(pkg_info, os.W_OK): + log.warn('Creating %s', pkg_info) + try: + f = open(pkg_info, 'w') + except EnvironmentError: log.warn("Don't have permissions to write %s, skipping", pkg_info) return - - log.warn('Creating %s', pkg_info) - f = open(pkg_info, 'w') try: f.write(SETUPTOOLS_PKG_INFO) finally: @@ -435,7 +440,7 @@ def _fake_setuptools(): res = _patch_egg_dir(setuptools_location) if not res: return - log.warn('Patched done.') + log.warn('Patching complete.') _relaunch() @@ -443,8 +448,9 @@ def _relaunch(): log.warn('Relaunching...') # we have to relaunch the process # pip marker to avoid a relaunch bug - _cmd = ['-c', 'install', '--single-version-externally-managed'] - if sys.argv[:3] == _cmd: + _cmd1 = ['-c', 'install', '--single-version-externally-managed'] + _cmd2 = ['-c', 'install', '--record'] + if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: sys.argv[0] = 'setup.py' args = [sys.executable] + sys.argv sys.exit(subprocess.call(args)) @@ -529,7 +535,7 @@ def main(version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() tarball = download_setuptools(download_base=options.download_base) - _install(tarball, _build_install_args(options)) + return _install(tarball, _build_install_args(options)) if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/docs/conf.py b/docs/conf.py index 7b82a884..08fa643d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,9 +48,9 @@ copyright = u'2009-2011, The fellowship of the packaging' # built documents. # # The short X.Y version. -version = '0.6.29' +version = '0.6.31' # The full version, including alpha/beta/rc tags. -release = '0.6.29' +release = '0.6.31' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 31eedcb1..31ecc931 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -616,14 +616,20 @@ Dependencies that aren't in PyPI If your project depends on packages that aren't registered in PyPI, you may still be able to depend on them, as long as they are available for download -as an egg, in the standard distutils ``sdist`` format, or as a single ``.py`` -file. You just need to add some URLs to the ``dependency_links`` argument to +as: + +- an egg, in the standard distutils ``sdist`` format, +- a single ``.py`` file, or +- a VCS repository (Subversion, Mercurial, or Git). + +You just need to add some URLs to the ``dependency_links`` argument to ``setup()``. The URLs must be either: -1. direct download URLs, or -2. the URLs of web pages that contain direct download links +1. direct download URLs, +2. the URLs of web pages that contain direct download links, or +3. the repository's URL In general, it's better to link to web pages, because it is usually less complex to update a web page than to release a new version of your project. @@ -637,6 +643,27 @@ by replacing them with underscores.) EasyInstall will recognize this suffix and automatically create a trivial ``setup.py`` to wrap the single ``.py`` file as an egg. +In the case of a VCS checkout, you should also append ``#egg=project-version`` +in order to identify for what package that checkout should be used. You can +append ``@REV`` to the URL's path (before the fragment) to specify a revision. +Additionally, you can also force the VCS being used by prepending the URL with +a certain prefix. Currently available are: + +- ``svn+URL`` for Subversion, +- ``git+URL`` for Git, and +- ``hg+URL`` for Mercurial + +A more complete example would be: + + ``vcs+proto://host/path@revision#egg=project-version`` + +Be careful with the version. It should match the one inside the project files. +If you want do disregard the version, you have to omit it both in the +``requires`` and in the URL's fragment. + +This will do a checkout (or a clone, in Git and Mercurial parlance) to a +temporary folder and run ``setup.py bdist_egg``. + The ``dependency_links`` option takes the form of a list of URL strings. For example, the below will cause EasyInstall to search the specified page for eggs or source distributions, if the package's dependencies aren't already @@ -2423,7 +2450,12 @@ command:: python setup.py upload_docs --upload-dir=docs/build/html -As with any other ``setuptools`` based command, you can define useful +If no ``--upload-dir`` is given, ``upload_docs`` will attempt to run the +``build_sphinx`` command to generate uploadable documentation. +For the command to become available, `Sphinx <http://sphinx.pocoo.org/>`_ +must be installed in the same environment as distribute. + +As with other ``setuptools``-based commands, you can define useful defaults in the ``setup.cfg`` of your Python project, e.g.: .. code-block:: ini diff --git a/pkg_resources.py b/pkg_resources.py index 7495f1b6..a39a32dc 100644 --- a/pkg_resources.py +++ b/pkg_resources.py @@ -2504,16 +2504,7 @@ class DistInfoDistribution(Distribution): def _compute_dependencies(self): """Recompute this distribution's dependencies.""" - def dummy_marker(marker): - def marker_fn(environment=None, override=None): - # 'empty markers are True' heuristic won't install extra deps. - return not marker.strip() - marker_fn.__doc__ = marker - return marker_fn - try: - from _markerlib import compile as compile_marker - except ImportError: - compile_marker = dummy_marker + from _markerlib import compile as compile_marker dm = self.__dep_map = {None: []} reqs = [] @@ -20,7 +20,7 @@ try: except Exception: pass -VERSION = '0.6.29' +VERSION = '0.6.31' def get_next_version(): digits = map(int, VERSION.split('.')) @@ -3,6 +3,7 @@ import sys import os import textwrap +import re # Allow to run setup.py from another directory. os.chdir(os.path.dirname(os.path.abspath(__file__))) @@ -41,7 +42,7 @@ init_path = convert_path('setuptools/command/__init__.py') exec(open(init_path).read(), d) SETUP_COMMANDS = d['__all__'] -VERSION = "0.6.29" +VERSION = "0.6.31" from setuptools import setup, find_packages from setuptools.command.build_py import build_py as _build_py @@ -130,6 +131,22 @@ if _being_installed(): from distribute_setup import _before_install _before_install() +# return contents of reStructureText file with linked issue references +def _linkified(rstfile): + bitroot = 'http://bitbucket.org/tarek/distribute' + revision = re.compile(r'\b(issue\s*#?\d+)\b', re.M | re.I) + + rstext = open(rstfile).read() + + anchors = revision.findall(rstext) # ['Issue #43', ...] + anchors = sorted(set(anchors)) + rstext = revision.sub(r'`\1`_', rstext) + rstext += "\n" + for x in anchors: + issue = re.findall(r'\d+', x)[0] + rstext += '.. _`%s`: %s/issue/%s\n' % (x, bitroot, issue) + rstext += "\n" + return rstext dist = setup( name="distribute", @@ -139,7 +156,7 @@ dist = setup( author="The fellowship of the packaging", author_email="distutils-sig@python.org", license="PSF or ZPL", - long_description = open('README.txt').read() + open('CHANGES.txt').read(), + long_description = open('README.txt').read() + _linkified('CHANGES.txt'), keywords = "CPAN PyPI distutils eggs package management", url = "http://packages.python.org/distribute", test_suite = 'setuptools.tests', diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py index 5787753f..e22b25c0 100755 --- a/setuptools/archive_util.py +++ b/setuptools/archive_util.py @@ -158,6 +158,9 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): finally: f.close() del data + unix_attributes = info.external_attr >> 16 + if unix_attributes: + os.chmod(target, unix_attributes) finally: z.close() diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 0ee9c55b..17fae984 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -425,12 +425,12 @@ def scan_module(egg_dir, base, name, stubs): return True # Extension module pkg = base[len(egg_dir)+1:].replace(os.sep,'.') module = pkg+(pkg and '.' or '')+os.path.splitext(name)[0] - f = open(filename,'rb'); f.read(8) # skip magic & date - try: - code = marshal.load(f); f.close() - except ValueError: - f.seek(0); f.read(12) # skip magic & date & file size; file size added in Python 3.3 - code = marshal.load(f); f.close() + if sys.version_info < (3, 3): + skip = 8 # skip magic & date + else: + skip = 12 # skip magic & date & file size + f = open(filename,'rb'); f.read(skip) + code = marshal.load(f); f.close() safe = True symbols = dict.fromkeys(iter_symbols(code)) for bad in ['__file__', '__path__']: diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 505dd4f3..8751acd4 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -51,10 +51,8 @@ try: if self.distribution.use_2to3_exclude_fixers is not None: excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers) for fixer_name in excluded_fixers: - if fixer_name not in self.fixer_names: - log.warn("Excluded fixer %s not found", fixer_name) - continue - self.fixer_names.remove(fixer_name) + if fixer_name in self.fixer_names: + self.fixer_names.remove(fixer_name) except ImportError: class Mixin2to3: diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index f2260236..337532bc 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -1522,7 +1522,10 @@ def get_exe_prefixes(exe_filename): if name.endswith('-nspkg.pth'): continue if parts[0].upper() in ('PURELIB','PLATLIB'): - for pth in yield_lines(z.read(name)): + contents = z.read(name) + if sys.version_info >= (3,): + contents = contents.decode() + for pth in yield_lines(contents): pth = pth.strip().replace('\\','/') if not pth.startswith('import'): prefixes.append((('%s/%s/' % (parts[0],pth)), '')) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 46cdf4e0..0c2ea0cc 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -9,7 +9,7 @@ from distutils.errors import * from distutils import log from setuptools.command.sdist import sdist from distutils.util import convert_path -from distutils.filelist import FileList +from distutils.filelist import FileList as _FileList from pkg_resources import parse_requirements, safe_name, parse_version, \ safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename from sdist import walk_revctrl @@ -162,7 +162,12 @@ class egg_info(Command): os.unlink(filename) def tagged_version(self): - return safe_version(self.distribution.get_version() + self.vtags) + version = self.distribution.get_version() + # egg_info may be called more than once for a distribution, + # in which case the version string already contains all tags. + if self.vtags and version.endswith(self.vtags): + return safe_version(version) + return safe_version(version + self.vtags) def run(self): self.mkpath(self.egg_info) @@ -269,16 +274,28 @@ class egg_info(Command): self.broken_egg_info = self.egg_info self.egg_info = bei # make it work for now -class FileList(FileList): +class FileList(_FileList): """File list that accepts only existing, platform-independent paths""" def append(self, item): if item.endswith('\r'): # Fix older sdists built on Windows item = item[:-1] path = convert_path(item) - if os.path.exists(path): - self.files.append(path) + if sys.version_info >= (3,): + try: + if os.path.exists(path) or os.path.exists(path.encode('utf-8')): + self.files.append(path) + except UnicodeEncodeError: + # Accept UTF-8 filenames even if LANG=C + if os.path.exists(path.encode('utf-8')): + self.files.append(path) + else: + log.warn("'%s' not %s encodable -- skipping", path, + sys.getfilesystemencoding()) + else: + if os.path.exists(path): + self.files.append(path) @@ -318,6 +335,18 @@ class manifest_maker(sdist): by 'add_defaults()' and 'read_template()') to the manifest file named by 'self.manifest'. """ + # The manifest must be UTF-8 encodable. See #303. + if sys.version_info >= (3,): + files = [] + for file in self.filelist.files: + try: + file.encode("utf-8") + except UnicodeEncodeError: + log.warn("'%s' not UTF-8 encodable -- skipping" % file) + else: + files.append(file) + self.filelist.files = files + files = self.filelist.files if os.sep!='/': files = [f.replace(os.sep,'/') for f in files] diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index a176f635..2fa3771a 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -262,7 +262,39 @@ class sdist(_sdist): self.get_finalized_command('egg_info').save_version_info(dest) + def _manifest_is_not_generated(self): + # check for special comment used in 2.7.1 and higher + if not os.path.isfile(self.manifest): + return False + fp = open(self.manifest, 'rbU') + try: + first_line = fp.readline() + finally: + fp.close() + return first_line != '# file GENERATED by distutils, do NOT edit\n'.encode() + + def read_manifest(self): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + log.info("reading manifest file '%s'", self.manifest) + manifest = open(self.manifest, 'rbU') + for line in manifest: + # The manifest must contain UTF-8. See #303. + if sys.version_info >= (3,): + try: + line = line.decode('UTF-8') + except UnicodeDecodeError: + log.warn("%r not UTF-8 decodable -- skipping" % line) + continue + # ignore comments and blank lines + line = line.strip() + if line.startswith('#') or not line: + continue + self.filelist.append(line) + manifest.close() diff --git a/setuptools/command/test.py b/setuptools/command/test.py index e5cb9bb5..a02ac142 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -2,6 +2,7 @@ from setuptools import Command from distutils.errors import DistutilsOptionError import sys from pkg_resources import * +from pkg_resources import _namespace_packages from unittest import TestLoader, main class ScanningLoader(TestLoader): @@ -139,6 +140,22 @@ class test(Command): def run_tests(self): import unittest + + # Purge modules under test from sys.modules. The test loader will + # re-import them from the build location. Required when 2to3 is used + # with namespace packages. + if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False): + module = self.test_args[-1].split('.')[0] + if module in _namespace_packages: + del_modules = [] + if module in sys.modules: + del_modules.append(module) + module += '.' + for name in sys.modules: + if name.startswith(module): + del_modules.append(name) + map(sys.modules.__delitem__, del_modules) + loader_ep = EntryPoint.parse("x="+self.test_loader) loader_class = loader_ep.load(require=False) cks = loader_class() diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 213f7b58..98fb7233 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -9,10 +9,13 @@ import os import socket import zipfile import httplib -import base64 import urlparse import tempfile import sys +import shutil + +from base64 import standard_b64encode +from pkg_resources import iter_entry_points from distutils import log from distutils.errors import DistutilsOptionError @@ -22,20 +25,13 @@ try: except ImportError: from setuptools.command.upload import upload -_IS_PYTHON3 = sys.version > '3' - -try: - bytes -except NameError: - bytes = str -def b(str_or_bytes): - """Return bytes by either encoding the argument as ASCII or simply return - the argument as-is.""" - if not isinstance(str_or_bytes, bytes): - return str_or_bytes.encode('ascii') - else: - return str_or_bytes +# This is not just a replacement for byte literals +# but works as a general purpose encoder +def b(s, encoding='utf-8'): + if isinstance(s, unicode): + return s.encode(encoding) + return s class upload_docs(upload): @@ -51,40 +47,62 @@ class upload_docs(upload): ] boolean_options = upload.boolean_options + def has_sphinx(self): + if self.upload_dir is None: + for ep in iter_entry_points('distutils.commands', 'build_sphinx'): + return True + + sub_commands = [('build_sphinx', has_sphinx)] + def initialize_options(self): upload.initialize_options(self) self.upload_dir = None + self.target_dir = None def finalize_options(self): upload.finalize_options(self) if self.upload_dir is None: - build = self.get_finalized_command('build') - self.upload_dir = os.path.join(build.build_base, 'docs') - self.mkpath(self.upload_dir) - self.ensure_dirname('upload_dir') - self.announce('Using upload directory %s' % self.upload_dir) + if self.has_sphinx(): + build_sphinx = self.get_finalized_command('build_sphinx') + self.target_dir = build_sphinx.builder_target_dir + else: + build = self.get_finalized_command('build') + self.target_dir = os.path.join(build.build_base, 'docs') + else: + self.ensure_dirname('upload_dir') + self.target_dir = self.upload_dir + self.announce('Using upload directory %s' % self.target_dir) - def create_zipfile(self): - name = self.distribution.metadata.get_name() - tmp_dir = tempfile.mkdtemp() - tmp_file = os.path.join(tmp_dir, "%s.zip" % name) - zip_file = zipfile.ZipFile(tmp_file, "w") - for root, dirs, files in os.walk(self.upload_dir): - if root == self.upload_dir and not files: - raise DistutilsOptionError( - "no files found in upload directory '%s'" - % self.upload_dir) - for name in files: - full = os.path.join(root, name) - relative = root[len(self.upload_dir):].lstrip(os.path.sep) - dest = os.path.join(relative, name) - zip_file.write(full, dest) - zip_file.close() - return tmp_file + def create_zipfile(self, filename): + zip_file = zipfile.ZipFile(filename, "w") + try: + self.mkpath(self.target_dir) # just in case + for root, dirs, files in os.walk(self.target_dir): + if root == self.target_dir and not files: + raise DistutilsOptionError( + "no files found in upload directory '%s'" + % self.target_dir) + for name in files: + full = os.path.join(root, name) + relative = root[len(self.target_dir):].lstrip(os.path.sep) + dest = os.path.join(relative, name) + zip_file.write(full, dest) + finally: + zip_file.close() def run(self): - zip_file = self.create_zipfile() - self.upload_file(zip_file) + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + tmp_dir = tempfile.mkdtemp() + name = self.distribution.metadata.get_name() + zip_file = os.path.join(tmp_dir, "%s.zip" % name) + try: + self.create_zipfile(zip_file) + self.upload_file(zip_file) + finally: + shutil.rmtree(tmp_dir) def upload_file(self, filename): content = open(filename, 'rb').read() @@ -95,36 +113,33 @@ class upload_docs(upload): 'content': (os.path.basename(filename), content), } # set up the authentication - credentials = self.username + ':' + self.password - if _IS_PYTHON3: # base64 only works with bytes in Python 3. - encoded_creds = base64.encodebytes(credentials.encode('utf8')) - auth = bytes("Basic ") - else: - encoded_creds = base64.encodestring(credentials) - auth = "Basic " - auth += encoded_creds.strip() + credentials = b(self.username + ':' + self.password) + credentials = standard_b64encode(credentials) + if sys.version_info >= (3,): + credentials = credentials.decode('ascii') + auth = "Basic " + credentials # Build up the MIME payload for the POST data - boundary = b('--------------GHSKFJDLGDS7543FJKLFHRE75642756743254') - sep_boundary = b('\n--') + boundary + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b('\n--') + b(boundary) end_boundary = sep_boundary + b('--') body = [] - for key, values in data.items(): + for key, values in data.iteritems(): + title = '\nContent-Disposition: form-data; name="%s"' % key # handle multiple entries for the same name if type(values) != type([]): values = [values] for value in values: if type(value) is tuple: - fn = b(';filename="%s"' % value[0]) + title += '; filename="%s"' % value[0] value = value[1] else: - fn = b("") + value = b(value) body.append(sep_boundary) - body.append(b('\nContent-Disposition: form-data; name="%s"'%key)) - body.append(fn) + body.append(b(title)) body.append(b("\n\n")) - body.append(b(value)) - if value and value[-1] == b('\r'): + body.append(value) + if value and value[-1:] == b('\r'): body.append(b('\n')) # write an extra newline (lurve Macs) body.append(end_boundary) body.append(b("\n")) diff --git a/setuptools/dist.py b/setuptools/dist.py index 2061c0a2..6236d5be 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -641,6 +641,43 @@ class Distribution(_Distribution): name = name[:-6] yield name + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + import sys + + if sys.version_info < (3,) or self.help_commands: + return _Distribution.handle_display_options(self, option_order) + + # Stdout may be StringIO (e.g. in tests) + import io + if not isinstance(sys.stdout, io.TextIOWrapper): + return _Distribution.handle_display_options(self, option_order) + + # Don't wrap stdout if utf-8 is already the encoding. Provides + # workaround for #334. + if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): + return _Distribution.handle_display_options(self, option_order) + + # Print metadata in UTF-8 no matter the platform + encoding = sys.stdout.encoding + errors = sys.stdout.errors + newline = sys.platform != 'win32' and '\n' or None + line_buffering = sys.stdout.line_buffering + + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) + try: + return _Distribution.handle_display_options(self, option_order) + finally: + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), encoding, errors, newline, line_buffering) + + # Install it throughout the distutils for module in distutils.dist, distutils.core, distutils.cmd: module.Distribution = Distribution diff --git a/setuptools/package_index.py b/setuptools/package_index.py index d0896feb..0ee21e3b 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -657,6 +657,10 @@ class PackageIndex(Environment): # if scheme=='svn' or scheme.startswith('svn+'): return self._download_svn(url, filename) + elif scheme=='git' or scheme.startswith('git+'): + return self._download_git(url, filename) + elif scheme.startswith('hg+'): + return self._download_hg(url, filename) elif scheme=='file': return urllib.url2pathname(urlparse.urlparse(url)[2]) else: @@ -697,6 +701,55 @@ class PackageIndex(Environment): os.system("svn checkout -q %s %s" % (url, filename)) return filename + def _vcs_split_rev_from_url(self, url, pop_prefix=False): + scheme, netloc, path, query, frag = urlparse.urlsplit(url) + + scheme = scheme.split('+', 1)[-1] + + # Some fragment identification fails + path = path.split('#',1)[0] + + rev = None + if '@' in path: + path, rev = path.rsplit('@', 1) + + # Also, discard fragment + url = urlparse.urlunsplit((scheme, netloc, path, query, '')) + + return url, rev + + def _download_git(self, url, filename): + filename = filename.split('#',1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing git clone from %s to %s", url, filename) + os.system("git clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Checking out %s", rev) + os.system("(cd %s && git checkout --quiet %s)" % ( + filename, + rev, + )) + + return filename + + def _download_hg(self, url, filename): + filename = filename.split('#',1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing hg clone from %s to %s", url, filename) + os.system("hg clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Updating to %s", rev) + os.system("(cd %s && hg up -C -r %s >&-)" % ( + filename, + rev, + )) + + return filename + def debug(self, msg, *args): log.debug(msg, *args) @@ -779,6 +832,11 @@ def open_with_auth(url): scheme, netloc, path, params, query, frag = urlparse.urlparse(url) + # Double scheme does not raise on Mac OS X as revealed by a + # failing test. We would expect "nonnumeric port". Refs #20. + if netloc.endswith(':'): + raise httplib.InvalidURL("nonnumeric port: ''") + if scheme in ('http', 'https'): auth, host = urllib2.splituser(netloc) else: @@ -859,4 +917,4 @@ def local_open(url): -# this line is a kludge to keep the trailing blank lines for pje's editor
\ No newline at end of file +# this line is a kludge to keep the trailing blank lines for pje's editor diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py index 70dce2d4..fcb78c36 100644 --- a/setuptools/tests/test_dist_info.py +++ b/setuptools/tests/test_dist_info.py @@ -7,7 +7,7 @@ import unittest import textwrap try: - import _markerlib + import ast except: pass @@ -34,8 +34,8 @@ class TestDistInfo(unittest.TestCase): assert versioned.version == '2.718' # from filename assert unversioned.version == '0.3' # from METADATA - @skipIf('_markerlib' not in globals(), - "_markerlib is used to test conditional dependencies (Python >= 2.5)") + @skipIf('ast' not in globals(), + "ast is used to test conditional dependencies (Python >= 2.6)") def test_conditional_dependencies(self): requires = [pkg_resources.Requirement.parse('splort==4'), pkg_resources.Requirement.parse('quux>=1.1')] @@ -50,7 +50,8 @@ class TestDistInfo(unittest.TestCase): versioned = os.path.join(self.tmpdir, 'VersionedDistribution-2.718.dist-info') os.mkdir(versioned) - open(os.path.join(versioned, 'METADATA'), 'w+').write(DALS( + metadata_file = open(os.path.join(versioned, 'METADATA'), 'w+') + metadata_file.write(DALS( """ Metadata-Version: 1.2 Name: VersionedDistribution @@ -58,11 +59,13 @@ class TestDistInfo(unittest.TestCase): Provides-Extra: baz Requires-Dist: quux (>=1.1); extra == 'baz' """)) + metadata_file.close() unversioned = os.path.join(self.tmpdir, 'UnversionedDistribution.dist-info') os.mkdir(unversioned) - open(os.path.join(unversioned, 'METADATA'), 'w+').write(DALS( + metadata_file = open(os.path.join(unversioned, 'METADATA'), 'w+') + metadata_file.write(DALS( """ Metadata-Version: 1.2 Name: UnversionedDistribution @@ -71,6 +74,7 @@ class TestDistInfo(unittest.TestCase): Provides-Extra: baz Requires-Dist: quux (>=1.1); extra == 'baz' """)) + metadata_file.close() def tearDown(self): shutil.rmtree(self.tmpdir) diff --git a/setuptools/tests/test_markerlib.py b/setuptools/tests/test_markerlib.py index 4cce0430..7ff2f584 100644 --- a/setuptools/tests/test_markerlib.py +++ b/setuptools/tests/test_markerlib.py @@ -3,14 +3,14 @@ import unittest from setuptools.tests.py26compat import skipIf try: - import _ast + import ast except ImportError: pass class TestMarkerlib(unittest.TestCase): - @skipIf('_ast' not in globals(), - "ast not available (Python < 2.5?)") + @skipIf('ast' not in globals(), + "ast not available (Python < 2.6?)") def test_markers(self): from _markerlib import interpret, default_environment, compile @@ -62,37 +62,3 @@ class TestMarkerlib(unittest.TestCase): statement = "python_version == '5'" self.assertEqual(compile(statement).__doc__, statement) - @skipIf('_ast' not in globals(), - "ast not available (Python < 2.5?)") - def test_ast(self): - try: - import ast, nose - raise nose.SkipTest() - except ImportError: - pass - - # Nonsensical code coverage tests. - import _markerlib._markers_ast as _markers_ast - - class Node(_ast.AST): - _fields = ('bogus') - list(_markers_ast.iter_fields(Node())) - - class Node2(_ast.AST): - def __init__(self): - self._fields = ('bogus',) - self.bogus = [Node()] - - class NoneTransformer(_markers_ast.NodeTransformer): - def visit_Attribute(self, node): - return None - - def visit_Str(self, node): - return None - - def visit_Node(self, node): - return [] - - NoneTransformer().visit(_markers_ast.parse('a.b = "c"')) - NoneTransformer().visit(Node2()) - diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 8d9ed922..a9d5d6e5 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """sdist tests""" @@ -6,10 +7,13 @@ import shutil import sys import tempfile import unittest +import urllib +import unicodedata from StringIO import StringIO from setuptools.command.sdist import sdist +from setuptools.command.egg_info import manifest_maker from setuptools.dist import Distribution @@ -28,7 +32,52 @@ setup(**%r) """ % SETUP_ATTRS +if sys.version_info >= (3,): + LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1') +else: + LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py' + + +# Cannot use context manager because of Python 2.4 +def quiet(): + global old_stdout, old_stderr + old_stdout, old_stderr = sys.stdout, sys.stderr + sys.stdout, sys.stderr = StringIO(), StringIO() + +def unquiet(): + sys.stdout, sys.stderr = old_stdout, old_stderr + + +# Fake byte literals for Python <= 2.5 +def b(s, encoding='utf-8'): + if sys.version_info >= (3,): + return s.encode(encoding) + return s + + +# Convert to POSIX path +def posix(path): + if sys.version_info >= (3,) and not isinstance(path, unicode): + return path.replace(os.sep.encode('ascii'), b('/')) + else: + return path.replace(os.sep, '/') + + +# HFS Plus uses decomposed UTF-8 +def decompose(path): + if isinstance(path, unicode): + return unicodedata.normalize('NFD', path) + try: + path = path.decode('utf-8') + path = unicodedata.normalize('NFD', path) + path = path.encode('utf-8') + except UnicodeError: + pass # Not UTF-8 + return path + + class TestSdistTest(unittest.TestCase): + def setUp(self): self.temp_dir = tempfile.mkdtemp() f = open(os.path.join(self.temp_dir, 'setup.py'), 'w') @@ -62,18 +111,273 @@ class TestSdistTest(unittest.TestCase): cmd.ensure_finalized() # squelch output - old_stdout = sys.stdout - old_stderr = sys.stderr - sys.stdout = StringIO() - sys.stderr = StringIO() + quiet() try: cmd.run() finally: - sys.stdout = old_stdout - sys.stderr = old_stderr + unquiet() manifest = cmd.filelist.files + self.assertTrue(os.path.join('sdist_test', 'a.txt') in manifest) + self.assertTrue(os.path.join('sdist_test', 'b.txt') in manifest) + self.assertTrue(os.path.join('sdist_test', 'c.rst') not in manifest) + + def test_manifest_is_written_with_utf8_encoding(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + mm = manifest_maker(dist) + mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + os.mkdir('sdist_test.egg-info') + + # UTF-8 filename + filename = os.path.join('sdist_test', 'smörbröd.py') + + # Add UTF-8 filename and write manifest + quiet() + try: + mm.run() + mm.filelist.files.append(filename) + mm.write_manifest() + finally: + unquiet() + + manifest = open(mm.manifest, 'rbU') + contents = manifest.read() + manifest.close() + + # The manifest should be UTF-8 encoded + try: + u_contents = contents.decode('UTF-8') + except UnicodeDecodeError, e: + self.fail(e) + + # The manifest should contain the UTF-8 filename + if sys.version_info >= (3,): + self.assertTrue(posix(filename) in u_contents) + else: + self.assertTrue(posix(filename) in contents) + + # Python 3 only + if sys.version_info >= (3,): + + def test_write_manifest_allows_utf8_filenames(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + mm = manifest_maker(dist) + mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + os.mkdir('sdist_test.egg-info') + + # UTF-8 filename + filename = os.path.join(b('sdist_test'), b('smörbröd.py')) + + # Add filename and write manifest + quiet() + try: + mm.run() + u_filename = filename.decode('utf-8') + mm.filelist.files.append(u_filename) + # Re-write manifest + mm.write_manifest() + finally: + unquiet() + + manifest = open(mm.manifest, 'rbU') + contents = manifest.read() + manifest.close() + + # The manifest should be UTF-8 encoded + try: + contents.decode('UTF-8') + except UnicodeDecodeError, e: + self.fail(e) + + # The manifest should contain the UTF-8 filename + self.assertTrue(posix(filename) in contents) + + # The filelist should have been updated as well + self.assertTrue(u_filename in mm.filelist.files) + + def test_write_manifest_skips_non_utf8_filenames(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + mm = manifest_maker(dist) + mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + os.mkdir('sdist_test.egg-info') + + # Latin-1 filename + filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) + + # Add filename with surrogates and write manifest + quiet() + try: + mm.run() + u_filename = filename.decode('utf-8', 'surrogateescape') + mm.filelist.files.append(u_filename) + # Re-write manifest + mm.write_manifest() + finally: + unquiet() + + manifest = open(mm.manifest, 'rbU') + contents = manifest.read() + manifest.close() + + # The manifest should be UTF-8 encoded + try: + contents.decode('UTF-8') + except UnicodeDecodeError, e: + self.fail(e) + + # The Latin-1 filename should have been skipped + self.assertFalse(posix(filename) in contents) + + # The filelist should have been updated as well + self.assertFalse(u_filename in mm.filelist.files) + + def test_manifest_is_read_with_utf8_encoding(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # Create manifest + quiet() + try: + cmd.run() + finally: + unquiet() + + # Add UTF-8 filename to manifest + filename = os.path.join(b('sdist_test'), b('smörbröd.py')) + cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + manifest = open(cmd.manifest, 'ab') + manifest.write(b('\n')+filename) + manifest.close() + + # The file must exist to be included in the filelist + open(filename, 'w').close() + + # Re-read manifest + cmd.filelist.files = [] + quiet() + try: + cmd.read_manifest() + finally: + unquiet() + + # The filelist should contain the UTF-8 filename + if sys.version_info >= (3,): + filename = filename.decode('utf-8') + self.assertTrue(filename in cmd.filelist.files) + + # Python 3 only + if sys.version_info >= (3,): + + def test_read_manifest_skips_non_utf8_filenames(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # Create manifest + quiet() + try: + cmd.run() + finally: + unquiet() + + # Add Latin-1 filename to manifest + filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) + cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + manifest = open(cmd.manifest, 'ab') + manifest.write(b('\n')+filename) + manifest.close() + + # The file must exist to be included in the filelist + open(filename, 'w').close() + + # Re-read manifest + cmd.filelist.files = [] + quiet() + try: + try: + cmd.read_manifest() + except UnicodeDecodeError, e: + self.fail(e) + finally: + unquiet() + + # The Latin-1 filename should have been skipped + filename = filename.decode('latin-1') + self.assertFalse(filename in cmd.filelist.files) + + def test_sdist_with_utf8_encoded_filename(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # UTF-8 filename + filename = os.path.join(b('sdist_test'), b('smörbröd.py')) + open(filename, 'w').close() + + quiet() + try: + cmd.run() + finally: + unquiet() + + if sys.platform == 'darwin': + filename = decompose(filename) + + if sys.version_info >= (3,): + if sys.platform == 'win32': + # Python 3 mangles the UTF-8 filename + filename = filename.decode('cp1252') + self.assertTrue(filename in cmd.filelist.files) + else: + filename = filename.decode('utf-8') + self.assertTrue(filename in cmd.filelist.files) + else: + self.assertTrue(filename in cmd.filelist.files) + + def test_sdist_with_latin1_encoded_filename(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # Latin-1 filename + filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) + open(filename, 'w').close() + + quiet() + try: + cmd.run() + finally: + unquiet() + + if sys.version_info >= (3,): + filename = filename.decode('latin-1') + if sys.platform == 'win32': + # Latin-1 is similar to Windows-1252 + self.assertTrue(filename in cmd.filelist.files) + else: + # The Latin-1 filename should have been skipped + self.assertFalse(filename in cmd.filelist.files) + else: + # No conversion takes place under Python 2 and the file + # is included. We shall keep it that way for BBB. + self.assertTrue(filename in cmd.filelist.files) + + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) - self.assert_(os.path.join('sdist_test', 'a.txt') in manifest) - self.assert_(os.path.join('sdist_test', 'b.txt') in manifest) - self.assert_(os.path.join('sdist_test', 'c.rst') not in manifest) diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py index 8b2dc892..769f16cc 100644 --- a/setuptools/tests/test_upload_docs.py +++ b/setuptools/tests/test_upload_docs.py @@ -54,12 +54,19 @@ class TestUploadDocsTest(unittest.TestCase): cmd = upload_docs(dist) cmd.upload_dir = self.upload_dir - zip_file = cmd.create_zipfile() + cmd.target_dir = self.upload_dir + tmp_dir = tempfile.mkdtemp() + tmp_file = os.path.join(tmp_dir, 'foo.zip') + try: + zip_file = cmd.create_zipfile(tmp_file) - assert zipfile.is_zipfile(zip_file) + assert zipfile.is_zipfile(tmp_file) - zip_f = zipfile.ZipFile(zip_file) # woh... + zip_file = zipfile.ZipFile(tmp_file) # woh... - assert zip_f.namelist() == ['index.html'] + assert zip_file.namelist() == ['index.html'] + zip_file.close() + finally: + shutil.rmtree(tmp_dir) @@ -1,5 +1,5 @@ def __boot(): - import sys, imp, os, os.path + import sys, os, os.path PYTHONPATH = os.environ.get('PYTHONPATH') if PYTHONPATH is None or (sys.platform=='win32' and not PYTHONPATH): PYTHONPATH = [] @@ -23,6 +23,7 @@ def __boot(): break else: try: + import imp # Avoid import loop in Python >= 3.3 stream, path, descr = imp.find_module('site',[item]) except ImportError: continue |
