diff options
53 files changed, 3248 insertions, 1484 deletions
@@ -49,13 +49,25 @@ be6f65eea9c10ce78b6698d8c220b6e5de577292 0.6.37 2b26ec8909bff210f47c5f8fc620bc505e1610b5 0.6.37 f0d502a83f6c83ba38ad21c15a849c2daf389ec7 0.6.38 d737b2039c5f92af8000f78bbc80b6a5183caa97 0.6.39 +9b2e2aa06e058c63e06c5e42a7f279ddae2dfb7d 0.7b1 0a783fa0dceb95b5fc743e47c2d89c1523d0afb7 0.6.40 ad107e9b4beea24516ac4e1e854696e586fe279d 0.6.41 f30167716b659f96c5e0b7ea3d5be2bcff8c0eac 0.6.42 +8951daac6c1bc7b24c7fb054fd369f2c5b88cdb3 0.7b2 35086ee286732b0f63d2be18d9f26f2734586e2d 0.6.43 +63e4eb2d61204f77f9b557201a0efa187b05a611 0.7b3 73aa98aee6bbc4a9d19a334a8ac928dece7799c6 0.6.44 +53b4ac9a748aa28893aaca42c41e5e99568667bb 0.7b4 ddca71ae5ceb9b14512dc60ea83802c10e224cf0 0.6.45 +7f2c08e9ca22023d1499c512fccc1513813b7dc4 0.7 +024dd30ed702135f5328975042566e48cc479d7d 0.7.1 +d04c05f035e3a5636006fc34f4be7e6c77035d17 0.7.2 +d212e48e0cef689acba57ed017289c027660b23c 0.7.3 +85640475dda0621f20e11db0995fa07f51744a98 0.7.4 b57e5ba934767dd498669b17551678081b3047b5 0.6.46 +dd5bbc116c53d3732d22f983e7ca6d8cfabd3b08 0.7.5 ee2c967017024197b38e39ced852808265387a4b 0.6.47 +48d3d26cbea68e21c96e51f01092e8fdead5cd60 0.7.6 cae9127e0534fc46d7ddbc11f68dc88fd9311459 0.6.48 +1506fa538fff01e70424530a32a44e070720cf3c 0.7.7 f657df1f1ed46596d236376649c99a470662b4ba 0.6.49 diff --git a/CHANGES.txt b/CHANGES.txt index 67430518..1a47f4db 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,79 @@ CHANGES ======= +----- +0.7.7 +----- + +* Distribute #375: Repair AttributeError created in last release (redo). +* Issue #30: Added test for get_cache_path. + +----- +0.7.6 +----- + +* Distribute #375: Repair AttributeError created in last release. + +----- +0.7.5 +----- + +* Issue #21: Restore Python 2.4 compatibility in ``test_easy_install``. +* Distribute #375: Merged additional warning from Distribute 0.6.46. +* Now honor the environment variable + ``SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT`` in addition to the now + deprecated ``DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT``. + +----- +0.7.4 +----- + +* Issue #20: Fix comparison of parsed SVN version on Python 3. + +----- +0.7.3 +----- + +* Issue #1: Disable installation of Windows-specific files on non-Windows systems. +* Use new sysconfig module with Python 2.7 or >=3.2. + +----- +0.7.2 +----- + +* Issue #14: Use markerlib when the `parser` module is not available. +* Issue #10: ``ez_setup.py`` now uses HTTPS to download setuptools from PyPI. + +----- +0.7.1 +----- + +* Fix NameError (Issue #3) again - broken in bad merge. + +--- +0.7 +--- + +* Merged Setuptools and Distribute. See docs/merge.txt for details. + +Added several features that were slated for setuptools 0.6c12: + +* Index URL now defaults to HTTPS. +* Added experimental environment marker support. Now clients may designate a + PEP-426 environment marker for "extra" dependencies. Setuptools uses this + feature in ``setup.py`` for optional SSL and certificate validation support + on older platforms. Based on Distutils-SIG discussions, the syntax is + somewhat tentative. There should probably be a PEP with a firmer spec before + the feature should be considered suitable for use. +* Added support for SSL certificate validation when installing packages from + an HTTPS service. + +----- +0.7b4 +----- + +* Issue #3: Fixed NameError in SSL support. + ------ 0.6.49 ------ @@ -488,7 +561,7 @@ how it parses version numbers. This closes issue #52. * Added an upload_docs command to easily upload project documentation to - PyPI's http://packages.python.org. This close issue #56. + PyPI's https://pythonhosted.org. This close issue #56. * Fixed a bootstrap bug on the use_setuptools() API. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d6aa151d..8515babe 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -21,6 +21,7 @@ Contributors * Martin von Löwis * Noufal Ibrahim * Pete Hollobon +* Phillip J. Eby * Philip Jenvey * Philip Thiem * Reinout van Rees diff --git a/DEVGUIDE.txt b/DEVGUIDE.txt index 8dcabfd1..f96d8115 100644 --- a/DEVGUIDE.txt +++ b/DEVGUIDE.txt @@ -2,11 +2,11 @@ Quick notes for contributors ============================ -Distribute is using Mercurial. +Setuptools is developed using the DVCS Mercurial. Grab the code at bitbucket:: - $ hg clone https://bitbucket.org/tarek/distribute + $ hg clone https://bitbucket.org/pypa/setuptools If you want to contribute changes, we recommend you fork the repository on bitbucket, commit the changes to your repository, and then make a pull request @@ -14,8 +14,8 @@ on bitbucket. If you make some changes, don't forget to: - add a note in CHANGES.txt -And remember that 0.6 (the only development line) is only bug fixes, and the -APIs should be fully backward compatible with Setuptools. +Please commit bug-fixes against the current maintenance branch and new +features to the default branch. You can run the tests via:: diff --git a/MANIFEST.in b/MANIFEST.in index 68337800..ff102123 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ recursive-include setuptools *.py *.txt *.exe *.xml recursive-include tests *.py *.c *.pyx *.txt -recursive-include setuptools/tests *.html +recursive-include setuptools/tests *.html entries* recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html recursive-include _markerlib *.py include *.py @@ -1,206 +1,170 @@ =============================== -Installing and Using Distribute +Installing and Using Setuptools =============================== .. contents:: **Table of Contents** ------------ -Disclaimers ------------ - -About the fork -============== - -`Distribute` is a now deprecated fork of the `Setuptools` project. - -Distribute was intended to replace Setuptools as the standard method -for working with Python module distributions. The code has since been merged -back into the parent project as is being maintained by the community at large. - -`Distribute` is now being maintained as a branch in the `Setuptools -repository <https://bitbucket.org/pypa/setuptools>`_. - -More documentation -================== - -You can get more information in the Sphinx-based documentation, located -at http://packages.python.org/distribute. This documentation includes the old -Setuptools documentation that is slowly replaced, and brand new content. - -About the installation process -============================== - -The `Distribute` installer modifies your installation by de-activating an -existing installation of `Setuptools` in a bootstrap process. This process -has been tested in various installation schemes and contexts but in case of a -bug during this process your Python installation might be left in a broken -state. Since all modified files and directories are copied before the -installation starts, you will be able to get back to a normal state by reading -the instructions in the `Uninstallation instructions`_ section. - -In any case, it is recommended to save you `site-packages` directory before -you start the installation of `Distribute`. ------------------------- Installation Instructions ------------------------- -Distribute is only released as a source distribution. - -It can be installed using pip, and can be done so with the source tarball, -or by using the ``distribute_setup.py`` script provided online. +Upgrading from Distribute +========================= -``distribute_setup.py`` is the simplest and preferred way on all systems. +Currently, Distribute disallows installing Setuptools 0.7+ over Distribute. +You must first uninstall any active version of Distribute first (see +`Uninstalling`_). -distribute_setup.py -=================== +Upgrading from Setuptools 0.6 +============================= -Download -`distribute_setup.py <http://python-distribute.org/distribute_setup.py>`_ -and execute it, using the Python interpreter of your choice. +Upgrading from prior versions of Setuptools is supported. Initial reports +good success in this regard. -If your shell has the ``curl`` program you can do:: +Windows +======= - $ curl -O http://python-distribute.org/distribute_setup.py - $ python distribute_setup.py +The recommended way to install setuptools on Windows is to download +`ez_setup.py`_ and run it. The script will download the appropriate .egg +file and install it for you. -Notice this file is also provided in the source release. +.. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/0.7.8/ez_setup.py -pip -=== +For best results, uninstall previous versions FIRST (see `Uninstalling`_). -Run easy_install or pip:: +Once installation is complete, you will find an ``easy_install.exe`` program in +your Python ``Scripts`` subdirectory. For simple invocation and best results, +add this directory to your ``PATH`` environment variable, if it is not already +present. - $ pip install distribute -Source installation -=================== +Unix-based Systems including Mac OS X +===================================== -Download the source tarball, uncompress it, then run the install command:: +Download `ez_setup.py`_ and run it using the target Python version. The script +will download the appropriate version and install it for you:: - $ curl -O http://pypi.python.org/packages/source/d/distribute/distribute-0.6.50.tar.gz - $ tar -xzvf distribute-0.6.50.tar.gz - $ cd distribute-0.6.50 - $ python setup.py install + > wget https://bitbucket.org/pypa/setuptools/raw/0.7.8/ez_setup.py -O - | python ---------------------------- -Uninstallation Instructions ---------------------------- +Note that you will may need to invoke the command with superuser privileges to +install to the system Python. -Like other distutils-based distributions, Distribute doesn't provide an -uninstaller yet. It's all done manually! We are all waiting for PEP 376 -support in Python. +Alternatively, on Python 2.6 and later, Setuptools may be installed to a +user-local path:: -Distribute is installed in three steps: - -1. it gets out of the way an existing installation of Setuptools -2. it installs a `fake` setuptools installation -3. it installs distribute - -Distribute can be removed like this: - -- remove the ``distribute*.egg`` file located in your site-packages directory -- remove the ``setuptools.pth`` file located in you site-packages directory -- remove the easy_install script located in you ``sys.prefix/bin`` directory -- remove the ``setuptools*.egg`` directory located in your site-packages directory, - if any. - -If you want to get back to setuptools: - -- reinstall setuptools using its instruction. - -Lastly: - -- remove the *.OLD.* directory located in your site-packages directory if any, - **once you have checked everything was working correctly again**. - -------------------------- -Quick help for developers -------------------------- + > wget https://bitbucket.org/pypa/setuptools/raw/0.7.8/ez_setup.py + > python ez_setup.py --user -To create an egg which is compatible with Distribute, use the same -practice as with Setuptools, e.g.:: - from setuptools import setup +Advanced Installation +===================== - setup(... - ) +For more advanced installation options, such as installing to custom +locations or prefixes, download and extract the source +tarball from `Setuptools on PyPI <https://pypi.python.org/pypi/setuptools>`_ +and run setup.py with any supported distutils and Setuptools options. +For example:: -To use `pkg_resources` to access data files in the egg, you should -require the Setuptools distribution explicitly:: + setuptools-0.7.8$ python setup.py --prefix=/opt/setuptools - from setuptools import setup +Use ``--help`` to get a full options list, but we recommend consulting +the `EasyInstall manual`_ for detailed instructions, especially `the section +on custom installation locations`_. - setup(... - install_requires=['setuptools'] - ) +.. _EasyInstall manual: https://pythonhosted.org/setuptools/EasyInstall +.. _the section on custom installation locations: https://pythonhosted.org/setuptools/EasyInstall#custom-installation-locations -Only if you need Distribute-specific functionality should you depend -on it explicitly. In this case, replace the Setuptools dependency:: - from setuptools import setup +Downloads +========= - setup(... - install_requires=['distribute'] - ) +All setuptools downloads can be found at `the project's home page in the Python +Package Index`_. Scroll to the very bottom of the page to find the links. ------------ -Install FAQ ------------ +.. _the project's home page in the Python Package Index: https://pypi.python.org/pypi/setuptools -- **Why is Distribute wrapping my Setuptools installation?** +In addition to the PyPI downloads, the development version of ``setuptools`` +is available from the `Bitbucket repo`_, and in-development versions of the +`0.6 branch`_ are available as well. - Since Distribute is a fork, and since it provides the same package - and modules, it renames the existing Setuptools egg and inserts a - new one which merely wraps the Distribute code. This way, full - backwards compatibility is kept for packages which rely on the - Setuptools modules. +.. _Bitbucket repo: https://bitbucket.org/pypa/setuptools/get/default.tar.gz#egg=setuptools-dev +.. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 - At the same time, packages can meet their dependency on Setuptools - without actually installing it (which would disable Distribute). +Uninstalling +============ -- **How does Distribute interact with virtualenv?** +On Windows, if Setuptools was installed using an ``.exe`` or ``.msi`` +installer, simply use the uninstall feature of "Add/Remove Programs" in the +Control Panel. - Everytime you create a virtualenv it will install setuptools by default. - You either need to re-install Distribute in it right after or pass the - ``--distribute`` option when creating it. +Otherwise, to uninstall Setuptools or Distribute, regardless of the Python +version, delete all ``setuptools*`` and ``distribute*`` files and +directories from your system's ``site-packages`` directory +(and any other ``sys.path`` directories) FIRST. - Once installed, your virtualenv will use Distribute transparently. +If you are upgrading or otherwise plan to re-install Setuptools or Distribute, +nothing further needs to be done. If you want to completely remove Setuptools, +you may also want to remove the 'easy_install' and 'easy_install-x.x' scripts +and associated executables installed to the Python scripts directory. - Although, if you have Setuptools installed in your system-wide Python, - and if the virtualenv you are in was generated without the `--no-site-packages` - option, the Distribute installation will stop. +-------------------------------- +Using Setuptools and EasyInstall +-------------------------------- - You need in this case to build a virtualenv with the `--no-site-packages` - option or to install `Distribute` globally. +Here are some of the available manuals, tutorials, and other resources for +learning about Setuptools, Python Eggs, and EasyInstall: -- **How does Distribute interacts with zc.buildout?** +* `The EasyInstall user's guide and reference manual`_ +* `The setuptools Developer's Guide`_ +* `The pkg_resources API reference`_ +* `Package Compatibility Notes`_ (user-maintained) +* `The Internal Structure of Python Eggs`_ - You can use Distribute in your zc.buildout, with the --distribute option, - starting at zc.buildout 1.4.2:: +Questions, comments, and bug reports should be directed to the `distutils-sig +mailing list`_. If you have written (or know of) any tutorials, documentation, +plug-ins, or other resources for setuptools users, please let us know about +them there, so this reference list can be updated. If you have working, +*tested* patches to correct problems or add features, you may submit them to +the `setuptools bug tracker`_. - $ python bootstrap.py --distribute +.. _setuptools bug tracker: https://bitbucket.org/pypa/setuptools/issues +.. _Package Compatibility Notes: https://pythonhosted.org/setuptools/PackageNotes +.. _The Internal Structure of Python Eggs: https://pythonhosted.org/setuptools/formats.html +.. _The setuptools Developer's Guide: https://pythonhosted.org/setuptools/setuptools.html +.. _The pkg_resources API reference: https://pythonhosted.org/setuptools/pkg_resources.html +.. _The EasyInstall user's guide and reference manual: https://pythonhosted.org/setuptools/easy_install.html +.. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ - For previous zc.buildout versions, *the only thing* you need to do - is use the bootstrap at `http://python-distribute.org/bootstrap.py`. Run - that bootstrap and ``bin/buildout`` (and all other buildout-generated - scripts) will transparently use distribute instead of setuptools. You do - not need a specific buildout release. - A shared eggs directory is no problem (since 0.6.6): the setuptools egg is - left in place unmodified. So other buildouts that do not yet use the new - bootstrap continue to work just fine. And there is no need to list - ``distribute`` somewhere in your eggs: using the bootstrap is enough. +------- +Credits +------- - The source code for the bootstrap script is located at - `http://bitbucket.org/tarek/buildout-distribute`. +* The original design for the ``.egg`` format and the ``pkg_resources`` API was + co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first + version of ``pkg_resources``, and supplied the OS X operating system version + compatibility algorithm. +* Ian Bicking implemented many early "creature comfort" features of + easy_install, including support for downloading via Sourceforge and + Subversion repositories. Ian's comments on the Web-SIG about WSGI + application deployment also inspired the concept of "entry points" in eggs, + and he has given talks at PyCon and elsewhere to inform and educate the + community about eggs and setuptools. +* Jim Fulton contributed time and effort to build automated tests of various + aspects of ``easy_install``, and supplied the doctests for the command-line + ``.exe`` wrappers on Windows. ------------------------------ -Feedback and getting involved ------------------------------ +* Phillip J. Eby is the principal author and maintainer of setuptools, and + first proposed the idea of an importable binary distribution format for + Python application plug-ins. + +* Significant parts of the implementation of setuptools were funded by the Open + Source Applications Foundation, to provide a plug-in infrastructure for the + Chandler PIM application. In addition, many OSAF staffers (such as Mike + "Code Bear" Taylor) contributed their time and stress as guinea pigs for the + use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) -- Mailing list: http://mail.python.org/mailman/listinfo/distutils-sig -- Issue tracker: http://bitbucket.org/tarek/distribute/issues/ -- Code Repository: http://bitbucket.org/pypa/setuptools?at=distribute +.. _files: diff --git a/distribute_setup.py b/distribute_setup.py deleted file mode 100644 index 647c2f80..00000000 --- a/distribute_setup.py +++ /dev/null @@ -1,556 +0,0 @@ -#!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 shutil -import sys -import time -import fnmatch -import tempfile -import tarfile -import optparse - -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.50" -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, install_args=()): - # 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', *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): - # 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) - shutil.rmtree(tmpdir) - # 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 - - # Setuptools 0.7b and later is a suitable (and preferable) - # substitute for any Distribute version. - try: - pkg_resources.require("setuptools>=0.7b") - return - except (pkg_resources.DistributionNotFound, - pkg_resources.VersionConflict): - pass - - 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""" - f = open(path) - existing_content = f.read() - f.close() - 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): - f = open(path) - existing_content = f.read() - f.close() - return existing_content == content - - -def _rename_path(path): - new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s to %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('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) - 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) - try: - f = open(pkg_info, 'w') - except EnvironmentError: - log.warn("Don't have permissions to write %s, skipping", pkg_info) - return - 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('Patching complete.') - _relaunch() - - -def _relaunch(): - log.warn('Relaunching...') - # we have to relaunch the process - # pip marker to avoid a relaunch bug - _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)) - - -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 _build_install_args(options): - """ - Build the arguments to 'python setup.py install' on the distribute package - """ - install_args = [] - if options.user_install: - if sys.version_info < (2, 6): - log.warn("--user requires Python 2.6 or later") - raise SystemExit(1) - install_args.append('--user') - return install_args - -def _parse_args(): - """ - Parse the command line for options - """ - parser = optparse.OptionParser() - parser.add_option( - '--user', dest='user_install', action='store_true', default=False, - help='install in user site package (requires Python 2.6 or later)') - parser.add_option( - '--download-base', dest='download_base', metavar="URL", - default=DEFAULT_URL, - help='alternative URL from where to download the distribute package') - options, args = parser.parse_args() - # positional arguments are ignored - return options - -def main(version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - options = _parse_args() - tarball = download_setuptools(download_base=options.download_base) - return _install(tarball, _build_install_args(options)) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/docs/_templates/indexsidebar.html b/docs/_templates/indexsidebar.html index 932909f3..a27c85fe 100644 --- a/docs/_templates/indexsidebar.html +++ b/docs/_templates/indexsidebar.html @@ -1,8 +1,8 @@ -<h3>Download</h3> +<h3>Download</h3> -<p>Current version: <b>{{ version }}</b></p> -<p>Get Distribute from the <a href="http://pypi.python.org/pypi/distribute"> Python Package Index</a> +<p>Current version: <b>{{ version }}</b></p> +<p>Get Setuptools from the <a href="https://pypi.python.org/pypi/setuptools"> Python Package Index</a> <h3>Questions? Suggestions? Contributions?</h3> -<p>Visit the <a href="http://bitbucket.org/tarek/distribute">Distribute project page</a> </p> +<p>Visit the <a href="https://bitbucket.org/pypa/setuptools">Setuptools project page</a> </p> diff --git a/docs/conf.py b/docs/conf.py index 41323122..8bbf4bea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Distribute documentation build configuration file, created by +# Setuptools documentation build configuration file, created by # sphinx-quickstart on Fri Jul 17 14:22:37 2009. # # This file is execfile()d with the current directory set to its containing dir. @@ -40,17 +40,17 @@ source_suffix = '.txt' master_doc = 'index' # General information about the project. -project = u'Distribute' -copyright = u'2009-2011, The fellowship of the packaging' +project = u'Setuptools' +copyright = u'2009-2013, The fellowship of the packaging' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.6.50' +version = '0.7.8' # The full version, including alpha/beta/rc tags. -release = '0.6.50' +release = '0.7.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -106,10 +106,10 @@ html_theme_path = ['_theme'] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -html_title = "Distribute documentation" +html_title = "Setuptools documentation" # A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = "Distribute" +html_short_title = "Setuptools" # The name of an image file (relative to this directory) to place at the top # of the sidebar. @@ -161,7 +161,7 @@ html_use_index = False #html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'Distributedoc' +htmlhelp_basename = 'Setuptoolsdoc' # -- Options for LaTeX output -------------------------------------------------- @@ -175,7 +175,7 @@ htmlhelp_basename = 'Distributedoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Distribute.tex', ur'Distribute Documentation', + ('index', 'Setuptools.tex', ur'Setuptools Documentation', ur'The fellowship of the packaging', 'manual'), ] diff --git a/docs/easy_install.txt b/docs/easy_install.txt index 9b4fcfbb..12bc73ea 100644 --- a/docs/easy_install.txt +++ b/docs/easy_install.txt @@ -31,7 +31,7 @@ Using "Easy Install" Installing "Easy Install" ------------------------- -Please see the `setuptools PyPI page <http://pypi.python.org/pypi/setuptools>`_ +Please see the `setuptools PyPI page <https://pypi.python.org/pypi/setuptools>`_ for download links and basic installation instructions for each of the supported platforms. @@ -94,7 +94,7 @@ directory, you can also retarget the installation location for scripts so they go on a directory that's already on the ``PATH``. For more information see the sections below on `Command-Line Options`_ and `Configuration Files`_. You can pass command line options (such as ``--script-dir``) to -``distribute_setup.py`` to control where ``easy_install.exe`` will be installed. +``ez_setup.py`` to control where ``easy_install.exe`` will be installed. @@ -776,10 +776,12 @@ Command-Line Options package not being available locally, or due to the use of the ``--update`` or ``-U`` option. -``--no-find-links`` Blocks the addition of any link. (New in Distribute 0.6.11) - This is useful if you want to avoid adding links defined in a project - easy_install is installing (wether it's a requested project or a - dependency.). When used, ``--find-links`` is ignored. +``--no-find-links`` Blocks the addition of any link. + This parameter is useful if you want to avoid adding links defined in a + project easy_install is installing (whether it's a requested project or a + dependency). When used, ``--find-links`` is ignored. + + Added in Distribute 0.6.11 and Setuptools 0.7. ``--delete-conflicting, -D`` (Removed in 0.6a11) (As of 0.6a11, this option is no longer necessary; please do not use it!) @@ -804,7 +806,7 @@ Command-Line Options ``--index-url=URL, -i URL`` (New in 0.4a1; default changed in 0.6c7) Specifies the base URL of the Python Package Index. The default is - http://pypi.python.org/simple if not specified. When a package is requested + https://pypi.python.org/simple if not specified. When a package is requested that is not locally available or linked from a ``--find-links`` download page, the package index will be searched for download pages for the needed package, and those download pages will be searched for links to download @@ -993,7 +995,7 @@ that the User installation scheme alone does not provide, e.g. the ability to hi Please refer to the `virtualenv`_ documentation for more details. -.. _virtualenv: http://pypi.python.org/pypi/virtualenv +.. _virtualenv: https://pypi.python.org/pypi/virtualenv @@ -1126,7 +1128,7 @@ History 0.6c7 * ``ftp:`` download URLs now work correctly. - * The default ``--index-url`` is now ``http://pypi.python.org/simple``, to use + * The default ``--index-url`` is now ``https://pypi.python.org/simple``, to use the Python Package Index's new simpler (and faster!) REST API. 0.6c6 diff --git a/docs/formats.txt b/docs/formats.txt new file mode 100644 index 00000000..dbfc2812 --- /dev/null +++ b/docs/formats.txt @@ -0,0 +1,696 @@ +===================================== +The Internal Structure of Python Eggs +===================================== + +STOP! This is not the first document you should read! + +This document assumes you have at least some passing familiarity with +*using* setuptools, the ``pkg_resources`` module, and EasyInstall. It +does not attempt to explain basic concepts like inter-project +dependencies, nor does it contain detailed lexical syntax for most +file formats. Neither does it explain concepts like "namespace +packages" or "resources" in any detail, as all of these subjects are +covered at length in the setuptools developer's guide and the +``pkg_resources`` reference manual. + +Instead, this is **internal** documentation for how those concepts and +features are *implemented* in concrete terms. It is intended for people +who are working on the setuptools code base, who want to be able to +troubleshoot setuptools problems, want to write code that reads the file +formats involved, or want to otherwise tinker with setuptools-generated +files and directories. + +Note, however, that these are all internal implementation details and +are therefore subject to change; stick to the published API if you don't +want to be responsible for keeping your code from breaking when +setuptools changes. You have been warned. + + +.. contents:: **Table of Contents** + + +---------------------- +Eggs and their Formats +---------------------- + +A "Python egg" is a logical structure embodying the release of a +specific version of a Python project, comprising its code, resources, +and metadata. There are multiple formats that can be used to physically +encode a Python egg, and others can be developed. However, a key +principle of Python eggs is that they should be discoverable and +importable. That is, it should be possible for a Python application to +easily and efficiently find out what eggs are present on a system, and +to ensure that the desired eggs' contents are importable. + +There are two basic formats currently implemented for Python eggs: + +1. ``.egg`` format: a directory or zipfile *containing* the project's + code and resources, along with an ``EGG-INFO`` subdirectory that + contains the project's metadata + +2. ``.egg-info`` format: a file or directory placed *adjacent* to the + project's code and resources, that directly contains the project's + metadata. + +Both formats can include arbitrary Python code and resources, including +static data files, package and non-package directories, Python +modules, C extension modules, and so on. But each format is optimized +for different purposes. + +The ``.egg`` format is well-suited to distribution and the easy +uninstallation or upgrades of code, since the project is essentially +self-contained within a single directory or file, unmingled with any +other projects' code or resources. It also makes it possible to have +multiple versions of a project simultaneously installed, such that +individual programs can select the versions they wish to use. + +The ``.egg-info`` format, on the other hand, was created to support +backward-compatibility, performance, and ease of installation for system +packaging tools that expect to install all projects' code and resources +to a single directory (e.g. ``site-packages``). Placing the metadata +in that same directory simplifies the installation process, since it +isn't necessary to create ``.pth`` files or otherwise modify +``sys.path`` to include each installed egg. + +Its disadvantage, however, is that it provides no support for clean +uninstallation or upgrades, and of course only a single version of a +project can be installed to a given directory. Thus, support from a +package management tool is required. (This is why setuptools' "install" +command refers to this type of egg installation as "single-version, +externally managed".) Also, they lack sufficient data to allow them to +be copied from their installation source. easy_install can "ship" an +application by copying ``.egg`` files or directories to a target +location, but it cannot do this for ``.egg-info`` installs, because +there is no way to tell what code and resources belong to a particular +egg -- there may be several eggs "scrambled" together in a single +installation location, and the ``.egg-info`` format does not currently +include a way to list the files that were installed. (This may change +in a future version.) + + +Code and Resources +================== + +The layout of the code and resources is dictated by Python's normal +import layout, relative to the egg's "base location". + +For the ``.egg`` format, the base location is the ``.egg`` itself. That +is, adding the ``.egg`` filename or directory name to ``sys.path`` +makes its contents importable. + +For the ``.egg-info`` format, however, the base location is the +directory that *contains* the ``.egg-info``, and thus it is the +directory that must be added to ``sys.path`` to make the egg importable. +(Note that this means that the "normal" installation of a package to a +``sys.path`` directory is sufficient to make it an "egg" if it has an +``.egg-info`` file or directory installed alongside of it.) + + +Project Metadata +================= + +If eggs contained only code and resources, there would of course be +no difference between them and any other directory or zip file on +``sys.path``. Thus, metadata must also be included, using a metadata +file or directory. + +For the ``.egg`` format, the metadata is placed in an ``EGG-INFO`` +subdirectory, directly within the ``.egg`` file or directory. For the +``.egg-info`` format, metadata is stored directly within the +``.egg-info`` directory itself. + +The minimum project metadata that all eggs must have is a standard +Python ``PKG-INFO`` file, named ``PKG-INFO`` and placed within the +metadata directory appropriate to the format. Because it's possible for +this to be the only metadata file included, ``.egg-info`` format eggs +are not required to be a directory; they can just be a ``.egg-info`` +file that directly contains the ``PKG-INFO`` metadata. This eliminates +the need to create a directory just to store one file. This option is +*not* available for ``.egg`` formats, since setuptools always includes +other metadata. (In fact, setuptools itself never generates +``.egg-info`` files, either; the support for using files was added so +that the requirement could easily be satisfied by other tools, such +as the distutils in Python 2.5). + +In addition to the ``PKG-INFO`` file, an egg's metadata directory may +also include files and directories representing various forms of +optional standard metadata (see the section on `Standard Metadata`_, +below) or user-defined metadata required by the project. For example, +some projects may define a metadata format to describe their application +plugins, and metadata in this format would then be included by plugin +creators in their projects' metadata directories. + + +Filename-Embedded Metadata +========================== + +To allow introspection of installed projects and runtime resolution of +inter-project dependencies, a certain amount of information is embedded +in egg filenames. At a minimum, this includes the project name, and +ideally will also include the project version number. Optionally, it +can also include the target Python version and required runtime +platform if platform-specific C code is included. The syntax of an +egg filename is as follows:: + + name ["-" version ["-py" pyver ["-" required_platform]]] "." ext + +The "name" and "version" should be escaped using the ``to_filename()`` +function provided by ``pkg_resources``, after first processing them with +``safe_name()`` and ``safe_version()`` respectively. These latter two +functions can also be used to later "unescape" these parts of the +filename. (For a detailed description of these transformations, please +see the "Parsing Utilities" section of the ``pkg_resources`` manual.) + +The "pyver" string is the Python major version, as found in the first +3 characters of ``sys.version``. "required_platform" is essentially +a distutils ``get_platform()`` string, but with enhancements to properly +distinguish Mac OS versions. (See the ``get_build_platform()`` +documentation in the "Platform Utilities" section of the +``pkg_resources`` manual for more details.) + +Finally, the "ext" is either ``.egg`` or ``.egg-info``, as appropriate +for the egg's format. + +Normally, an egg's filename should include at least the project name and +version, as this allows the runtime system to find desired project +versions without having to read the egg's PKG-INFO to determine its +version number. + +Setuptools, however, only includes the version number in the filename +when an ``.egg`` file is built using the ``bdist_egg`` command, or when +an ``.egg-info`` directory is being installed by the +``install_egg_info`` command. When generating metadata for use with the +original source tree, it only includes the project name, so that the +directory will not have to be renamed each time the project's version +changes. + +This is especially important when version numbers change frequently, and +the source metadata directory is kept under version control with the +rest of the project. (As would be the case when the project's source +includes project-defined metadata that is not generated from by +setuptools from data in the setup script.) + + +Egg Links +========= + +In addition to the ``.egg`` and ``.egg-info`` formats, there is a third +egg-related extension that you may encounter on occasion: ``.egg-link`` +files. + +These files are not eggs, strictly speaking. They simply provide a way +to reference an egg that is not physically installed in the desired +location. They exist primarily as a cross-platform alternative to +symbolic links, to support "installing" code that is being developed in +a different location than the desired installation location. For +example, if a user is developing an application plugin in their home +directory, but the plugin needs to be "installed" in an application +plugin directory, running "setup.py develop -md /path/to/app/plugins" +will install an ``.egg-link`` file in ``/path/to/app/plugins``, that +tells the egg runtime system where to find the actual egg (the user's +project source directory and its ``.egg-info`` subdirectory). + +``.egg-link`` files are named following the format for ``.egg`` and +``.egg-info`` names, but only the project name is included; no version, +Python version, or platform information is included. When the runtime +searches for available eggs, ``.egg-link`` files are opened and the +actual egg file/directory name is read from them. + +Each ``.egg-link`` file should contain a single file or directory name, +with no newlines. This filename should be the base location of one or +more eggs. That is, the name must either end in ``.egg``, or else it +should be the parent directory of one or more ``.egg-info`` format eggs. + +As of setuptools 0.6c6, the path may be specified as a platform-independent +(i.e. ``/``-separated) relative path from the directory containing the +``.egg-link`` file, and a second line may appear in the file, specifying a +platform-independent relative path from the egg's base directory to its +setup script directory. This allows installation tools such as EasyInstall +to find the project's setup directory and build eggs or perform other setup +commands on it. + + +----------------- +Standard Metadata +----------------- + +In addition to the minimum required ``PKG-INFO`` metadata, projects can +include a variety of standard metadata files or directories, as +described below. Except as otherwise noted, these files and directories +are automatically generated by setuptools, based on information supplied +in the setup script or through analysis of the project's code and +resources. + +Most of these files and directories are generated via "egg-info +writers" during execution of the setuptools ``egg_info`` command, and +are listed in the ``egg_info.writers`` entry point group defined by +setuptools' own ``setup.py`` file. + +Project authors can register their own metadata writers as entry points +in this group (as described in the setuptools manual under "Adding new +EGG-INFO Files") to cause setuptools to generate project-specific +metadata files or directories during execution of the ``egg_info`` +command. It is up to project authors to document these new metadata +formats, if they create any. + + +``.txt`` File Formats +===================== + +Files described in this section that have ``.txt`` extensions have a +simple lexical format consisting of a sequence of text lines, each line +terminated by a linefeed character (regardless of platform). Leading +and trailing whitespace on each line is ignored, as are blank lines and +lines whose first nonblank character is a ``#`` (comment symbol). (This +is the parsing format defined by the ``yield_lines()`` function of +the ``pkg_resources`` module.) + +All ``.txt`` files defined by this section follow this format, but some +are also "sectioned" files, meaning that their contents are divided into +sections, using square-bracketed section headers akin to Windows +``.ini`` format. Note that this does *not* imply that the lines within +the sections follow an ``.ini`` format, however. Please see an +individual metadata file's documentation for a description of what the +lines and section names mean in that particular file. + +Sectioned files can be parsed using the ``split_sections()`` function; +see the "Parsing Utilities" section of the ``pkg_resources`` manual for +for details. + + +Dependency Metadata +=================== + + +``requires.txt`` +---------------- + +This is a "sectioned" text file. Each section is a sequence of +"requirements", as parsed by the ``parse_requirements()`` function; +please see the ``pkg_resources`` manual for the complete requirement +parsing syntax. + +The first, unnamed section (i.e., before the first section header) in +this file is the project's core requirements, which must be installed +for the project to function. (Specified using the ``install_requires`` +keyword to ``setup()``). + +The remaining (named) sections describe the project's "extra" +requirements, as specified using the ``extras_require`` keyword to +``setup()``. The section name is the name of the optional feature, and +the section body lists that feature's dependencies. + +Note that it is not normally necessary to inspect this file directly; +``pkg_resources.Distribution`` objects have a ``requires()`` method +that can be used to obtain ``Requirement`` objects describing the +project's core and optional dependencies. + + + +``dependency_links.txt`` +------------------------ + +A list of dependency URLs, one per line, as specified using the +``dependency_links`` keyword to ``setup()``. These may be direct +download URLs, or the URLs of web pages containing direct download +links, and will be used by EasyInstall to find dependencies, as though +the user had manually provided them via the ``--find-links`` command +line option. Please see the setuptools manual and EasyInstall manual +for more information on specifying this option, and for information on +how EasyInstall processes ``--find-links`` URLs. + + +``depends.txt`` -- Obsolete, do not create! +------------------------------------------- + +This file follows an identical format to ``requires.txt``, but is +obsolete and should not be used. The earliest versions of setuptools +required users to manually create and maintain this file, so the runtime +still supports reading it, if it exists. The new filename was created +so that it could be automatically generated from ``setup()`` information +without overwriting an existing hand-created ``depends.txt``, if one +was already present in the project's source ``.egg-info`` directory. + + +``namespace_packages.txt`` -- Namespace Package Metadata +======================================================== + +A list of namespace package names, one per line, as supplied to the +``namespace_packages`` keyword to ``setup()``. Please see the manuals +for setuptools and ``pkg_resources`` for more information about +namespace packages. + + +``entry_points.txt`` -- "Entry Point"/Plugin Metadata +===================================================== + +This is a "sectioned" text file, whose contents encode the +``entry_points`` keyword supplied to ``setup()``. All sections are +named, as the section names specify the entry point groups in which the +corresponding section's entry points are registered. + +Each section is a sequence of "entry point" lines, each parseable using +the ``EntryPoint.parse`` classmethod; please see the ``pkg_resources`` +manual for the complete entry point parsing syntax. + +Note that it is not necessary to parse this file directly; the +``pkg_resources`` module provides a variety of APIs to locate and load +entry points automatically. Please see the setuptools and +``pkg_resources`` manuals for details on the nature and uses of entry +points. + + +The ``scripts`` Subdirectory +============================ + +This directory is currently only created for ``.egg`` files built by +the setuptools ``bdist_egg`` command. It will contain copies of all +of the project's "traditional" scripts (i.e., those specified using the +``scripts`` keyword to ``setup()``). This is so that they can be +reconstituted when an ``.egg`` file is installed. + +The scripts are placed here using the disutils' standard +``install_scripts`` command, so any ``#!`` lines reflect the Python +installation where the egg was built. But instead of copying the +scripts to the local script installation directory, EasyInstall writes +short wrapper scripts that invoke the original scripts from inside the +egg, after ensuring that sys.path includes the egg and any eggs it +depends on. For more about `script wrappers`_, see the section below on +`Installation and Path Management Issues`_. + + +Zip Support Metadata +==================== + + +``native_libs.txt`` +------------------- + +A list of C extensions and other dynamic link libraries contained in +the egg, one per line. Paths are ``/``-separated and relative to the +egg's base location. + +This file is generated as part of ``bdist_egg`` processing, and as such +only appears in ``.egg`` files (and ``.egg`` directories created by +unpacking them). It is used to ensure that all libraries are extracted +from a zipped egg at the same time, in case there is any direct linkage +between them. Please see the `Zip File Issues`_ section below for more +information on library and resource extraction from ``.egg`` files. + + +``eager_resources.txt`` +----------------------- + +A list of resource files and/or directories, one per line, as specified +via the ``eager_resources`` keyword to ``setup()``. Paths are +``/``-separated and relative to the egg's base location. + +Resource files or directories listed here will be extracted +simultaneously, if any of the named resources are extracted, or if any +native libraries listed in ``native_libs.txt`` are extracted. Please +see the setuptools manual for details on what this feature is used for +and how it works, as well as the `Zip File Issues`_ section below. + + +``zip-safe`` and ``not-zip-safe`` +--------------------------------- + +These are zero-length files, and either one or the other should exist. +If ``zip-safe`` exists, it means that the project will work properly +when installedas an ``.egg`` zipfile, and conversely the existence of +``not-zip-safe`` means the project should not be installed as an +``.egg`` file. The ``zip_safe`` option to setuptools' ``setup()`` +determines which file will be written. If the option isn't provided, +setuptools attempts to make its own assessment of whether the package +can work, based on code and content analysis. + +If neither file is present at installation time, EasyInstall defaults +to assuming that the project should be unzipped. (Command-line options +to EasyInstall, however, take precedence even over an existing +``zip-safe`` or ``not-zip-safe`` file.) + +Note that these flag files appear only in ``.egg`` files generated by +``bdist_egg``, and in ``.egg`` directories created by unpacking such an +``.egg`` file. + + + +``top_level.txt`` -- Conflict Management Metadata +================================================= + +This file is a list of the top-level module or package names provided +by the project, one Python identifier per line. + +Subpackages are not included; a project containing both a ``foo.bar`` +and a ``foo.baz`` would include only one line, ``foo``, in its +``top_level.txt``. + +This data is used by ``pkg_resources`` at runtime to issue a warning if +an egg is added to ``sys.path`` when its contained packages may have +already been imported. + +(It was also once used to detect conflicts with non-egg packages at +installation time, but in more recent versions, setuptools installs eggs +in such a way that they always override non-egg packages, thus +preventing a problem from arising.) + + +``SOURCES.txt`` -- Source Files Manifest +======================================== + +This file is roughly equivalent to the distutils' ``MANIFEST`` file. +The differences are as follows: + +* The filenames always use ``/`` as a path separator, which must be + converted back to a platform-specific path whenever they are read. + +* The file is automatically generated by setuptools whenever the + ``egg_info`` or ``sdist`` commands are run, and it is *not* + user-editable. + +Although this metadata is included with distributed eggs, it is not +actually used at runtime for any purpose. Its function is to ensure +that setuptools-built *source* distributions can correctly discover +what files are part of the project's source, even if the list had been +generated using revision control metadata on the original author's +system. + +In other words, ``SOURCES.txt`` has little or no runtime value for being +included in distributed eggs, and it is possible that future versions of +the ``bdist_egg`` and ``install_egg_info`` commands will strip it before +installation or distribution. Therefore, do not rely on its being +available outside of an original source directory or source +distribution. + + +------------------------------ +Other Technical Considerations +------------------------------ + + +Zip File Issues +=============== + +Although zip files resemble directories, they are not fully +substitutable for them. Most platforms do not support loading dynamic +link libraries contained in zipfiles, so it is not possible to directly +import C extensions from ``.egg`` zipfiles. Similarly, there are many +existing libraries -- whether in Python or C -- that require actual +operating system filenames, and do not work with arbitrary "file-like" +objects or in-memory strings, and thus cannot operate directly on the +contents of zip files. + +To address these issues, the ``pkg_resources`` module provides a +"resource API" to support obtaining either the contents of a resource, +or a true operating system filename for the resource. If the egg +containing the resource is a directory, the resource's real filename +is simply returned. However, if the egg is a zipfile, then the +resource is first extracted to a cache directory, and the filename +within the cache is returned. + +The cache directory is determined by the ``pkg_resources`` API; please +see the ``set_cache_path()`` and ``get_default_cache()`` documentation +for details. + + +The Extraction Process +---------------------- + +Resources are extracted to a cache subdirectory whose name is based +on the enclosing ``.egg`` filename and the path to the resource. If +there is already a file of the correct name, size, and timestamp, its +filename is returned to the requester. Otherwise, the desired file is +extracted first to a temporary name generated using +``mkstemp(".$extract",target_dir)``, and then its timestamp is set to +match the one in the zip file, before renaming it to its final name. +(Some collision detection and resolution code is used to handle the +fact that Windows doesn't overwrite files when renaming.) + +If a resource directory is requested, all of its contents are +recursively extracted in this fashion, to ensure that the directory +name can be used as if it were valid all along. + +If the resource requested for extraction is listed in the +``native_libs.txt`` or ``eager_resources.txt`` metadata files, then +*all* resources listed in *either* file will be extracted before the +requested resource's filename is returned, thus ensuring that all +C extensions and data used by them will be simultaneously available. + + +Extension Import Wrappers +------------------------- + +Since Python's built-in zip import feature does not support loading +C extension modules from zipfiles, the setuptools ``bdist_egg`` command +generates special import wrappers to make it work. + +The wrappers are ``.py`` files (along with corresponding ``.pyc`` +and/or ``.pyo`` files) that have the same module name as the +corresponding C extension. These wrappers are located in the same +package directory (or top-level directory) within the zipfile, so that +say, ``foomodule.so`` will get a corresponding ``foo.py``, while +``bar/baz.pyd`` will get a corresponding ``bar/baz.py``. + +These wrapper files contain a short stanza of Python code that asks +``pkg_resources`` for the filename of the corresponding C extension, +then reloads the module using the obtained filename. This will cause +``pkg_resources`` to first ensure that all of the egg's C extensions +(and any accompanying "eager resources") are extracted to the cache +before attempting to link to the C library. + +Note, by the way, that ``.egg`` directories will also contain these +wrapper files. However, Python's default import priority is such that +C extensions take precedence over same-named Python modules, so the +import wrappers are ignored unless the egg is a zipfile. + + +Installation and Path Management Issues +======================================= + +Python's initial setup of ``sys.path`` is very dependent on the Python +version and installation platform, as well as how Python was started +(i.e., script vs. ``-c`` vs. ``-m`` vs. interactive interpreter). +In fact, Python also provides only two relatively robust ways to affect +``sys.path`` outside of direct manipulation in code: the ``PYTHONPATH`` +environment variable, and ``.pth`` files. + +However, with no cross-platform way to safely and persistently change +environment variables, this leaves ``.pth`` files as EasyInstall's only +real option for persistent configuration of ``sys.path``. + +But ``.pth`` files are rather strictly limited in what they are allowed +to do normally. They add directories only to the *end* of ``sys.path``, +after any locally-installed ``site-packages`` directory, and they are +only processed *in* the ``site-packages`` directory to start with. + +This is a double whammy for users who lack write access to that +directory, because they can't create a ``.pth`` file that Python will +read, and even if a sympathetic system administrator adds one for them +that calls ``site.addsitedir()`` to allow some other directory to +contain ``.pth`` files, they won't be able to install newer versions of +anything that's installed in the systemwide ``site-packages``, because +their paths will still be added *after* ``site-packages``. + +So EasyInstall applies two workarounds to solve these problems. + +The first is that EasyInstall leverages ``.pth`` files' "import" feature +to manipulate ``sys.path`` and ensure that anything EasyInstall adds +to a ``.pth`` file will always appear before both the standard library +and the local ``site-packages`` directories. Thus, it is always +possible for a user who can write a Python-read ``.pth`` file to ensure +that their packages come first in their own environment. + +Second, when installing to a ``PYTHONPATH`` directory (as opposed to +a "site" directory like ``site-packages``) EasyInstall will also install +a special version of the ``site`` module. Because it's in a +``PYTHONPATH`` directory, this module will get control before the +standard library version of ``site`` does. It will record the state of +``sys.path`` before invoking the "real" ``site`` module, and then +afterwards it processes any ``.pth`` files found in ``PYTHONPATH`` +directories, including all the fixups needed to ensure that eggs always +appear before the standard library in sys.path, but are in a relative +order to one another that is defined by their ``PYTHONPATH`` and +``.pth``-prescribed sequence. + +The net result of these changes is that ``sys.path`` order will be +as follows at runtime: + +1. The ``sys.argv[0]`` directory, or an emtpy string if no script + is being executed. + +2. All eggs installed by EasyInstall in any ``.pth`` file in each + ``PYTHONPATH`` directory, in order first by ``PYTHONPATH`` order, + then normal ``.pth`` processing order (which is to say alphabetical + by ``.pth`` filename, then by the order of listing within each + ``.pth`` file). + +3. All eggs installed by EasyInstall in any ``.pth`` file in each "site" + directory (such as ``site-packages``), following the same ordering + rules as for the ones on ``PYTHONPATH``. + +4. The ``PYTHONPATH`` directories themselves, in their original order + +5. Any paths from ``.pth`` files found on ``PYTHONPATH`` that were *not* + eggs installed by EasyInstall, again following the same relative + ordering rules. + +6. The standard library and "site" directories, along with the contents + of any ``.pth`` files found in the "site" directories. + +Notice that sections 1, 4, and 6 comprise the "normal" Python setup for +``sys.path``. Sections 2 and 3 are inserted to support eggs, and +section 5 emulates what the "normal" semantics of ``.pth`` files on +``PYTHONPATH`` would be if Python natively supported them. + +For further discussion of the tradeoffs that went into this design, as +well as notes on the actual magic inserted into ``.pth`` files to make +them do these things, please see also the following messages to the +distutils-SIG mailing list: + +* http://mail.python.org/pipermail/distutils-sig/2006-February/006026.html +* http://mail.python.org/pipermail/distutils-sig/2006-March/006123.html + + +Script Wrappers +--------------- + +EasyInstall never directly installs a project's original scripts to +a script installation directory. Instead, it writes short wrapper +scripts that first ensure that the project's dependencies are active +on sys.path, before invoking the original script. These wrappers +have a #! line that points to the version of Python that was used to +install them, and their second line is always a comment that indicates +the type of script wrapper, the project version required for the script +to run, and information identifying the script to be invoked. + +The format of this marker line is:: + + "# EASY-INSTALL-" script_type ": " tuple_of_strings "\n" + +The ``script_type`` is one of ``SCRIPT``, ``DEV-SCRIPT``, or +``ENTRY-SCRIPT``. The ``tuple_of_strings`` is a comma-separated +sequence of Python string constants. For ``SCRIPT`` and ``DEV-SCRIPT`` +wrappers, there are two strings: the project version requirement, and +the script name (as a filename within the ``scripts`` metadata +directory). For ``ENTRY-SCRIPT`` wrappers, there are three: +the project version requirement, the entry point group name, and the +entry point name. (See the "Automatic Script Creation" section in the +setuptools manual for more information about entry point scripts.) + +In each case, the project version requirement string will be a string +parseable with the ``pkg_resources`` modules' ``Requirement.parse()`` +classmethod. The only difference between a ``SCRIPT`` wrapper and a +``DEV-SCRIPT`` is that a ``DEV-SCRIPT`` actually executes the original +source script in the project's source tree, and is created when the +"setup.py develop" command is run. A ``SCRIPT`` wrapper, on the other +hand, uses the "installed" script written to the ``EGG-INFO/scripts`` +subdirectory of the corresponding ``.egg`` zipfile or directory. +(``.egg-info`` eggs do not have script wrappers associated with them, +except in the "setup.py develop" case.) + +The purpose of including the marker line in generated script wrappers is +to facilitate introspection of installed scripts, and their relationship +to installed eggs. For example, an uninstallation tool could use this +data to identify what scripts can safely be removed, and/or identify +what scripts would stop working if a particular egg is uninstalled. + diff --git a/docs/index.txt b/docs/index.txt index 5f3b945b..162a5f6f 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -1,36 +1,25 @@ -Welcome to Distribute's documentation! -====================================== +Welcome to Setuptools' documentation! +===================================== -`Distribute` is a fork of the `Setuptools` project. +Setuptools is a fully-featured, actively-maintained, and stable library +designed to facilitate packaging Python projects, where packaging includes: -Distribute is intended to replace Setuptools as the standard method for -working with Python module distributions. - -For those who may wonder why they should switch to Distribute over Setuptools, it’s quite simple: - -- Distribute is a drop-in replacement for Setuptools -- The code is actively maintained, and has over 10 commiters -- Distribute offers Python 3 support ! + - Python package and module definitions + - Distribution package metadata + - Test hooks + - Project installation + - Platform-specific details + - Python 3 support Documentation content: .. toctree:: :maxdepth: 2 + merge roadmap python3 using setuptools easy_install pkg_resources - - -.. image:: http://python-distribute.org/pip_distribute.png - -Design done by Idan Gazit (http://pixane.com) - License: cc-by-3.0 - -Copy & paste:: - - curl -O http://python-distribute.org/distribute_setup.py - python distribute_setup.py - easy_install pip
\ No newline at end of file diff --git a/docs/merge-faq.txt b/docs/merge-faq.txt new file mode 100644 index 00000000..52013098 --- /dev/null +++ b/docs/merge-faq.txt @@ -0,0 +1,80 @@ +Setuptools/Distribute Merge FAQ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +How do I upgrade from Distribute? +================================= + +Distribute specifically prohibits installation of Setuptools 0.7 from Distribute 0.6. There are then two options for upgrading. + +Note that after upgrading using either technique, the only option to downgrade to either version is to completely uninstall Distribute and Setuptools 0.7 versions before reinstalling an 0.6 release. + +Use Distribute 0.7 +------------------ + +The PYPA has put together a compatibility wrapper, a new release of Distribute version 0.7. This package will install over Distribute 0.6.x installations and will replace Distribute with a simple wrapper that requires Setuptools 0.7 or later. This technique is experimental, but initial results indicate this technique is the easiest upgrade path. + + +Uninstall +--------- + +First, completely uninstall Distribute. Since Distribute does not have an automated installation routine, this process is manual. Follow the instructions in the README for uninstalling. + + +How do I upgrade from Setuptools 0.6? +===================================== + +There are no special instructions for upgrading over older versions of Setuptools. Simply use `easy_install -U` or run the latest `ez_setup.py`. + +Where does the merge occur? +======================================================== + +The merge is occurring between the heads of the default branch of Distribute and the setuptools-0.6 branch of Setuptools. The Setuptools SVN repo has been converted to a Mercurial repo hosted on Bitbucket. The work is still underway, so the exact changesets included may change, although the anticipated merge targets are Setuptools at 0.6c12 and Distribute at 0.6.35. + +What happens to other branches? +======================================================== + +Distribute 0.7 was abandoned long ago and won't be included in the resulting code tree, but may be retained for posterity in the original repo. + +Setuptools default branch (also 0.7 development) may also be abandoned or may be incorporated into the new merged line if desirable (and as resources allow). + +What history is lost/changed? +======================================================== + +As setuptools was not on Mercurial when the fork occurred and as Distribute did not include the full setuptools history (prior to the creation of the setuptools-0.6 branch), the two source trees were not compatible. In order to most effectively communicate the code history, the Distribute code was grafted onto the (originally private) setuptools Mercurial repo. Although this grafting maintained the full code history with names, dates, and changes, it did lose the original hashes of those changes. Therefore, references to changes by hash (including tags) are lost. + +Additionally, any heads that were not actively merged into the Distribute 0.6.35 release were also omitted. As a result, the changesets included in the merge repo are those from the original setuptools repo and all changesets ancestral to the Distribute 0.6.35 release. + +What features will be in the merged code base? +======================================================== + +In general, all "features" added in distribute will be included in setuptools. Where there exist conflicts or undesirable features, we will be explicit about what these limitations are. Changes that are backward-incompatible from setuptools 0.6 to distribute will likely be removed, and these also will be well documented. + +Bootstrapping scripts (ez_setup/distribute_setup) and docs, as with distribute, will be maintained in the repository and built as part of the release process. Documentation and bootstrapping scripts will be hosted at python.org, as they are with distribute now. Documentation at telecommunity will be updated to refer or redirect to the new, merged docs. + +On the whole, the merged setuptools should be largely compatible with the latest releases of both setuptools and distribute and will be an easy transition for users of either library. + +Who is invited to contribute? Who is excluded? +======================================================== + +While we've worked privately to initiate this merge due to the potential sensitivity of the topic, no one is excluded from this effort. We invite all members of the community, especially those most familiar with Python packaging and its challenges to join us in the effort. + +We have lots of ideas for how we'd like to improve the codebase, release process, everything. Like distribute, the post-merge setuptools will have its source hosted on bitbucket. (So if you're currently a distribute contributor, about the only thing that's going to change is the URL of the repository you follow.) Also like distribute, it'll support Python 3, and hopefully we'll soon merge Vinay Sajip's patches to make it run on Python 3 without needing 2to3 to be run on the code first. + +While we've worked privately to initiate this merge due to the potential sensitivity of the topic, no one is excluded from this effort. We invite all members of the community, especially those most familiar with Python packaging and its challenges to join us in the effort. + +Why Setuptools and not Distribute or another name? +======================================================== + +We do, however, understand that this announcement might be unsettling for some. The setuptools name has been subjected to a lot of deprecation in recent years, so the idea that it will now be the preferred name instead of distribute might be somewhat difficult or disorienting for some. We considered use of another name (Distribute or an entirely new name), but that would serve to only complicate matters further. Instead, our goal is to simplify the packaging landscape but without losing any hard-won advancements. We hope that the people who worked to spread the first message will be equally enthusiastic about spreading the new one, and we especially look forward to seeing the new posters and slogans celebrating setuptools. + +What is the timeframe of release? +======================================================== + +There are no hard timeframes for any of this effort, although progress is underway and a draft merge is underway and being tested privately. As an unfunded volunteer effort, our time to put in on it is limited, and we've both had some recent health and other challenges that have made working on this difficult, which in part explains why we haven't met our original deadline of a completed merge before PyCon. + +The final Setuptools 0.7 was cut on June 1, 2013 and will be released to PyPI shortly thereafter. + +What version number can I expect for the new release? +======================================================== + +The new release will roughly follow the previous trend for setuptools and release the new release as 0.7. This number is somewhat arbitrary, but we wanted something other than 0.6 to distinguish it from its ancestor forks but not 1.0 to avoid putting too much emphasis on the release itself and to focus on merging the functionality. In the future, the project will likely adopt a versioning scheme similar to semver to convey semantic meaning about the release in the version number. diff --git a/docs/merge.txt b/docs/merge.txt new file mode 100644 index 00000000..ba37d6e4 --- /dev/null +++ b/docs/merge.txt @@ -0,0 +1,122 @@ +Merge with Distribute +~~~~~~~~~~~~~~~~~~~~~ + +In 2013, the fork of Distribute was merged back into Setuptools. This +document describes some of the details of the merge. + +.. toctree:: + :maxdepth: 2 + + merge-faq + +Process +======= + +In order to try to accurately reflect the fork and then re-merge of the +projects, the merge process brought both code trees together into one +repository and grafted the Distribute fork onto the Setuptools development +line (as if it had been created as a branch in the first place). + +The rebase to get distribute onto setuptools went something like this:: + + hg phase -d -f -r 26b4c29b62db + hg rebase -s 26b4c29b62db -d 7a5cf59c78d7 + +The technique required a late version of mercurial (2.5) to work correctly. + +The only code that was included was the code that was ancestral to the public +releases of Distribute 0.6. Additionally, because Setuptools was not hosted +on Mercurial at the time of the fork and because the Distribute fork did not +include a complete conversion of the Setuptools history, the Distribute +changesets had to be re-applied to a new, different conversion of the +Setuptools SVN repository. As a result, all of the hashes have changed. + +Distribute was grafted in a 'distribute' branch and the 'setuptools-0.6' +branch was targeted for the merge. The 'setuptools' branch remains with +unreleased code and may be incorporated in the future. + +Reconciling Differences +======================= + +There were both technical and philosophical differences between Setuptools +and Distribute. To reconcile these differences in a manageable way, the +following technique was undertaken: + +Create a 'Setuptools-Distribute merge' branch, based on a late release of +Distribute (0.6.35). This was done with a00b441856c4. + +In that branch, first remove code that is no longer relevant to +Setuptools (such as the setuptools patching code). + +Next, in the the merge branch, create another base from at the point where the +fork occurred (such that the code is still essentially an older but pristine +setuptools). This base can be found as 955792b069d0. This creates two heads +in the merge branch, each with a basis in the fork. + +Then, repeatedly copy changes for a +single file or small group of files from a late revision of that file in the +'setuptools-0.6' branch (1aae1efe5733 was used) and commit those changes on +the setuptools-only head. That head is then merged with the head with +Distribute changes. It is in this Mercurial +merge operation that the fundamental differences between Distribute and +Setuptools are reconciled, but since only a single file or small set of files +are used, the scope is limited. + +Finally, once all the challenging files have been reconciled and merged, the +remaining changes from the setuptools-0.6 branch are merged, deferring to the +reconciled changes (a1fa855a5a62 and 160ccaa46be0). + +Originally, jaraco attempted all of this using anonymous heads in the +Distribute branch, but later realized this technique made for a somewhat +unclear merge process, so the changes were re-committed as described above +for clarity. In this way, the "distribute" and "setuptools" branches can +continue to track the official Distribute changesets. + +Concessions +=========== + +With the merge of Setuptools and Distribute, the following concessions were +made: + +Differences from setuptools 0.6c12: + +Major Changes +------------- + +* Python 3 support. +* Improved support for GAE. +* Support `PEP-370 <http://www.python.org/dev/peps/pep-0370/>`_ per-user site + packages. +* Sort order of Distributions in pkg_resources now prefers PyPI to external + links (Distribute issue 163). +* Python 2.4 or greater is required (drop support for Python 2.3). + +Minor Changes +------------- + +* Wording of some output has changed to replace contractions with their + canonical form (i.e. prefer "could not" to "couldn't"). +* Manifest files are only written for 32-bit .exe launchers. + +Differences from Distribute 0.6.36: + +Major Changes +------------- + +* The _distribute property of the setuptools module has been removed. +* Distributions are once again installed as zipped eggs by default, per the + rationale given in `the seminal bug report + <http://bugs.python.org/setuptools/issue33>`_ indicates that the feature + should remain and no substantial justification was given in the `Distribute + report <https://bitbucket.org/tarek/distribute/issue/19/>`_. + +Minor Changes +------------- + +* The patch for `#174 <https://bitbucket.org/tarek/distribute/issue/174>`_ + has been rolled-back, as the comment on the ticket indicates that the patch + addressed a symptom and not the fundamental issue. +* ``easy_install`` (the command) once again honors setup.cfg if found in the + current directory. The "mis-behavior" characterized in `#99 + <https://bitbucket.org/tarek/distribute/issue/99>`_ is actually intended + behavior, and no substantial rationale was given for the deviation. diff --git a/docs/python3.txt b/docs/python3.txt index 2f6cde4a..1e019951 100644 --- a/docs/python3.txt +++ b/docs/python3.txt @@ -1,18 +1,19 @@ ===================================================== -Supporting both Python 2 and Python 3 with Distribute +Supporting both Python 2 and Python 3 with Setuptools ===================================================== -Starting with version 0.6.2, Distribute supports Python 3. Installing and -using distribute for Python 3 code works exactly the same as for Python 2 -code, but Distribute also helps you to support Python 2 and Python 3 from +Starting with Distribute version 0.6.2 and Setuptools 0.7, the Setuptools +project supported Python 3. Installing and +using setuptools for Python 3 code works exactly the same as for Python 2 +code, but Setuptools also helps you to support Python 2 and Python 3 from the same source code by letting you run 2to3 on the code as a part of the build process, by setting the keyword parameter ``use_2to3`` to True. -Distribute as help during porting +Setuptools as help during porting ================================= -Distribute can make the porting process much easier by automatically running +Setuptools can make the porting process much easier by automatically running 2to3 as a part of the test running. To do this you need to configure the setup.py so that you can run the unit tests with ``python setup.py test``. @@ -24,10 +25,10 @@ The test command will now first run the build command during which the code will be converted with 2to3, and the tests will then be run from the build directory, as opposed from the source directory as is normally done. -Distribute will convert all Python files, and also all doctests in Python +Setuptools will convert all Python files, and also all doctests in Python files. However, if you have doctests located in separate text files, these will not automatically be converted. By adding them to the -``convert_2to3_doctests`` keyword parameter Distrubute will convert them as +``convert_2to3_doctests`` keyword parameter Setuptools will convert them as well. By default, the conversion uses all fixers in the ``lib2to3.fixers`` package. @@ -86,12 +87,14 @@ Advanced features If you don't want to run the 2to3 conversion on the doctests in Python files, you can turn that off by setting ``setuptools.use_2to3_on_doctests = False``. -Note on compatibility with setuptools -===================================== +Note on compatibility with older versions of setuptools +======================================================= -Setuptools do not know about the new keyword parameters to support Python 3. +Setuptools earlier than 0.7 does not know about the new keyword parameters to +support Python 3. As a result it will warn about the unknown keyword parameters if you use -setuptools instead of Distribute under Python 2. This is not an error, and +those versions of setuptools instead of Distribute under Python 2. This output +is not an error, and install process will continue as normal, but if you want to get rid of that error this is easy. Simply conditionally add the new parameters into an extra dict and pass that dict into setup():: @@ -117,5 +120,5 @@ dict and pass that dict into setup():: **extra ) -This way the parameters will only be used under Python 3, where you have to -use Distribute. +This way the parameters will only be used under Python 3, where Distribute or +Setuptools 0.7 or later is required. diff --git a/docs/roadmap.txt b/docs/roadmap.txt index ea5070ea..44bcdb0f 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -2,85 +2,13 @@ Roadmap ======= -Distribute has two branches: +Setuptools has merged with Distribute and to provide a unified codebase for +ongoing development. -- 0.6.x : provides a Setuptools-0.6cX compatible version -- 0.7.x : will provide a refactoring - -0.6.x -===== - -Not "much" is going to happen here, we want this branch to be helpful -to the community *today* by addressing the 40-or-so bugs -that were found in Setuptools and never fixed. This is eventually -happen soon because its development is -fast : there are up to 5 commiters that are working on it very often -(and the number grows weekly.) - -The biggest issue with this branch is that it is providing the same -packages and modules setuptools does, and this -requires some bootstrapping work where we make sure once Distribute is -installed, all Distribution that requires Setuptools -will continue to work. This is done by faking the metadata of -Setuptools 0.6c9. That's the only way we found to do this. - -There's one major thing though: thanks to the work of Lennart, Alex, -Martin, this branch supports Python 3, -which is great to have to speed up Py3 adoption. - -The goal of the 0.6.x is to remove as much bugs as we can, and try if -possible to remove the patches done -on Distutils. We will support 0.6.x maintenance for years and we will -promote its usage everywhere instead of -Setuptools. - -Some new commands are added there, when they are helpful and don't -interact with the rest. I am thinking -about "upload_docs" that let you upload documentation to PyPI. The -goal is to move it to Distutils -at some point, if the documentation feature of PyPI stays and starts to be used. - -0.7.x -===== - -We've started to refactor Distribute with this roadmap in mind (and -no, as someone said, it's not vaporware, -we've done a lot already) - -- 0.7.x can be installed and used with 0.6.x - -- easy_install is going to be deprecated ! use Pip ! - -- the version system will be deprecated, in favor of the one in Distutils - -- no more Distutils monkey-patch that happens once you use the code - (things like 'from distutils import cmd; cmd.Command = CustomCommand') - -- no more custom site.py (that is: if something misses in Python's - site.py we'll add it there instead of patching it) - -- no more namespaced packages system, if PEP 382 (namespaces package - support) makes it to 2.7 - -- The code is splitted in many packages and might be distributed under - several distributions. - - - distribute.resources: that's the old pkg_resources, but - reorganized in clean, pep-8 modules. This package will - only contain the query APIs and will focus on being PEP 376 - compatible. We will promote its usage and see if Pip wants - to use it as a basis. - It will probably shrink a lot though, once the stdlib provides PEP 376 support. - - - distribute.entrypoints: that's the old pkg_resources entry points - system, but on its own. it uses distribute.resources - - - distribute.index: that's package_index and a few other things. - everything required to interact with PyPI. We will promote - its usage and see if Pip wants to use it as a basis. - - - distribute.core (might be renamed to main): that's everything - else, and uses the other packages. - -Goal: A first release before (or when) Python 2.7 / 3.2 is out. +This new effort will draw from the resources of both projects to leverage +community contribution for ongoing advancement but also maintain stability +for the user base. +An initial release of Setuptools 0.7 will attempt to be compatible both with +Setuptools 0.6c11 and Distribute 0.6.36. Where compatibility cannot be +achieved, the changes should be well-documented. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index fe8bb3f6..5d80b230 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1,8 +1,8 @@ ================================================== -Building and Distributing Packages with Distribute +Building and Distributing Packages with Setuptools ================================================== -``Distribute`` is a collection of enhancements to the Python ``distutils`` +``Setuptools`` is a collection of enhancements to the Python ``distutils`` (for Python 2.3.5 and up on most platforms; 64-bit platforms require a minimum of Python 2.4) that allow you to more easily build and distribute Python packages, especially ones that have dependencies on other packages. @@ -15,7 +15,7 @@ including just a single `bootstrap module`_ (an 8K .py file), your package will automatically download and install ``setuptools`` if the user is building your package from source and doesn't have a suitable version already installed. -.. _bootstrap module: http://nightly.ziade.org/distribute_setup.py +.. _bootstrap module: https://bitbucket.org/pypa/setuptools/downloads/ez_setup.py Feature Highlights: @@ -2406,7 +2406,7 @@ The ``upload`` command has a few options worth noting: ``--repository=URL, -r URL`` The URL of the repository to upload to. Defaults to - http://pypi.python.org/pypi (i.e., the main PyPI installation). + https://pypi.python.org/pypi (i.e., the main PyPI installation). .. _upload_docs: @@ -2414,7 +2414,7 @@ The ``upload`` command has a few options worth noting: ====================================================== PyPI now supports uploading project documentation to the dedicated URL -http://packages.python.org/<project>/. +https://pythonhosted.org/<project>/. The ``upload_docs`` command will create the necessary zip file out of a documentation directory and will post to the repository. @@ -2468,7 +2468,7 @@ The ``upload_docs`` command has the following options: ``--repository=URL, -r URL`` The URL of the repository to upload to. Defaults to - http://pypi.python.org/pypi (i.e., the main PyPI installation). + https://pypi.python.org/pypi (i.e., the main PyPI installation). -------------------------------- diff --git a/docs/using.txt b/docs/using.txt index 192f1dc2..6f93c386 100644 --- a/docs/using.txt +++ b/docs/using.txt @@ -1,21 +1,10 @@ ================================ -Using Distribute in your project +Using Setuptools in your project ================================ -To use Distribute in your project, the recommended way is to ship -`distribute_setup.py` alongside your `setup.py` script and call +To use Setuptools in your project, the recommended way is to ship +`ez_setup.py` alongside your `setup.py` script and call it at the very begining of `setup.py` like this:: - from distribute_setup import use_setuptools + from ez_setup import use_setuptools use_setuptools() - -Another way is to add ``Distribute`` in the ``install_requires`` option:: - - from setuptools import setup - - setup(... - install_requires=['distribute'] - ) - - -XXX to be finished diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 00000000..6baa9b99 --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,258 @@ +#!python +"""Bootstrap setuptools 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 ez_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 shutil +import sys +import tempfile +import tarfile +import optparse +import subprocess + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +DEFAULT_VERSION = "0.7.8" +DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" + +def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +def _install(tarball, install_args=()): + # 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 Setuptools') + 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): + # 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 Setuptools egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # 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, 'setuptools-%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): + # 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: + import pkg_resources + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("setuptools>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of setuptools (>=%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 setuptools'." + "\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) + + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools 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 = "setuptools-%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 _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 _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the setuptools package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the setuptools package') + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pkg_resources.py b/pkg_resources.py index 41c73d42..bdd738f2 100644 --- a/pkg_resources.py +++ b/pkg_resources.py @@ -41,14 +41,10 @@ if sys.version_info >= (3, 3) and sys.implementation.name == "cpython": else: importlib_bootstrap = None -# This marker is used to simplify the process that checks is the -# setuptools package was installed by the Setuptools project -# or by the Distribute project, in case Setuptools creates -# a distribution with the same version. -# -# The bootstrapping script for instance, will check if this -# attribute is present to decide wether to reinstall the package -_distribute = True +try: + import parser +except ImportError: + pass def _bypass_ensure_directory(name, mode=0777): # Sandbox-bypassing version of ensure_directory() @@ -98,6 +94,7 @@ _sget_none = _sset_none = lambda *args: None + def get_supported_platform(): """Return this platform's maximum compatible version. @@ -162,7 +159,7 @@ __all__ = [ # Parsing functions and string utilities 'parse_requirements', 'parse_version', 'safe_name', 'safe_version', 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', - 'safe_extra', 'to_filename', + 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker', # filesystem utilities 'ensure_directory', 'normalize_path', @@ -250,9 +247,10 @@ def get_build_platform(): needs some hacks for Linux and Mac OS X. """ try: - from distutils.util import get_platform - except ImportError: + # Python 2.7 or >=3.2 from sysconfig import get_platform + except ImportError: + from distutils.util import get_platform plat = get_platform() if sys.platform == "darwin" and not plat.startswith('macosx-'): @@ -271,6 +269,12 @@ macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)") darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)") get_platform = get_build_platform # XXX backward compat + + + + + + def compatible_platforms(provided,required): """Can code for the `provided` platform run on the `required` platform? @@ -446,7 +450,7 @@ class WorkingSet(object): def add_entry(self, entry): """Add a path item to ``.entries``, finding any distributions on it - ``find_distributions(entry,True)`` is used to find distributions + ``find_distributions(entry, True)`` is used to find distributions corresponding to the path entry, and they are added. `entry` is always appended to ``.entries``, even if it is already present. (This is because ``sys.path`` can contain the same value more than @@ -553,7 +557,7 @@ class WorkingSet(object): keys2.append(dist.key) self._added_new(dist) - def resolve(self, requirements, env=None, installer=None, replacement=True): + def resolve(self, requirements, env=None, installer=None): """List all distributions needed to (recursively) meet `requirements` `requirements` must be a sequence of ``Requirement`` objects. `env`, @@ -572,9 +576,6 @@ class WorkingSet(object): while requirements: req = requirements.pop(0) # process dependencies breadth-first - if _override_setuptools(req) and replacement: - req = Requirement.parse('distribute') - if req in processed: # Ignore cyclic or redundant dependencies continue @@ -694,7 +695,6 @@ class WorkingSet(object): activated to fulfill the requirements; all relevant distributions are included, even if they were already activated in this working set. """ - needed = self.resolve(parse_requirements(requirements)) for dist in needed: @@ -702,7 +702,6 @@ class WorkingSet(object): return needed - def subscribe(self, callback): """Invoke `callback` for all distributions (including existing ones)""" if callback in self.callbacks: @@ -711,14 +710,15 @@ class WorkingSet(object): for dist in self: callback(dist) - def _added_new(self, dist): for callback in self.callbacks: callback(dist) def __getstate__(self): - return (self.entries[:], self.entry_keys.copy(), self.by_key.copy(), - self.callbacks[:]) + return ( + self.entries[:], self.entry_keys.copy(), self.by_key.copy(), + self.callbacks[:] + ) def __setstate__(self, (entries, keys, by_key, callbacks)): self.entries = entries[:] @@ -727,8 +727,6 @@ class WorkingSet(object): self.callbacks = callbacks[:] - - class Environment(object): """Searchable snapshot of distributions on a search path""" @@ -1205,6 +1203,160 @@ def to_filename(name): +_marker_names = { + 'os': ['name'], 'sys': ['platform'], + 'platform': ['version','machine','python_implementation'], + 'python_version': [], 'python_full_version': [], 'extra':[], +} + +_marker_values = { + 'os_name': lambda: os.name, + 'sys_platform': lambda: sys.platform, + 'python_full_version': lambda: sys.version.split()[0], + 'python_version': lambda:'%s.%s' % (sys.version_info[0], sys.version_info[1]), + 'platform_version': lambda: _platinfo('version'), + 'platform_machine': lambda: _platinfo('machine'), + 'python_implementation': lambda: _platinfo('python_implementation') or _pyimp(), +} + +def _platinfo(attr): + try: + import platform + except ImportError: + return '' + return getattr(platform, attr, lambda:'')() + +def _pyimp(): + if sys.platform=='cli': + return 'IronPython' + elif sys.platform.startswith('java'): + return 'Jython' + elif '__pypy__' in sys.builtin_module_names: + return 'PyPy' + else: + return 'CPython' + +def invalid_marker(text): + """Validate text as a PEP 426 environment marker; return exception or False""" + try: + evaluate_marker(text) + except SyntaxError: + return sys.exc_info()[1] + return False + +def evaluate_marker(text, extra=None, _ops={}): + """ + Evaluate a PEP 426 environment marker on CPython 2.4+. + Return a boolean indicating the marker result in this environment. + Raise SyntaxError if marker is invalid. + + This implementation uses the 'parser' module, which is not implemented on + Jython and has been superseded by the 'ast' module in Python 2.6 and + later. + """ + + if not _ops: + + from token import NAME, STRING + import token, symbol, operator + + def and_test(nodelist): + # MUST NOT short-circuit evaluation, or invalid syntax can be skipped! + return reduce(operator.and_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)]) + + def test(nodelist): + # MUST NOT short-circuit evaluation, or invalid syntax can be skipped! + return reduce(operator.or_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)]) + + def atom(nodelist): + t = nodelist[1][0] + if t == token.LPAR: + if nodelist[2][0] == token.RPAR: + raise SyntaxError("Empty parentheses") + return interpret(nodelist[2]) + raise SyntaxError("Language feature not supported in environment markers") + + def comparison(nodelist): + if len(nodelist)>4: + raise SyntaxError("Chained comparison not allowed in environment markers") + comp = nodelist[2][1] + cop = comp[1] + if comp[0] == NAME: + if len(nodelist[2]) == 3: + if cop == 'not': + cop = 'not in' + else: + cop = 'is not' + try: + cop = _ops[cop] + except KeyError: + raise SyntaxError(repr(cop)+" operator not allowed in environment markers") + return cop(evaluate(nodelist[1]), evaluate(nodelist[3])) + + _ops.update({ + symbol.test: test, symbol.and_test: and_test, symbol.atom: atom, + symbol.comparison: comparison, 'not in': lambda x,y: x not in y, + 'in': lambda x,y: x in y, '==': operator.eq, '!=': operator.ne, + }) + if hasattr(symbol,'or_test'): + _ops[symbol.or_test] = test + + def interpret(nodelist): + while len(nodelist)==2: nodelist = nodelist[1] + try: + op = _ops[nodelist[0]] + except KeyError: + raise SyntaxError("Comparison or logical expression expected") + raise SyntaxError("Language feature not supported in environment markers: "+symbol.sym_name[nodelist[0]]) + return op(nodelist) + + def evaluate(nodelist): + while len(nodelist)==2: nodelist = nodelist[1] + kind = nodelist[0] + name = nodelist[1] + #while len(name)==2: name = name[1] + if kind==NAME: + try: + op = _marker_values[name] + except KeyError: + raise SyntaxError("Unknown name %r" % name) + return op() + if kind==STRING: + s = nodelist[1] + if s[:1] not in "'\"" or s.startswith('"""') or s.startswith("'''") \ + or '\\' in s: + raise SyntaxError( + "Only plain strings allowed in environment markers") + return s[1:-1] + raise SyntaxError("Language feature not supported in environment markers") + + return interpret(parser.expr(text).totuple(1)[1]) + +def _markerlib_evaluate(text): + """ + Evaluate a PEP 426 environment marker using markerlib. + Return a boolean indicating the marker result in this environment. + Raise SyntaxError if marker is invalid. + """ + import _markerlib + # markerlib implements Metadata 1.2 (PEP 345) environment markers. + # Translate the variables to Metadata 2.0 (PEP 426). + env = _markerlib.default_environment() + for key in env.keys(): + new_key = key.replace('.', '_') + env[new_key] = env.pop(key) + try: + result = _markerlib.interpret(text, env) + except NameError: + e = sys.exc_info()[1] + raise SyntaxError(e.args[0]) + return result + +if 'parser' not in globals(): + # fallback to less-complete _markerlib implementation if 'parser' module + # is not available. + evaluate_marker = _markerlib_evaluate + class NullProvider: """Try to implement resources and metadata for arbitrary PEP 302 loaders""" @@ -1879,7 +2031,7 @@ def _handle_ns(packageName, path_item): return None module = sys.modules.get(packageName) if module is None: - module = sys.modules[packageName] = types.ModuleType(packageName) + module = sys.modules[packageName] = imp.new_module(packageName) module.__path__ = []; _set_parent_ns(packageName) elif not hasattr(module,'__path__'): raise TypeError("Not a package:", packageName) @@ -2058,7 +2210,6 @@ def parse_version(s): parts.pop() parts.append(part) return tuple(parts) - class EntryPoint(object): """Object representing an advertised importable object""" @@ -2299,7 +2450,14 @@ class Distribution(object): dm = self.__dep_map = {None: []} for name in 'requires.txt', 'depends.txt': for extra,reqs in split_sections(self._get_metadata(name)): - if extra: extra = safe_extra(extra) + if extra: + if ':' in extra: + extra, marker = extra.split(':',1) + if invalid_marker(marker): + reqs=[] # XXX warn + elif not evaluate_marker(marker): + reqs=[] + extra = safe_extra(extra) or None dm.setdefault(extra,[]).extend(parse_requirements(reqs)) return dm _dep_map = property(_dep_map) @@ -2323,6 +2481,8 @@ class Distribution(object): for line in self.get_metadata_lines(name): yield line + + def activate(self,path=None): """Ensure distribution is importable on `path` (default=sys.path)""" if path is None: path = sys.path @@ -2361,6 +2521,9 @@ class Distribution(object): raise AttributeError,attr return getattr(self._provider, attr) + + + #@classmethod def from_filename(cls,filename,metadata=None, **kw): return cls.from_location( @@ -2402,42 +2565,16 @@ class Distribution(object): - - - - - - - - - - - - def insert_on(self, path, loc = None): """Insert self.location in path before its nearest parent directory""" loc = loc or self.location - - if self.project_name == 'setuptools': - try: - version = self.version - except ValueError: - version = '' - if '0.7' in version: - raise ValueError( - "A 0.7-series setuptools cannot be installed " - "with distribute. Found one at %s" % str(self.location)) - if not loc: return - if path is sys.path: - self.check_version_conflict() - nloc = _normalize_cached(loc) bdir = os.path.dirname(nloc) - npath= map(_normalize_cached, path) + npath= [(p and _normalize_cached(p) or p) for p in path] bp = None for p, item in enumerate(npath): @@ -2445,10 +2582,14 @@ class Distribution(object): break elif item==bdir and self.precedence==EGG_DIST: # if it's an .egg, give it precedence over its directory + if path is sys.path: + self.check_version_conflict() path.insert(p, loc) npath.insert(p, nloc) break else: + if path is sys.path: + self.check_version_conflict() path.append(loc) return @@ -2465,9 +2606,8 @@ class Distribution(object): return - def check_version_conflict(self): - if self.key=='distribute': + if self.key=='setuptools': return # ignore the inevitable setuptools self-conflicts :( nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt')) @@ -2726,7 +2866,7 @@ class Requirement: def __contains__(self,item): if isinstance(item,Distribution): - if item.key <> self.key: return False + if item.key != self.key: return False if self.index: item = item.parsed_version # only get if we need it elif isinstance(item,basestring): item = parse_version(item) @@ -2748,22 +2888,11 @@ class Requirement: def __repr__(self): return "Requirement.parse(%r)" % str(self) #@staticmethod - def parse(s, replacement=True): + def parse(s): reqs = list(parse_requirements(s)) if reqs: - if len(reqs) == 1: - founded_req = reqs[0] - # if asked for setuptools distribution - # and if distribute is installed, we want to give - # distribute instead - if _override_setuptools(founded_req) and replacement: - distribute = list(parse_requirements('distribute')) - if len(distribute) == 1: - return distribute[0] - return founded_req - else: - return founded_req - + if len(reqs)==1: + return reqs[0] raise ValueError("Expected only one requirement", s) raise ValueError("No requirements found", s) @@ -2780,26 +2909,6 @@ state_machine = { } -def _override_setuptools(req): - """Return True when distribute wants to override a setuptools dependency. - - We want to override when the requirement is setuptools and the version is - a variant of 0.6. - - """ - if req.project_name == 'setuptools': - if not len(req.specs): - # Just setuptools: ok - return True - for comparator, version in req.specs: - if comparator in ['==', '>=', '>']: - if '0.7' in version: - # We want some setuptools not from the 0.6 series. - return False - return True - return False - - def _get_mro(cls): """Get an mro for a type or classic class""" if not isinstance(cls,type): @@ -2865,7 +2974,6 @@ _initialize(globals()) # Prepare the master working set and make the ``require()`` API available _declare_state('object', working_set = WorkingSet()) - try: # Does the main program list any requirements? from __main__ import __requires__ @@ -22,19 +22,44 @@ try: except Exception: pass -VERSION = '0.6.50' -PACKAGE_INDEX = 'https://pypi.python.org/pypi' +VERSION = '0.7.8' PACKAGE_INDEX = 'https://pypi.python.org/pypi' -def get_next_version(): - digits = map(int, VERSION.split('.')) - digits[-1] += 1 - return '.'.join(map(str, digits)) +def set_versions(): + global VERSION + version = raw_input("Release as version [%s]> " % VERSION) or VERSION + if version != VERSION: + VERSION = bump_versions(version) + +def infer_next_version(version): + """ + Infer a next version from the current version by incrementing the last + number or appending a number. + + >>> infer_next_version('1.0') + '1.1' -NEXT_VERSION = get_next_version() + >>> infer_next_version('1.0b') + '1.0b1' + + >>> infer_next_version('1.0.9') + '1.0.10' + + >>> infer_next_version('1') + '2' + + >>> infer_next_version('') + '1' + """ + def incr(match): + ver = int(match.group(0) or '0') + return str(ver + 1) + return re.sub('\d*$', incr, version) -files_with_versions = ('docs/conf.py', 'setup.py', 'release.py', - 'README.txt', 'distribute_setup.py') +files_with_versions = ( + 'docs/conf.py', 'setup.py', 'release.py', 'ez_setup.py', 'README.txt', + 'setuptools/__init__.py', +) def get_repo_name(): """ @@ -65,7 +90,7 @@ def get_mercurial_creds(system='https://bitbucket.org', username=None): Credential = collections.namedtuple('Credential', 'username password') return Credential(username, password) -def add_milestone_and_version(version=NEXT_VERSION): +def add_milestone_and_version(version): auth = 'Basic ' + ':'.join(get_mercurial_creds()).encode('base64').strip() headers = { 'Authorization': auth, @@ -81,12 +106,17 @@ def add_milestone_and_version(version=NEXT_VERSION): except urllib2.HTTPError as e: print(e.fp.read()) -def bump_versions(): - list(map(bump_version, files_with_versions)) +def bump_versions(target_ver): + for filename in files_with_versions: + bump_version(filename, target_ver) + subprocess.check_call(['hg', 'ci', '-m', + 'Bumped to {target_ver} in preparation for next ' + 'release.'.format(**vars())]) + return target_ver -def bump_version(filename): +def bump_version(filename, target_ver): with open(filename, 'rb') as f: - lines = [line.replace(VERSION, NEXT_VERSION) for line in f] + lines = [line.replace(VERSION, target_ver) for line in f] with open(filename, 'wb') as f: f.writelines(lines) @@ -96,6 +126,8 @@ def do_release(): assert has_sphinx(), "You must have Sphinx installed to release" + set_versions() + res = raw_input('Have you read through the SCM changelog and ' 'confirmed the changelog is current for releasing {VERSION}? ' .format(**globals())) @@ -114,6 +146,20 @@ def do_release(): subprocess.check_call(['hg', 'update', VERSION]) + upload_to_pypi() + + # update to the tip for the next operation + subprocess.check_call(['hg', 'update']) + + # we just tagged the current version, bump for the next release. + next_ver = bump_versions(infer_next_version(VERSION)) + + # push the changes + subprocess.check_call(['hg', 'push']) + + add_milestone_and_version(next_ver) + +def upload_to_pypi(): linkify('CHANGES.txt', 'CHANGES (links).txt') has_docs = build_docs() @@ -123,27 +169,14 @@ def do_release(): sys.executable, 'setup.py', '-q', 'egg_info', '-RD', '-b', '', 'sdist', - 'register', '-r', PACKAGE_INDEX, - 'upload', '-r', PACKAGE_INDEX, + #'register', '-r', PACKAGE_INDEX, + #'upload', '-r', PACKAGE_INDEX, ] if has_docs: - cmd.extend(['upload_docs', '-r', PACKAGE_INDEX]) + cmd.extend([ + 'upload_docs', '-r', PACKAGE_INDEX + ]) subprocess.check_call(cmd) - upload_bootstrap_script() - - # update to the tip for the next operation - subprocess.check_call(['hg', 'update']) - - # we just tagged the current version, bump for the next release. - bump_versions() - subprocess.check_call(['hg', 'ci', '-m', - 'Bumped to {NEXT_VERSION} in preparation for next ' - 'release.'.format(**globals())]) - - # push the changes - subprocess.check_call(['hg', 'push']) - - add_milestone_and_version() def has_sphinx(): try: @@ -169,14 +202,6 @@ def build_docs(): subprocess.check_call(cmd, cwd='docs') return True -def upload_bootstrap_script(): - scp_command = 'pscp' if sys.platform.startswith('win') else 'scp' - try: - subprocess.check_call([scp_command, 'distribute_setup.py', - 'pypi@ziade.org:python-distribute.org/']) - except: - print("Unable to upload bootstrap script. Ask Tarek to do it.") - def linkify(source, dest): with open(source) as source: out = _linkified_text(source.read()) @@ -209,7 +234,7 @@ def _linkified_text(rst_content): anchors = sorted(anchors) - bitroot = 'http://bitbucket.org/tarek/distribute' + bitroot = 'https://bitbucket.org/tarek/distribute' rst_content += "\n" for x in anchors: issue = re.findall(r'\d+', x)[0] @@ -1,6 +1,5 @@ [egg_info] tag_build = dev -tag_svn_revision = 1 [aliases] release = egg_info -RDb '' @@ -21,7 +21,7 @@ if sys.version_info >= (3,): manifest_file.close() dir_util.create_tree(tmp_src, fl.files) outfiles_2to3 = [] - dist_script = os.path.join("build", "src", "distribute_setup.py") + dist_script = os.path.join("build", "src", "ez_setup.py") for f in fl.files: outf, copied = file_util.copy_file(f, os.path.join(tmp_src, f), update=1) if copied and outf.endswith(".py") and outf != dist_script: @@ -46,7 +46,7 @@ exec(init_file.read(), d) init_file.close() SETUP_COMMANDS = d['__all__'] -VERSION = "0.6.50" +VERSION = "0.7.8" from setuptools import setup, find_packages from setuptools.command.build_py import build_py as _build_py @@ -55,7 +55,12 @@ from setuptools.command.test import test as _test scripts = [] console_scripts = ["easy_install = setuptools.command.easy_install:main"] -if os.environ.get("DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT") is None: + +# Gentoo distributions manage the python-version-specific scripts themselves, +# so they define an environment variable to suppress the creation of the +# version-specific scripts. +if os.environ.get("SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT") in (None, "", "0") and \ + os.environ.get("DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT") in (None, "", "0"): console_scripts.append("easy_install-%s = setuptools.command.easy_install:main" % sys.version[:3]) # specific command that is used to generate windows .exe files @@ -82,7 +87,7 @@ class build_py(_build_py): class test(_test): """Specific test class to avoid rewriting the entry_points.txt""" def run(self): - entry_points = os.path.join('distribute.egg-info', 'entry_points.txt') + entry_points = os.path.join('setuptools.egg-info', 'entry_points.txt') if not os.path.exists(entry_points): _test.run(self) @@ -107,32 +112,6 @@ class test(_test): f.close() -# if we are installing Distribute using "python setup.py install" -# we need to get setuptools out of the way -def _easy_install_marker(): - return (len(sys.argv) == 5 and sys.argv[2] == 'bdist_egg' and - sys.argv[3] == '--dist-dir' and 'egg-dist-tmp-' in sys.argv[-1]) - -def _buildout_marker(): - command = os.environ.get('_') - if command: - return 'buildout' in os.path.basename(command) - -def _being_installed(): - if os.environ.get('DONT_PATCH_SETUPTOOLS') is not None: - return False - if _buildout_marker(): - # Installed by buildout, don't mess with a global setuptools. - return False - # easy_install marker - if "--help" in sys.argv[1:] or "-h" in sys.argv[1:]: # Don't bother doing anything if they're just asking for help - return False - return 'install' in sys.argv[1:] or _easy_install_marker() - -if _being_installed(): - from distribute_setup import _before_install - _before_install() - readme_file = open('README.txt') # the release script adds hyperlinks to issues if os.path.exists('CHANGES (links).txt'): @@ -140,12 +119,17 @@ if os.path.exists('CHANGES (links).txt'): else: # but if the release script has not run, fall back to the source file changes_file = open('CHANGES.txt') -long_description = readme_file.read() + changes_file.read() +long_description = readme_file.read() + '\n' + changes_file.read() readme_file.close() changes_file.close() +package_data = {'setuptools': ['site-patch.py']} +if sys.platform == 'win32': + package_data.setdefault('setuptools', []).extend(['*.exe']) + package_data.setdefault('setuptools.command', []).extend(['*.xml']) + dist = setup( - name="distribute", + name="setuptools", version=VERSION, description="Easily download, build, install, upgrade, and uninstall " "Python packages", @@ -154,11 +138,11 @@ dist = setup( license="PSF or ZPL", long_description = long_description, keywords = "CPAN PyPI distutils eggs package management", - url = "http://packages.python.org/distribute", + url = "https://pypi.python.org/pypi/setuptools", test_suite = 'setuptools.tests', src_root = src_root, packages = find_packages(), - package_data = {'setuptools':['*.exe', 'site-patch.py'], 'setuptools.command':['*.xml']}, + package_data = package_data, py_modules = ['pkg_resources', 'easy_install'], @@ -229,9 +213,20 @@ dist = setup( Topic :: System :: Systems Administration Topic :: Utilities """).strip().splitlines(), - scripts = scripts, + extras_require = { + "ssl:sys_platform=='win32'": "wincertstore==0.1", + "ssl:sys_platform=='win32' and python_version=='2.4'": "ctypes==1.0.2", + "ssl:python_version in '2.4, 2.5'":"ssl==1.16", + "certs": "certifi==0.0.8", + }, + dependency_links = [ + 'https://pypi.python.org/packages/source/c/certifi/certifi-0.0.8.tar.gz#md5=dc5f5e7f0b5fc08d27654b17daa6ecec', + 'https://pypi.python.org/packages/source/s/ssl/ssl-1.16.tar.gz#md5=fb12d335d56f3c8c7c1fefc1c06c4bfb', + 'https://pypi.python.org/packages/source/w/wincertstore/wincertstore-0.1.zip#md5=2f9accbebe8f7b4c06ac7aa83879b81c', + 'https://bitbucket.org/pypa/setuptools/downloads/ctypes-1.0.2.win32-py2.4.exe#md5=9092a0ad5a3d79fa2d980f1ddc5e9dbc', + 'https://bitbucket.org/pypa/setuptools/downloads/ssl-1.16-py2.4-win32.egg#md5=3cfa2c526dc66e318e8520b6f1aadce5', + 'https://bitbucket.org/pypa/setuptools/downloads/ssl-1.16-py2.5-win32.egg#md5=85ad1cda806d639743121c0bbcb5f39b', + ], + scripts = [], + # tests_require = "setuptools[ssl]", ) - -if _being_installed(): - from distribute_setup import _after_install - _after_install(dist) diff --git a/setuptools.egg-info/dependency_links.txt b/setuptools.egg-info/dependency_links.txt new file mode 100644 index 00000000..c688b7ea --- /dev/null +++ b/setuptools.egg-info/dependency_links.txt @@ -0,0 +1,6 @@ +https://pypi.python.org/packages/source/c/certifi/certifi-0.0.8.tar.gz#md5=dc5f5e7f0b5fc08d27654b17daa6ecec +https://pypi.python.org/packages/source/s/ssl/ssl-1.16.tar.gz#md5=fb12d335d56f3c8c7c1fefc1c06c4bfb +https://pypi.python.org/packages/source/w/wincertstore/wincertstore-0.1.zip#md5=2f9accbebe8f7b4c06ac7aa83879b81c +https://bitbucket.org/pypa/setuptools/downloads/ctypes-1.0.2.win32-py2.4.exe#md5=9092a0ad5a3d79fa2d980f1ddc5e9dbc +https://bitbucket.org/pypa/setuptools/downloads/ssl-1.16-py2.4-win32.egg#md5=3cfa2c526dc66e318e8520b6f1aadce5 +https://bitbucket.org/pypa/setuptools/downloads/ssl-1.16-py2.5-win32.egg#md5=85ad1cda806d639743121c0bbcb5f39b diff --git a/setuptools.egg-info/entry_points.txt b/setuptools.egg-info/entry_points.txt new file mode 100644 index 00000000..663882d6 --- /dev/null +++ b/setuptools.egg-info/entry_points.txt @@ -0,0 +1,62 @@ +[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/setuptools.egg-info/requires.txt b/setuptools.egg-info/requires.txt new file mode 100644 index 00000000..91d84d9c --- /dev/null +++ b/setuptools.egg-info/requires.txt @@ -0,0 +1,13 @@ + + +[ssl:sys_platform=='win32'] +wincertstore==0.1 + +[ssl:sys_platform=='win32' and python_version=='2.4'] +ctypes==1.0.2 + +[certs] +certifi==0.0.8 + +[ssl:python_version in '2.4, 2.5'] +ssl==1.16
\ No newline at end of file diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 9de373f9..104ea064 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -8,22 +8,12 @@ from distutils.util import convert_path import os import sys -__version__ = '0.6' +__version__ = '0.7.8' __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'find_packages' ] -# This marker is used to simplify the process that checks is the -# setuptools package was installed by the Setuptools project -# or by the Distribute project, in case Setuptools creates -# a distribution with the same version. -# -# The distribute_setup script for instance, will check if this -# attribute is present to decide whether to reinstall the package -# or not. -_distribute = True - bootstrap_install_from = None # If we run 2to3 on .py files, should we also convert docstrings? @@ -51,7 +41,7 @@ def find_packages(where='.', exclude=()): os.path.isfile(os.path.join(fn,'__init__.py')) ): out.append(prefix+name); stack.append((fn,prefix+name+'.')) - for pat in list(exclude)+['ez_setup', 'distribute_setup']: + for pat in list(exclude)+['ez_setup']: from fnmatch import fnmatchcase out = [item for item in out if not fnmatchcase(item,pat)] return out diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py index e22b25c0..1109f346 100755 --- a/setuptools/archive_util.py +++ b/setuptools/archive_util.py @@ -6,7 +6,7 @@ __all__ = [ "UnrecognizedFormat", "extraction_drivers", "unpack_directory", ] -import zipfile, tarfile, os, shutil +import zipfile, tarfile, os, shutil, posixpath from pkg_resources import ensure_directory from distutils.errors import DistutilsError @@ -138,7 +138,7 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): name = info.filename # don't extract absolute paths or ones with .. in them - if name.startswith('/') or '..' in name: + if name.startswith('/') or '..' in name.split('/'): continue target = os.path.join(extract_dir, *name.split('/')) @@ -172,43 +172,39 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): by ``tarfile.open()``). See ``unpack_archive()`` for an explanation of the `progress_filter` argument. """ - try: tarobj = tarfile.open(filename) except tarfile.TarError: raise UnrecognizedFormat( "%s is not a compressed or uncompressed tar file" % (filename,) ) - try: tarobj.chown = lambda *args: None # don't do any chowning! for member in tarobj: name = member.name # don't extract absolute paths or ones with .. in them - if not name.startswith('/') and '..' not in name: + if not name.startswith('/') and '..' not in name.split('/'): prelim_dst = os.path.join(extract_dir, *name.split('/')) - final_dst = progress_filter(name, prelim_dst) - # If progress_filter returns None, then we do not extract - # this file - # TODO: Do we really need to limit to just these file types? - # tarobj.extract() will handle all files on all platforms, - # turning file types that aren't allowed on that platform into - # regular files. - if final_dst and (member.isfile() or member.isdir() or - member.islnk() or member.issym()): - tarobj.extract(member, extract_dir) - if final_dst != prelim_dst: - shutil.move(prelim_dst, final_dst) + + # resolve any links and to extract the link targets as normal files + while member is not None and (member.islnk() or member.issym()): + linkpath = member.linkname + if member.issym(): + linkpath = posixpath.join(posixpath.dirname(member.name), linkpath) + linkpath = posixpath.normpath(linkpath) + member = tarobj._getmember(linkpath) + + if member is not None and (member.isfile() or member.isdir()): + final_dst = progress_filter(name, prelim_dst) + if final_dst: + if final_dst.endswith(os.sep): + final_dst = final_dst[:-1] + try: + tarobj._extract_member(member, final_dst) # XXX Ugh + except tarfile.ExtractError: + pass # chown/chmod/mkfifo/mknode/makedev failed return True finally: tarobj.close() - - - extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile - - - - - diff --git a/setuptools/command/alias.py b/setuptools/command/alias.py index f5368b29..40c00b55 100755 --- a/setuptools/command/alias.py +++ b/setuptools/command/alias.py @@ -9,7 +9,7 @@ def shquote(arg): """Quote an argument for later parsing by shlex.split()""" for c in '"', "'", "\\", "#": if c in arg: return repr(arg) - if arg.split()<>[arg]: + if arg.split()!=[arg]: return repr(arg) return arg @@ -33,7 +33,7 @@ class alias(option_base): def finalize_options(self): option_base.finalize_options(self) - if self.remove and len(self.args)<>1: + if self.remove and len(self.args)!=1: raise DistutilsOptionError( "Must specify exactly one argument (the alias name) when " "using --remove" diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 17fae984..1ba0499e 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -7,10 +7,14 @@ import sys, os, marshal from setuptools import Command from distutils.dir_util import remove_tree, mkpath try: - from distutils.sysconfig import get_python_version, get_python_lib + # Python 2.7 or >=3.2 + from sysconfig import get_path, get_python_version + def _get_purelib(): + return get_path("purelib") except ImportError: - from sysconfig import get_python_version - from distutils.sysconfig import get_python_lib + from distutils.sysconfig import get_python_lib, get_python_version + def _get_purelib(): + return get_python_lib(False) from distutils import log from distutils.errors import DistutilsSetupError @@ -130,7 +134,7 @@ class bdist_egg(Command): # Hack for packages that install data to install's --install-lib self.get_finalized_command('install').install_lib = self.bdist_dir - site_packages = os.path.normcase(os.path.realpath(get_python_lib())) + site_packages = os.path.normcase(os.path.realpath(_get_purelib())) old, self.distribution.data_files = self.distribution.data_files,[] for item in old: @@ -170,12 +174,13 @@ class bdist_egg(Command): def run(self): # Generate metadata first self.run_command("egg_info") - # We run install_lib before install_data, because some data hacks # pull their data path from the install_lib command. log.info("installing library code to %s" % self.bdist_dir) instcmd = self.get_finalized_command('install') old_root = instcmd.root; instcmd.root = None + if self.distribution.has_c_libraries() and not self.skip_build: + self.run_command('build_clib') cmd = self.call_command('install_lib', warn_dir=0) instcmd.root = old_root @@ -195,7 +200,6 @@ class bdist_egg(Command): to_compile.extend(self.make_init_files()) if to_compile: cmd.byte_compile(to_compile) - if self.distribution.data_files: self.do_install_data() @@ -407,7 +411,7 @@ def write_safety_flag(egg_dir, safe): for flag,fn in safety_flags.items(): fn = os.path.join(egg_dir, fn) if os.path.exists(fn): - if safe is None or bool(safe)<>flag: + if safe is None or bool(safe)!=flag: os.unlink(fn) elif safe is not None and bool(safe)==flag: f=open(fn,'wt'); f.write('\n'); f.close() diff --git a/setuptools/command/bdist_wininst.py b/setuptools/command/bdist_wininst.py index 93e6846d..e8521f83 100755 --- a/setuptools/command/bdist_wininst.py +++ b/setuptools/command/bdist_wininst.py @@ -2,26 +2,24 @@ from distutils.command.bdist_wininst import bdist_wininst as _bdist_wininst import os, sys class bdist_wininst(_bdist_wininst): + _good_upload = _bad_upload = None def create_exe(self, arcname, fullname, bitmap=None): _bdist_wininst.create_exe(self, arcname, fullname, bitmap) - dist_files = getattr(self.distribution, 'dist_files', []) - + installer_name = self.get_installer_filename(fullname) if self.target_version: - installer_name = os.path.join(self.dist_dir, - "%s.win32-py%s.exe" % - (fullname, self.target_version)) pyversion = self.target_version - - # fix 2.5 bdist_wininst ignoring --target-version spec - bad = ('bdist_wininst','any',installer_name) - if bad in dist_files: - dist_files.remove(bad) + # fix 2.5+ bdist_wininst ignoring --target-version spec + self._bad_upload = ('bdist_wininst', 'any', installer_name) else: - installer_name = os.path.join(self.dist_dir, - "%s.win32.exe" % fullname) pyversion = 'any' - good = ('bdist_wininst', pyversion, installer_name) + self._good_upload = ('bdist_wininst', pyversion, installer_name) + + def _fix_upload_names(self): + good, bad = self._good_upload, self._bad_upload + dist_files = getattr(self.distribution, 'dist_files', []) + if bad in dist_files: + dist_files.remove(bad) if good not in dist_files: dist_files.append(good) @@ -36,6 +34,49 @@ class bdist_wininst(_bdist_wininst): self._is_running = True try: _bdist_wininst.run(self) + self._fix_upload_names() finally: self._is_running = False + + if not hasattr(_bdist_wininst, 'get_installer_filename'): + def get_installer_filename(self, fullname): + # Factored out to allow overriding in subclasses + if self.target_version: + # if we create an installer for a specific python version, + # it's better to include this in the name + installer_name = os.path.join(self.dist_dir, + "%s.win32-py%s.exe" % + (fullname, self.target_version)) + else: + installer_name = os.path.join(self.dist_dir, + "%s.win32.exe" % fullname) + return installer_name + # get_installer_filename() + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index 4a94572c..50a039ce 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -9,9 +9,15 @@ import os, sys from distutils.file_util import copy_file from setuptools.extension import Library from distutils.ccompiler import new_compiler -from distutils.sysconfig import customize_compiler, get_config_var -get_config_var("LDSHARED") # make sure _config_vars is initialized -from distutils.sysconfig import _config_vars +from distutils.sysconfig import customize_compiler +try: + # Python 2.7 or >=3.2 + from sysconfig import _CONFIG_VARS +except ImportError: + from distutils.sysconfig import get_config_var + get_config_var("LDSHARED") # make sure _config_vars is initialized + del get_config_var + from distutils.sysconfig import _config_vars as _CONFIG_VARS from distutils import log from distutils.errors import * @@ -82,17 +88,15 @@ class build_ext(_build_ext): def get_ext_filename(self, fullname): filename = _build_ext.get_ext_filename(self,fullname) - if fullname not in self.ext_map: - return filename - ext = self.ext_map[fullname] - if isinstance(ext,Library): - fn, ext = os.path.splitext(filename) - return self.shlib_compiler.library_filename(fn,libtype) - elif use_stubs and ext._links_to_dynamic: - d,fn = os.path.split(filename) - return os.path.join(d,'dl-'+fn) - else: - return filename + if fullname in self.ext_map: + ext = self.ext_map[fullname] + if isinstance(ext,Library): + fn, ext = os.path.splitext(filename) + return self.shlib_compiler.library_filename(fn,libtype) + elif use_stubs and ext._links_to_dynamic: + d,fn = os.path.split(filename) + return os.path.join(d,'dl-'+fn) + return filename def initialize_options(self): _build_ext.initialize_options(self) @@ -133,16 +137,16 @@ class build_ext(_build_ext): compiler=self.compiler, dry_run=self.dry_run, force=self.force ) if sys.platform == "darwin": - tmp = _config_vars.copy() + tmp = _CONFIG_VARS.copy() try: # XXX Help! I don't have any idea whether these are right... - _config_vars['LDSHARED'] = "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup" - _config_vars['CCSHARED'] = " -dynamiclib" - _config_vars['SO'] = ".dylib" + _CONFIG_VARS['LDSHARED'] = "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup" + _CONFIG_VARS['CCSHARED'] = " -dynamiclib" + _CONFIG_VARS['SO'] = ".dylib" customize_compiler(compiler) finally: - _config_vars.clear() - _config_vars.update(tmp) + _CONFIG_VARS.clear() + _CONFIG_VARS.update(tmp) else: customize_compiler(compiler) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 77b0bc31..60b3e011 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -7,7 +7,7 @@ A tool for doing automatic download/extract/build of distutils-based Python packages. For detailed documentation, see the accompanying EasyInstall.txt file, or visit the `EasyInstall home page`__. -__ http://packages.python.org/distribute/easy_install.html +__ https://pythonhosted.org/setuptools/easy_install.html """ import sys @@ -25,9 +25,22 @@ import pkg_resources from setuptools import Command, _dont_write_bytecode from setuptools.sandbox import run_setup from distutils import log, dir_util +try: + # Python 2.7 or >=3.2 + from sysconfig import get_config_vars, get_path + def _get_platlib(): + return get_path("platlib") + def _get_purelib(): + return get_path("purelib") +except ImportError: + from distutils.sysconfig import get_config_vars, get_python_lib + def _get_platlib(): + return get_python_lib(True) + def _get_purelib(): + return get_python_lib(False) + from distutils.util import get_platform from distutils.util import convert_path, subst_vars -from distutils.sysconfig import get_python_lib, get_config_vars from distutils.errors import DistutilsArgError, DistutilsOptionError, \ DistutilsError, DistutilsPlatformError from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS @@ -202,7 +215,7 @@ class easy_install(Command): def finalize_options(self): if self.version: - print 'distribute %s' % get_distribution('distribute').version + print 'setuptools %s' % get_distribution('setuptools').version sys.exit() py_version = sys.version.split()[0] @@ -283,7 +296,7 @@ class easy_install(Command): else: self.all_site_dirs.append(normalize_path(d)) if not self.editable: self.check_site_dir() - self.index_url = self.index_url or "http://pypi.python.org/simple" + self.index_url = self.index_url or "https://pypi.python.org/simple" self.shadow_path = self.all_site_dirs[:] for path_item in self.install_dir, normalize_path(self.script_dir): if path_item not in self.shadow_path: @@ -402,7 +415,7 @@ class easy_install(Command): # Is it a configured, PYTHONPATH, implicit, or explicit site dir? is_site_dir = instdir in self.all_site_dirs - if not is_site_dir: + if not is_site_dir and not self.multi_version: # No? Then directly test whether it does .pth file processing is_site_dir = self.check_pth_processing() else: @@ -467,7 +480,7 @@ variable. For information on other options, you may wish to consult the documentation at: - http://packages.python.org/distribute/easy_install.html + https://pythonhosted.org/setuptools/easy_install.html Please make the appropriate changes for your system and try again. """ @@ -590,7 +603,6 @@ Please make the appropriate changes for your system and try again. spec, tmpdir, self.upgrade, self.editable, not self.always_copy, self.local_index ) - if dist is None: msg = "Could not find suitable distribution for %r" % spec if self.always_copy: @@ -661,8 +673,7 @@ Please make the appropriate changes for your system and try again. self.update_pth(dist) self.package_index.add(dist) self.local_index.add(dist) - if not self.editable: - self.install_egg_scripts(dist) + self.install_egg_scripts(dist) self.installed_projects[dist.key] = dist log.info(self.installation_report(requirement, dist, *info)) if (dist.has_metadata('dependency_links.txt') and @@ -710,7 +721,7 @@ Please make the appropriate changes for your system and try again. return True if not dist.has_metadata('zip-safe'): return True - return True + return False def maybe_move(self, spec, dist_filename, setup_base): dst = os.path.join(self.build_directory, spec.key) @@ -896,7 +907,7 @@ Please make the appropriate changes for your system and try again. f = open(pkg_inf,'w') f.write('Metadata-Version: 1.0\n') for k,v in cfg.items('metadata'): - if k<>'target_version': + if k!='target_version': f.write('%s: %s\n' % (k.replace('_','-').title(), v)) f.close() script_dir = os.path.join(egg_info,'scripts') @@ -1170,7 +1181,8 @@ See the setuptools documentation for the "develop" command for more info. if not self.dry_run: self.pth_file.save() - if dist.key=='distribute': + + if dist.key=='setuptools': # Ensure that setuptools itself never becomes unavailable! # XXX should this check for latest version? filename = os.path.join(self.install_dir,'setuptools.pth') @@ -1190,7 +1202,6 @@ See the setuptools documentation for the "develop" command for more info. def pf(src,dst): if dst.endswith('.py') and not src.startswith('EGG-INFO/'): to_compile.append(dst) - to_chmod.append(dst) elif dst.endswith('.dll') or dst.endswith('.so'): to_chmod.append(dst) self.unpack_progress(src,dst) @@ -1229,6 +1240,7 @@ See the setuptools documentation for the "develop" command for more info. + def no_default_version_msg(self): return """bad install directory or PYTHONPATH @@ -1255,7 +1267,7 @@ Here are some of your options for correcting the problem: * You can set up the installation directory to support ".pth" files by using one of the approaches described here: - http://packages.python.org/distribute/easy_install.html#custom-installation-locations + https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations Please make the appropriate changes for your system and try again.""" % ( self.install_dir, os.environ.get('PYTHONPATH','') @@ -1399,8 +1411,7 @@ def get_site_dirs(): 'Python', sys.version[:3], 'site-packages')) - for plat_specific in (0,1): - site_lib = get_python_lib(plat_specific) + for site_lib in (_get_purelib(), _get_platlib()): if site_lib not in sitedirs: sitedirs.append(site_lib) if HAS_USER_SITE: @@ -1512,7 +1523,7 @@ def get_exe_prefixes(exe_filename): ('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''), ('PLATLIB/', ''), ('SCRIPTS/', 'EGG-INFO/scripts/'), - ('DATA/LIB/site-packages', ''), + ('DATA/lib/site-packages', ''), ] z = zipfile.ZipFile(exe_filename) try: @@ -1523,7 +1534,7 @@ def get_exe_prefixes(exe_filename): if parts[1].endswith('.egg-info'): prefixes.insert(0,('/'.join(parts[:2]), 'EGG-INFO/')) break - if len(parts)<>2 or not name.endswith('.pth'): + if len(parts)!=2 or not name.endswith('.pth'): continue if name.endswith('-nspkg.pth'): continue @@ -1842,7 +1853,7 @@ def get_script_args(dist, executable=sys_executable, wininst=False): ext = '-script.py' old = ['.py','.pyc','.pyo'] new_header = re.sub('(?i)pythonw.exe','python.exe',header) - if os.path.exists(new_header[2:-1]) or sys.platform!='win32': + if os.path.exists(new_header[2:-1].strip('"')) or sys.platform!='win32': hdr = new_header else: hdr = header @@ -1961,12 +1972,6 @@ usage: %(script)s [options] requirement_or_url ... def _show_help(self,*args,**kw): with_ei_usage(lambda: Distribution._show_help(self,*args,**kw)) - def find_config_files(self): - files = Distribution.find_config_files(self) - if 'setup.cfg' in files: - files.remove('setup.cfg') - return files - if argv is None: argv = sys.argv[1:] diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 0c2ea0cc..b283b28a 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -210,7 +210,8 @@ class egg_info(Command): - def get_svn_revision(self): + @staticmethod + def get_svn_revision(): revision = 0 urlre = re.compile('url="([^"]+)"') revre = re.compile('committed-rev="(\d+)"') @@ -224,18 +225,21 @@ class egg_info(Command): data = f.read() f.close() - if data.startswith('10') or data.startswith('9') or data.startswith('8'): + if data.startswith('<?xml'): + dirurl = urlre.search(data).group(1) # get repository URL + localrev = max([int(m.group(1)) for m in revre.finditer(data)]+[0]) + else: + try: svnver = int(data.splitlines()[0]) + except: svnver=-1 + if svnver<8: + log.warn("unrecognized .svn/entries format; skipping %s", base) + dirs[:] = [] + continue + data = map(str.splitlines,data.split('\n\x0c\n')) del data[0][0] # get rid of the '8' or '9' or '10' dirurl = data[0][3] localrev = max([int(d[9]) for d in data if len(d)>9 and d[9]]+[0]) - elif data.startswith('<?xml'): - dirurl = urlre.search(data).group(1) # get repository URL - localrev = max([int(m.group(1)) for m in revre.finditer(data)]+[0]) - else: - log.warn("unrecognized .svn/entries format; skipping %s", base) - dirs[:] = [] - continue if base==os.curdir: base_url = dirurl+'/' # save the root url elif not dirurl.startswith(base_url): @@ -248,9 +252,6 @@ class egg_info(Command): - - - def find_sources(self): """Generate SOURCES.txt manifest file""" manifest_filename = os.path.join(self.egg_info,"SOURCES.txt") diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 2fa3771a..f8f964b3 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -1,6 +1,7 @@ from distutils.command.sdist import sdist as _sdist from distutils.util import convert_path from distutils import log +from glob import glob import os, re, sys, pkg_resources from glob import glob @@ -41,7 +42,6 @@ def joinpath(prefix,suffix): - def walk_revctrl(dirname=''): """Find all files under revision control""" for ep in pkg_resources.iter_entry_points('setuptools.file_finders'): @@ -89,18 +89,22 @@ def entries_finder(dirname, filename): f = open(filename,'rU') data = f.read() f.close() - if data.startswith('10') or data.startswith('9') or data.startswith('8'): + if data.startswith('<?xml'): + for match in entries_pattern.finditer(data): + yield joinpath(dirname,unescape(match.group(1))) + else: + svnver=-1 + try: svnver = int(data.splitlines()[0]) + except: pass + if svnver<8: + log.warn("unrecognized .svn/entries format in %s", os.path.abspath(dirname)) + return for record in map(str.splitlines, data.split('\n\x0c\n')[1:]): # subversion 1.6/1.5/1.4 if not record or len(record)>=6 and record[5]=="delete": continue # skip deleted yield joinpath(dirname, record[0]) - elif data.startswith('<?xml'): - for match in entries_pattern.finditer(data): - yield joinpath(dirname,unescape(match.group(1))) - else: - log.warn("unrecognized .svn/entries format in %s", os.path.abspath(dirname)) - + finders = [ (convert_path('CVS/Entries'), @@ -121,10 +125,6 @@ finders = [ - - - - class sdist(_sdist): """Smart sdist that finds anything supported by revision control""" @@ -157,7 +157,7 @@ class sdist(_sdist): import distutils.command if 'check' not in distutils.command.__all__: self.check_metadata() - + self.make_distribution() dist_files = getattr(self.distribution,'dist_files',[]) @@ -166,6 +166,26 @@ class sdist(_sdist): if data not in dist_files: dist_files.append(data) + def __read_template_hack(self): + # This grody hack closes the template file (MANIFEST.in) if an + # exception occurs during read_template. + # Doing so prevents an error when easy_install attempts to delete the + # file. + try: + _sdist.read_template(self) + except: + sys.exc_info()[2].tb_next.tb_frame.f_locals['template'].close() + raise + # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle + # has been fixed, so only override the method if we're using an earlier + # Python. + if ( + sys.version_info < (2,7,2) + or (3,0) <= sys.version_info < (3,1,4) + or (3,2) <= sys.version_info < (3,2,1) + ): + read_template = __read_template_hack + def add_defaults(self): standards = [READMES, self.distribution.script_name] @@ -219,26 +239,6 @@ class sdist(_sdist): build_scripts = self.get_finalized_command('build_scripts') self.filelist.extend(build_scripts.get_source_files()) - def __read_template_hack(self): - # This grody hack closes the template file (MANIFEST.in) if an - # exception occurs during read_template. - # Doing so prevents an error when easy_install attempts to delete the - # file. - try: - _sdist.read_template(self) - except: - sys.exc_info()[2].tb_next.tb_frame.f_locals['template'].close() - raise - # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle - # has been fixed, so only override the method if we're using an earlier - # Python. - if ( - sys.version_info < (2,7,2) - or (3,0) <= sys.version_info < (3,1,4) - or (3,2) <= sys.version_info < (3,2,1) - ): - read_template = __read_template_hack - def check_readme(self): for f in READMES: if os.path.exists(f): @@ -301,13 +301,4 @@ class sdist(_sdist): - - - - - - - - - # diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 21b9615c..4b500f68 100755 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -23,7 +23,7 @@ class upload(Command): description = "upload binary package to PyPI" - DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi' + DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi' user_options = [ ('repository=', 'r', diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 1d5a7445..6df3f394 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -2,7 +2,7 @@ """upload_docs Implements a Distutils 'upload_docs' subcommand (upload documentation to -PyPI's packages.python.org). +PyPI's pythonhosted.org). """ import os @@ -185,7 +185,7 @@ class upload_docs(upload): elif r.status == 301: location = r.getheader('Location') if location is None: - location = 'http://packages.python.org/%s/' % meta.get_name() + location = 'https://pythonhosted.org/%s/' % meta.get_name() self.announce('Upload successful. Visit %s' % location, log.INFO) else: diff --git a/setuptools/depends.py b/setuptools/depends.py index 4b7b3437..5fdf2d7e 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -36,7 +36,7 @@ class Require: def version_ok(self,version): """Is 'version' sufficiently up-to-date?""" return self.attribute is None or self.format is None or \ - str(version)<>"unknown" and version >= self.requested_version + str(version)!="unknown" and version >= self.requested_version def get_version(self, paths=None, default="unknown"): diff --git a/setuptools/dist.py b/setuptools/dist.py index 998a4dbe..907ce550 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -48,7 +48,6 @@ def assert_string_list(dist, attr, value): raise DistutilsSetupError( "%r must be a list of strings (got %r)" % (attr,value) ) - def check_nsp(dist, attr, value): """Verify that namespace packages are valid""" assert_string_list(dist,attr,value) @@ -62,14 +61,18 @@ def check_nsp(dist, attr, value): parent = '.'.join(nsp.split('.')[:-1]) if parent not in value: distutils.log.warn( - "%r is declared as a package namespace, but %r is not:" - " please correct this in setup.py", nsp, parent + "WARNING: %r is declared as a package namespace, but %r" + " is not: please correct this in setup.py", nsp, parent ) def check_extras(dist, attr, value): """Verify that extras_require mapping is valid""" try: for k,v in value.items(): + if ':' in k: + k,m = k.split(':',1) + if pkg_resources.invalid_marker(m): + raise DistutilsSetupError("Invalid environment marker: "+m) list(pkg_resources.parse_requirements(v)) except (TypeError,ValueError,AttributeError): raise DistutilsSetupError( @@ -78,9 +81,6 @@ def check_extras(dist, attr, value): "requirement specifiers." ) - - - def assert_bool(dist, attr, value): """Verify that value is True, False, 0, or 1""" if bool(value) != value: @@ -122,6 +122,47 @@ def check_package_data(dist, attr, value): "wildcard patterns" ) +def check_packages(dist, attr, value): + for pkgname in value: + if not re.match(r'\w+(\.\w+)*', pkgname): + distutils.log.warn( + "WARNING: %r not a valid package name; please use only" + ".-separated package names in setup.py", pkgname + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + class Distribution(_Distribution): """Distribution with support for features, tests, and package data @@ -845,11 +886,4 @@ class Feature: -def check_packages(dist, attr, value): - for pkgname in value: - if not re.match(r'\w+(\.\w+)*', pkgname): - distutils.log.warn( - "WARNING: %r not a valid package name; please use only" - ".-separated package names in setup.py", pkgname - ) diff --git a/setuptools/package_index.py b/setuptools/package_index.py index b0388628..4f39c70a 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -1,8 +1,9 @@ """PyPI and direct package downloading""" -import sys, os.path, re, urlparse, urllib, urllib2, shutil, random, socket, cStringIO +import sys, os.path, re, urlparse, urllib2, shutil, random, socket, cStringIO import itertools import base64 -import httplib +import httplib, urllib +from setuptools import ssl_support from pkg_resources import * from distutils import log from distutils.errors import DistutilsError @@ -11,8 +12,8 @@ try: except ImportError: from md5 import md5 from fnmatch import translate - from setuptools.py24compat import wraps +from setuptools.py27compat import get_all_headers EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$') HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I) @@ -58,6 +59,8 @@ def parse_bdist_wininst(name): def egg_info_for_url(url): scheme, server, path, parameters, query, fragment = urlparse.urlparse(url) base = urllib2.unquote(path.split('/')[-1]) + if server=='sourceforge.net' and base=='download': # XXX Yuck + base = urllib2.unquote(path.split('/')[-2]) if '#' in base: base, fragment = base.split('#',1) return base,fragment @@ -80,14 +83,12 @@ def distros_for_location(location, basename, metadata=None): if basename.endswith('.egg') and '-' in basename: # only one, unambiguous interpretation return [Distribution.from_location(location, basename, metadata)] - if basename.endswith('.exe'): win_base, py_ver, platform = parse_bdist_wininst(basename) if win_base is not None: return interpret_distro_name( location, win_base, metadata, py_ver, BINARY_DIST, platform ) - # Try source distro extensions (.zip, .tgz, etc.) # for ext in EXTENSIONS: @@ -186,17 +187,15 @@ def find_external_links(url, page): if match: yield urlparse.urljoin(url, htmldecode(match.group(1))) - -user_agent = "Python-urllib/%s distribute/%s" % ( - sys.version[:3], require('distribute')[0].version +user_agent = "Python-urllib/%s setuptools/%s" % ( + sys.version[:3], require('setuptools')[0].version ) - class PackageIndex(Environment): """A distribution index that scans web pages for download URLs""" - def __init__(self, index_url="http://pypi.python.org/simple", hosts=('*',), - *args, **kw + def __init__(self, index_url="https://pypi.python.org/simple", hosts=('*',), + ca_bundle=None, verify_ssl=True, *args, **kw ): Environment.__init__(self,*args,**kw) self.index_url = index_url + "/"[:not index_url.endswith('/')] @@ -205,8 +204,9 @@ class PackageIndex(Environment): self.package_pages = {} self.allows = re.compile('|'.join(map(translate,hosts))).match self.to_scan = [] - - + if verify_ssl and ssl_support.is_available and (ca_bundle or ssl_support.find_ca_bundle()): + self.opener = ssl_support.opener_for(ca_bundle) + else: self.opener = urllib2.urlopen def process_url(self, url, retrieve=False): """Evaluate a URL as a possible download, and maybe retrieve it""" @@ -232,10 +232,10 @@ class PackageIndex(Environment): return self.info("Reading %s", url) + self.fetched_urls[url] = True # prevent multiple fetch attempts f = self.open_url(url, "Download error on %s: %%s -- Some packages may not be found!" % url) if f is None: return - self.fetched_urls[url] = self.fetched_urls[f.url] = True - + self.fetched_urls[f.url] = True if 'html' not in f.headers.get('content-type', '').lower(): f.close() # not html, we can't process it return @@ -385,7 +385,7 @@ class PackageIndex(Environment): def check_md5(self, cs, info, filename, tfp): if re.match('md5=[0-9a-f]{32}$', info): self.debug("Validating md5 checksum for %s", filename) - if cs.hexdigest()<>info[4:]: + if cs.hexdigest()!=info[4:]: tfp.close() os.unlink(filename) raise DistutilsError( @@ -484,7 +484,6 @@ class PackageIndex(Environment): set, development and system eggs (i.e., those using the ``.egg-info`` format) will be ignored. """ - # process a Requirement self.info("Searching for %s", requirement) skipped = {} @@ -504,10 +503,9 @@ class PackageIndex(Environment): continue if dist in req and (dist.precedence<=SOURCE_DIST or not source): - self.info("Best match: %s", dist) - return dist.clone( - location=self.download(dist.location, tmpdir) - ) + return dist + + if force_scan: self.prescan() @@ -531,7 +529,10 @@ class PackageIndex(Environment): (source and "a source distribution of " or ""), requirement, ) - return dist + else: + self.info("Best match: %s", dist) + return dist.clone(location=self.download(dist.location, tmpdir)) + def fetch(self, requirement, tmpdir, force_scan=False, source=False): """Obtain a file suitable for fulfilling `requirement` @@ -547,12 +548,6 @@ class PackageIndex(Environment): return None - - - - - - def gen_setup(self, filename, fragment, tmpdir): match = EGG_FRAGMENT.match(fragment) dists = match and [d for d in @@ -614,8 +609,8 @@ class PackageIndex(Environment): size = -1 if "content-length" in headers: # Some servers return multiple Content-Length headers :( - content_length = headers.get("Content-Length") - size = int(content_length) + sizes = get_all_headers(headers, 'Content-Length') + size = max(map(int, sizes)) self.reporthook(url, filename, blocknum, bs, size) tfp = open(filename,'wb') while True: @@ -641,7 +636,7 @@ class PackageIndex(Environment): if url.startswith('file:'): return local_open(url) try: - return open_with_auth(url) + return open_with_auth(url, self.opener) except (ValueError, httplib.InvalidURL), v: msg = ' '.join([str(arg) for arg in v.args]) if warning: @@ -673,9 +668,8 @@ class PackageIndex(Environment): def _download_url(self, scheme, url, tmpdir): # Determine download filename # - name = filter(None,urlparse.urlparse(url)[2].split('/')) + name, fragment = egg_info_for_url(url) if name: - name = name[-1] while '..' in name: name = name.replace('..','.').replace('\\','_') else: @@ -700,8 +694,6 @@ class PackageIndex(Environment): self.url_ok(url, True) # raises error if not allowed return self._attempt_download(url, filename) - - def scan_url(self, url): self.process_url(url, True) @@ -728,10 +720,39 @@ class PackageIndex(Environment): os.unlink(filename) raise DistutilsError("Unexpected HTML page found at "+url) + + + + + + + + + + + + + + + def _download_svn(self, url, filename): url = url.split('#',1)[0] # remove any fragment for svn's sake + creds = '' + if url.lower().startswith('svn:') and '@' in url: + scheme, netloc, path, p, q, f = urlparse.urlparse(url) + if not netloc and path.startswith('//') and '/' in path[2:]: + netloc, path = path[2:].split('/',1) + auth, host = urllib.splituser(netloc) + if auth: + if ':' in auth: + user, pw = auth.split(':',1) + creds = " --username=%s --password=%s" % (user, pw) + else: + creds = " --username="+auth + netloc = host + url = urlparse.urlunparse((scheme, netloc, url, p, q, f)) self.info("Doing subversion checkout from %s to %s", url, filename) - os.system("svn checkout -q %s %s" % (url, filename)) + os.system("svn checkout%s -q %s %s" % (creds, url, filename)) return filename def _vcs_split_rev_from_url(self, url, pop_prefix=False): @@ -792,6 +813,18 @@ class PackageIndex(Environment): def warn(self, msg, *args): log.warn(msg, *args) + + + + + + + + + + + + # This pattern matches a character entity reference (a decimal numeric # references, a hexadecimal numeric reference, or a named reference). entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub @@ -860,7 +893,7 @@ def _encode_auth(auth): # strip the trailing carriage return return encoded.rstrip() -def open_with_auth(url): +def open_with_auth(url, opener=urllib2.urlopen): """Open a urllib2 request, handling HTTP authentication""" scheme, netloc, path, params, query, frag = urlparse.urlparse(url) @@ -871,7 +904,7 @@ def open_with_auth(url): raise httplib.InvalidURL("nonnumeric port: ''") if scheme in ('http', 'https'): - auth, host = urllib2.splituser(netloc) + auth, host = urllib.splituser(netloc) else: auth = None @@ -884,7 +917,7 @@ def open_with_auth(url): request = urllib2.Request(url) request.add_header('User-Agent', user_agent) - fp = urllib2.urlopen(request) + fp = opener(request) if auth: # Put authentication info back into request URL if same host, diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py new file mode 100644 index 00000000..9d2886db --- /dev/null +++ b/setuptools/py27compat.py @@ -0,0 +1,15 @@ +""" +Compatibility Support for Python 2.7 and earlier +""" + +import sys + +def get_all_headers(message, key): + """ + Given an HTTPMessage, return all headers matching a given key. + """ + return message.get_all(key) + +if sys.version_info < (3,): + def get_all_headers(message, key): + return message.getheaders(key) diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 1583b81f..f3095125 100755 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -9,9 +9,40 @@ except NameError: _file = None _open = open from distutils.errors import DistutilsError +from pkg_resources import working_set + __all__ = [ "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + def run_setup(setup_script, args): """Run a distutils setup script, sandboxed in its directory""" old_dir = os.getcwd() @@ -29,6 +60,9 @@ def run_setup(setup_script, args): try: sys.argv[:] = [setup_script]+list(args) sys.path.insert(0, setup_dir) + # reset to include setup dir, w/clean callback list + working_set.__init__() + working_set.callbacks.append(lambda dist:dist.activate()) DirectorySandbox(setup_dir).run( lambda: execfile( "setup.py", @@ -55,6 +89,8 @@ def run_setup(setup_script, args): sys.argv[:] = save_argv tempfile.tempdir = save_tmp + + class AbstractSandbox: """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" @@ -86,7 +122,6 @@ class AbstractSandbox: __builtin__.open = _open self._copy(_os) - def _mk_dual_path_wrapper(name): original = getattr(_os,name) def wrap(self,src,dst,*args,**kw): @@ -95,7 +130,6 @@ class AbstractSandbox: return original(src,dst,*args,**kw) return wrap - for name in ["rename", "link", "symlink"]: if hasattr(_os,name): locals()[name] = _mk_dual_path_wrapper(name) @@ -118,7 +152,6 @@ class AbstractSandbox: ]: if hasattr(_os,name): locals()[name] = _mk_single_path_wrapper(name) - def _mk_single_with_return(name): original = getattr(_os,name) def wrap(self,path,*args,**kw): @@ -240,16 +273,11 @@ class DirectorySandbox(AbstractSandbox): self._violation("os.open", file, flags, mode) return _os.open(file,flags,mode) - WRITE_FLAGS = reduce( - operator.or_, - [getattr(_os, a, 0) for a in + operator.or_, [getattr(_os, a, 0) for a in "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] ) - - - class SandboxViolation(DistutilsError): """A setup script attempted to modify the filesystem outside the sandbox""" diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py new file mode 100644 index 00000000..6dca5fab --- /dev/null +++ b/setuptools/ssl_support.py @@ -0,0 +1,247 @@ +import sys, os, socket, urllib2, atexit, re +import pkg_resources +from pkg_resources import ResolutionError, ExtractionError + +try: + import ssl +except ImportError: + ssl = None + +__all__ = [ + 'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths', + 'opener_for' +] + +cert_paths = """ +/etc/pki/tls/certs/ca-bundle.crt +/etc/ssl/certs/ca-certificates.crt +/usr/share/ssl/certs/ca-bundle.crt +/usr/local/share/certs/ca-root.crt +/etc/ssl/cert.pem +/System/Library/OpenSSL/certs/cert.pem +""".strip().split() + + +HTTPSHandler = HTTPSConnection = object + +for what, where in ( + ('HTTPSHandler', ['urllib2','urllib.request']), + ('HTTPSConnection', ['httplib', 'http.client']), +): + for module in where: + try: + exec("from %s import %s" % (module, what)) + except ImportError: + pass + +is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection) + + + + + +try: + from socket import create_connection +except ImportError: + _GLOBAL_DEFAULT_TIMEOUT = getattr(socket, '_GLOBAL_DEFAULT_TIMEOUT', object()) + def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, + source_address=None): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + host, port = address + err = None + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + if timeout is not _GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except error: + err = True + if sock is not None: + sock.close() + if err: + raise + else: + raise error("getaddrinfo returns an empty list") + + +try: + from ssl import CertificateError, match_hostname +except ImportError: + class CertificateError(ValueError): + pass + + def _dnsname_to_pat(dn): + pats = [] + for frag in dn.split(r'.'): + if frag == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + else: + # Otherwise, '*' matches any dotless fragment. + frag = re.escape(frag) + pats.append(frag.replace(r'\*', '[^.]*')) + return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + + def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules + are mostly followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_to_pat(value).match(hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_to_pat(value).match(hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") + + + + + + + + + + + + + + + + + + + + + + + + +class VerifyingHTTPSHandler(HTTPSHandler): + """Simple verifying handler: no auth, subclasses, timeouts, etc.""" + + def __init__(self, ca_bundle): + self.ca_bundle = ca_bundle + HTTPSHandler.__init__(self) + + def https_open(self, req): + return self.do_open( + lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req + ) + + +class VerifyingHTTPSConn(HTTPSConnection): + """Simple verifying connection: no auth, subclasses, timeouts, etc.""" + def __init__(self, host, ca_bundle, **kw): + HTTPSConnection.__init__(self, host, **kw) + self.ca_bundle = ca_bundle + + def connect(self): + sock = create_connection( + (self.host, self.port), getattr(self,'source_address',None) + ) + self.sock = ssl.wrap_socket( + sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle + ) + try: + match_hostname(self.sock.getpeercert(), self.host) + except CertificateError: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + +def opener_for(ca_bundle=None): + """Get a urlopen() replacement that uses ca_bundle for verification""" + return urllib2.build_opener( + VerifyingHTTPSHandler(ca_bundle or find_ca_bundle()) + ).open + + + +_wincerts = None + +def get_win_certfile(): + global _wincerts + if _wincerts is not None: + return _wincerts.name + + try: + from wincertstore import CertFile + except ImportError: + return None + + class MyCertFile(CertFile): + def __init__(self, stores=(), certs=()): + CertFile.__init__(self) + for store in stores: + self.addstore(store) + self.addcerts(certs) + atexit.register(self.close) + + _wincerts = MyCertFile(stores=['CA', 'ROOT']) + return _wincerts.name + + +def find_ca_bundle(): + """Return an existing CA bundle path, or None""" + if os.name=='nt': + return get_win_certfile() + else: + for cert_path in cert_paths: + if os.path.isfile(cert_path): + return cert_path + try: + return pkg_resources.resource_filename('certifi', 'cacert.pem') + except (ImportError, ResolutionError, ExtractionError): + return None + + + + + diff --git a/setuptools/tests/entries-v10 b/setuptools/tests/entries-v10 new file mode 100644 index 00000000..4446c501 --- /dev/null +++ b/setuptools/tests/entries-v10 @@ -0,0 +1,615 @@ +10 + +dir +89001 +http://svn.python.org/projects/sandbox/branches/setuptools-0.6 +http://svn.python.org/projects + + + +2013-06-03T17:26:03.052972Z +89000 +phillip.eby + + + + + + + + + + + + + + +6015fed2-1504-0410-9fe1-9d1591cc4771 + +api_tests.txt +file + + + + +2013-06-19T13:20:47.948712Z +dec366372ca14fbeaeb26f492bcf5725 +2013-05-15T22:04:59.389374Z +88997 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +12312 + +setuptools.egg-info +dir + +README.txt +file + + + + +2013-06-19T13:20:47.948712Z +26f0dd5d095522ba3ad999b6b6777b92 +2011-05-31T20:10:56.416725Z +88846 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +7615 + +easy_install.py +file + + + + +2013-06-19T13:20:47.948712Z +97b52fe7253bf4683f9f626f015eb72e +2006-09-20T20:48:18.716070Z +51935 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +126 + +setuptools +dir + +launcher.c +file + + + + +2013-06-19T13:20:47.924700Z +e5a8e77de9022688b80f77fc6d742fee +2009-10-19T21:03:29.785400Z +75544 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +7476 + +ez_setup.py +file + + + + +2013-06-19T13:20:47.924700Z +17e8ec5e08faccfcb08b5f8d5167ca14 +2011-01-20T18:50:00.815420Z +88124 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +8350 + +version +file + + + + +2013-06-19T13:20:47.924700Z +e456da09e0c9e224a56302f8316b6dbf +2007-01-09T19:21:05.921317Z +53317 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +1143 + +setup.py +file + + + + +2013-06-19T13:20:47.924700Z +d4e5b3c16bd61bfef6c0bb9377a3a3ea +2013-05-15T22:04:59.389374Z +88997 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +5228 + +release.sh +file + + + + +2013-06-19T13:20:47.932704Z +b1fd4054a1c107ff0f27baacd97be94c +2009-10-28T17:12:45.227140Z +75925 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +1044 + +pkg_resources.txt +file + + + + +2013-06-19T13:20:47.928702Z +f497e7c92a4de207cbd9ab1943f93388 +2009-10-12T20:00:02.336146Z +75385 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +94518 + +site.py +file + + + + +2013-06-19T13:20:47.932704Z +ebaac6fb6525f77ca950d22e6f8315df +2006-03-11T00:39:09.666740Z +42965 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +2362 + +version.dat +file + + + + +2013-06-19T13:20:47.932704Z +8e14ecea32b9874cd7d29277494554c0 +2009-10-28T17:12:45.227140Z +75925 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +80 + +virtual-python.py +file + + + + +2013-06-19T13:20:47.932704Z +aa857add3b5563238f0a904187f5ded9 +2005-10-17T02:26:39.000000Z +41262 +pje +has-props + + + + + + + + + + + + + + + + + + + + +3898 + +setup.cfg +file + + + + +2013-06-19T13:20:47.932704Z +eda883e744fce83f8107ad8dc8303536 +2006-09-21T22:26:48.050256Z +51965 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +296 + +setuptools.txt +file + + + + +2013-06-19T13:20:47.940708Z +11926256f06046b196eaf814772504e7 +2013-05-15T22:04:59.389374Z +88997 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +149832 + +pkg_resources.py +file + + + + +2013-06-19T13:20:47.940708Z +b63a30f5f0f0225a788c2c0e3430b3cf +2013-05-15T22:04:59.389374Z +88997 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +90397 + +tests +dir + +wikiup.cfg +file + + + + +2013-06-19T13:20:47.944710Z +34ad845a5e0a0b46458557fa910bf429 +2008-08-21T17:23:50.797633Z +65935 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +136 + +EasyInstall.txt +file + + + + +2013-06-19T13:20:47.944710Z +e97387c517f70fc18a377e42d19d64d4 +2013-05-15T22:04:59.389374Z +88997 +phillip.eby +has-props + + + + + + + + + + + + + + + + + + + + +82495 + diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 582219ce..d17a5340 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -13,7 +13,7 @@ import StringIO import distutils.core from setuptools.sandbox import run_setup, SandboxViolation -from setuptools.command.easy_install import easy_install, fix_jython_executable, get_script_args, main +from setuptools.command.easy_install import easy_install, fix_jython_executable, get_script_args from setuptools.command.easy_install import PthDistributions from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution @@ -84,41 +84,6 @@ class TestEasyInstallTest(unittest.TestCase): self.assertEqual(script, WANTED) - def test_no_setup_cfg(self): - # makes sure easy_install as a command (main) - # doesn't use a setup.cfg file that is located - # in the current working directory - dir = tempfile.mkdtemp() - setup_cfg = open(os.path.join(dir, 'setup.cfg'), 'w') - setup_cfg.write('[easy_install]\nfind_links = http://example.com') - setup_cfg.close() - setup_py = open(os.path.join(dir, 'setup.py'), 'w') - setup_py.write(SETUP_PY) - setup_py.close() - - from setuptools.dist import Distribution - - def _parse_command_line(self): - msg = 'Error: a local setup.cfg was used' - opts = self.command_options - if 'easy_install' in opts: - assert 'find_links' not in opts['easy_install'], msg - return self._old_parse_command_line() - - Distribution._old_parse_command_line = Distribution.parse_command_line - Distribution.parse_command_line = _parse_command_line - - old_wd = os.getcwd() - try: - os.chdir(dir) - reset_setup_stop_context( - lambda: self.assertRaises(SystemExit, main, []) - ) - finally: - os.chdir(old_wd) - shutil.rmtree(dir) - Distribution.parse_command_line = Distribution._old_parse_command_line - def test_no_find_links(self): # new option '--no-find-links', that blocks find-links added at # the project level @@ -266,10 +231,10 @@ class TestUserInstallTest(unittest.TestCase): del os.environ['PYTHONPATH'] def test_setup_requires(self): - """Regression test for issue #318 + """Regression test for Distribute issue #318 - Ensures that a package with setup_requires can be installed when - distribute is installed in the user site-packages without causing a + Ensure that a package with setup_requires can be installed when + setuptools is installed in the user site-packages without causing a SandboxViolation. """ @@ -307,11 +272,12 @@ class TestUserInstallTest(unittest.TestCase): sys.stdout = StringIO.StringIO() sys.stderr = StringIO.StringIO() try: - reset_setup_stop_context( - lambda: run_setup(test_setup_py, ['install']) - ) - except SandboxViolation: - self.fail('Installation caused SandboxViolation') + try: + reset_setup_stop_context( + lambda: run_setup(test_setup_py, ['install']) + ) + except SandboxViolation: + self.fail('Installation caused SandboxViolation') finally: sys.stdout = old_stdout sys.stderr = old_stderr @@ -373,13 +339,13 @@ class TestSetupRequires(unittest.TestCase): doesn't exist) and invoke installer on it. """ def build_sdist(dir): - dist_path = os.path.join(dir, 'distribute-test-fetcher-1.0.tar.gz') + dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz') make_trivial_sdist( dist_path, textwrap.dedent(""" import setuptools setuptools.setup( - name="distribute-test-fetcher", + name="setuptools-test-fetcher", version="1.0", setup_requires = ['does-not-exist'], ) @@ -447,7 +413,7 @@ def argv_context(f, repl): def reset_setup_stop_context(f): """ - When the distribute tests are run using setup.py test, and then + When the setuptools tests are run using setup.py test, and then one wants to invoke another setup() command (such as easy_install) within those tests, it's necessary to reset the global variable in distutils.core so that the setup() command will run naturally. diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py new file mode 100644 index 00000000..f26a1f51 --- /dev/null +++ b/setuptools/tests/test_egg_info.py @@ -0,0 +1,40 @@ +import os +import tempfile +import shutil +import unittest + +import pkg_resources +from setuptools.command import egg_info + +ENTRIES_V10 = pkg_resources.resource_string(__name__, 'entries-v10') +"An entries file generated with svn 1.6.17 against the legacy Setuptools repo" + +class TestEggInfo(unittest.TestCase): + + def setUp(self): + self.test_dir = tempfile.mkdtemp() + os.mkdir(os.path.join(self.test_dir, '.svn')) + + self.old_cwd = os.getcwd() + os.chdir(self.test_dir) + + def tearDown(self): + os.chdir(self.old_cwd) + shutil.rmtree(self.test_dir) + + def _write_entries(self, entries): + fn = os.path.join(self.test_dir, '.svn', 'entries') + entries_f = open(fn, 'wb') + entries_f.write(entries) + entries_f.close() + + def test_version_10_format(self): + """ + """ + self._write_entries(ENTRIES_V10) + rev = egg_info.egg_info.get_svn_revision() + self.assertEqual(rev, '89000') + + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 3e446b54..1060e787 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -46,18 +46,14 @@ class TestPackageIndex(unittest.TestCase): import httplib raise httplib.BadStatusLine('line') - old_urlopen = urllib2.urlopen - urllib2.urlopen = _urlopen + index.opener = _urlopen url = 'http://example.com' try: - try: - v = index.open_url(url) - except Exception, v: - self.assertTrue('line' in str(v)) - else: - raise AssertionError('Should have raise here!') - finally: - urllib2.urlopen = old_urlopen + v = index.open_url(url) + except Exception, v: + self.assertTrue('line' in str(v)) + else: + raise AssertionError('Should have raise here!') def test_bad_url_double_scheme(self): """ @@ -101,7 +97,7 @@ class TestPackageIndex(unittest.TestCase): """ Download links from the pypi simple index should be used before external download links. - http://bitbucket.org/tarek/distribute/issue/163/md5-validation-error + https://bitbucket.org/tarek/distribute/issue/163 Usecase : - someone uploads a package on pypi, a md5 is generated @@ -110,7 +106,7 @@ class TestPackageIndex(unittest.TestCase): - someone reuploads the package (with a different md5) - while easy_installing, an MD5 error occurs because the external link is used - -> Distribute should use the link from pypi, not the external one. + -> Setuptools should use the link from pypi, not the external one. """ if sys.platform.startswith('java'): # Skip this test on jython because binding to :0 fails diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py index 292b78d1..34e341b5 100644 --- a/setuptools/tests/test_resources.py +++ b/setuptools/tests/test_resources.py @@ -197,29 +197,6 @@ class DistroTests(TestCase): ) self.assertRaises(UnknownExtra, d.requires, ["foo"]) - def testSetuptoolsDistributeCombination(self): - # Ensure that installing a 0.7-series setuptools fails. PJE says that - # it will not co-exist. - ws = WorkingSet([]) - d = Distribution( - "/some/path", - project_name="setuptools", - version="0.7a1") - self.assertRaises(ValueError, ws.add, d) - # A 0.6-series is no problem - d2 = Distribution( - "/some/path", - project_name="setuptools", - version="0.6c9") - ws.add(d2) - - # a unexisting version needs to work - ws = WorkingSet([]) - d3 = Distribution( - "/some/path", - project_name="setuptools") - ws.add(d3) - class EntryPointTests(TestCase): @@ -372,21 +349,13 @@ class RequirementsTests(TestCase): self.assertTrue(d("foo-0.3a3.egg") in r2) self.assertTrue(d("foo-0.3a5.egg") in r2) - def testDistributeSetuptoolsOverride(self): - # Plain setuptools or distribute mean we return distribute. - self.assertEqual( - Requirement.parse('setuptools').project_name, 'distribute') - self.assertEqual( - Requirement.parse('distribute').project_name, 'distribute') - # setuptools lower than 0.7 means distribute - self.assertEqual( - Requirement.parse('setuptools==0.6c9').project_name, 'distribute') - self.assertEqual( - Requirement.parse('setuptools==0.6c10').project_name, 'distribute') - self.assertEqual( - Requirement.parse('setuptools>=0.6').project_name, 'distribute') + def testSetuptoolsProjectName(self): + """ + The setuptools project should implement the setuptools package. + """ + self.assertEqual( - Requirement.parse('setuptools < 0.7').project_name, 'distribute') + Requirement.parse('setuptools').project_name, 'setuptools') # setuptools 0.7 and higher means setuptools. self.assertEqual( Requirement.parse('setuptools == 0.7').project_name, 'setuptools') @@ -558,7 +527,7 @@ class ScriptHeaderTests(TestCase): platform = sys.platform sys.platform = 'java1.5.0_13' - stdout = sys.stdout + stdout, stderr = sys.stdout, sys.stderr try: # A mock sys.executable that uses a shebang line (this file) exe = os.path.normpath(os.path.splitext(__file__)[0] + '.py') @@ -581,7 +550,7 @@ class ScriptHeaderTests(TestCase): finally: del sys.modules["java"] sys.platform = platform - sys.stdout = stdout + sys.stdout, sys.stderr = stdout, stderr @@ -590,7 +559,7 @@ class NamespaceTests(TestCase): def setUp(self): self._ns_pkgs = pkg_resources._namespace_packages.copy() - self._tmpdir = tempfile.mkdtemp(prefix="tests-distribute-") + self._tmpdir = tempfile.mkdtemp(prefix="tests-setuptools-") os.makedirs(os.path.join(self._tmpdir, "site-pkgs")) self._prev_sys_path = sys.path[:] sys.path.append(os.path.join(self._tmpdir, "site-pkgs")) @@ -634,7 +603,7 @@ class NamespaceTests(TestCase): try: import pkg1.pkg2 except ImportError, e: - self.fail("Distribute tried to import the parent namespace package") + self.fail("Setuptools tried to import the parent namespace package") # check the _namespace_packages dict self._assertIn("pkg1.pkg2", pkg_resources._namespace_packages.keys()) self.assertEqual(pkg_resources._namespace_packages["pkg1"], ["pkg1.pkg2"]) diff --git a/tests/api_tests.txt b/tests/api_tests.txt index 6cf6e66f..d03da14e 100644 --- a/tests/api_tests.txt +++ b/tests/api_tests.txt @@ -119,7 +119,7 @@ editing are also a Distribution. (And, with a little attention to the directory names used, and including some additional metadata, such a "development distribution" can be made pluggable as well.) - >>> from pkg_resources import WorkingSet, VersionConflict + >>> from pkg_resources import WorkingSet A working set's entries are the sys.path entries that correspond to the active distributions. By default, the working set's entries are the items on @@ -170,8 +170,8 @@ You can append a path entry to a working set using ``add_entry()``:: >>> ws.entries ['http://example.com/something'] >>> ws.add_entry(pkg_resources.__file__) - >>> ws.entries == ['http://example.com/something', pkg_resources.__file__] - True + >>> ws.entries + ['http://example.com/something', '...pkg_resources.py...'] Multiple additions result in multiple entries, even if the entry is already in the working set (because ``sys.path`` can contain the same entry more than @@ -210,9 +210,12 @@ working set triggers a ``pkg_resources.VersionConflict`` error: >>> try: ... ws.find(Requirement.parse("Bar==1.0")) - ... except VersionConflict: - ... print 'ok' - ok + ... except pkg_resources.VersionConflict: + ... exc = sys.exc_info()[1] + ... print(str(exc)) + ... else: + ... raise AssertionError("VersionConflict was not raised") + (Bar 0.9 (http://example.com/something), Requirement.parse('Bar==1.0')) You can subscribe a callback function to receive notifications whenever a new distribution is added to a working set. The callback is immediately invoked @@ -222,14 +225,14 @@ again for new distributions added thereafter:: >>> def added(dist): print "Added", dist >>> ws.subscribe(added) Added Bar 0.9 - >>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12") + >>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12") >>> ws.add(foo12) Added Foo 1.2 Note, however, that only the first distribution added for a given project name will trigger a callback, even during the initial ``subscribe()`` callback:: - >>> foo14 = Distribution(project_name="Foo", version="1.4", location="f14") + >>> foo14 = Distribution(project_name="Foo", version="1.4", location="f14") >>> ws.add(foo14) # no callback, because Foo 1.2 is already active >>> ws = WorkingSet([]) @@ -237,7 +240,7 @@ will trigger a callback, even during the initial ``subscribe()`` callback:: >>> ws.add(foo14) >>> ws.subscribe(added) Added Foo 1.2 - + And adding a callback more than once has no effect, either:: >>> ws.subscribe(added) # no callbacks @@ -260,7 +263,7 @@ Finding Plugins >>> plugins.add(foo12) >>> plugins.add(foo14) >>> plugins.add(just_a_test) - + In the simplest case, we just get the newest version of each distribution in the plugin environment:: @@ -318,7 +321,7 @@ number does not matter:: >>> cp("macosx-9.5-ppc", reqd) False -Backwards compatibility for packages made via earlier versions of +Backwards compatibility for packages made via earlier versions of setuptools is provided as well:: >>> cp("darwin-8.2.0-Power_Macintosh", reqd) @@ -328,3 +331,94 @@ setuptools is provided as well:: >>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc") False + +Environment Markers +------------------- + + >>> from pkg_resources import invalid_marker as im, evaluate_marker as em + >>> import os + + >>> print(im("sys_platform")) + Comparison or logical expression expected + + >>> print(im("sys_platform==")) # doctest: +ELLIPSIS + unexpected EOF while parsing (...line 1) + + >>> print(im("sys_platform=='win32'")) + False + + >>> print(im("sys=='x'")) + Unknown name 'sys' + + >>> print(im("(extra)")) + Comparison or logical expression expected + + >>> print(im("(extra")) # doctest: +ELLIPSIS + unexpected EOF while parsing (...line 1) + + >>> print(im("os.open('foo')=='y'")) + Language feature not supported in environment markers + + >>> print(im("'x'=='y' and os.open('foo')=='y'")) # no short-circuit! + Language feature not supported in environment markers + + >>> print(im("'x'=='x' or os.open('foo')=='y'")) # no short-circuit! + Language feature not supported in environment markers + + >>> print(im("'x' < 'y'")) + '<' operator not allowed in environment markers + + >>> print(im("'x' < 'y' < 'z'")) + Chained comparison not allowed in environment markers + + >>> print(im("r'x'=='x'")) + Only plain strings allowed in environment markers + + >>> print(im("'''x'''=='x'")) + Only plain strings allowed in environment markers + + >>> print(im('"""x"""=="x"')) + Only plain strings allowed in environment markers + + >>> print(im(r"'x\n'=='x'")) + Only plain strings allowed in environment markers + + >>> print(im("os.open=='y'")) + Language feature not supported in environment markers + + >>> em('"x"=="x"') + True + + >>> em('"x"=="y"') + False + + >>> em('"x"=="y" and "x"=="x"') + False + + >>> em('"x"=="y" or "x"=="x"') + True + + >>> em('"x"=="y" and "x"=="q" or "z"=="z"') + True + + >>> em('"x"=="y" and ("x"=="q" or "z"=="z")') + False + + >>> em('"x"=="y" and "z"=="z" or "x"=="q"') + False + + >>> em('"x"=="x" and "z"=="z" or "x"=="q"') + True + + >>> em("sys_platform=='win32'") == (sys.platform=='win32') + True + + >>> em("'x' in 'yx'") + True + + >>> em("'yx' in 'x'") + False + + + + diff --git a/tests/install_test.py b/tests/install_test.py deleted file mode 100644 index 02deb818..00000000 --- a/tests/install_test.py +++ /dev/null @@ -1,75 +0,0 @@ -import urllib2 -import sys -import os - -if os.path.exists('distribute_setup.py'): - print 'distribute_setup.py exists in the current dir, aborting' - sys.exit(2) - -print '**** Starting Test' -print '\n\n' - -is_jython = sys.platform.startswith('java') -if is_jython: - import subprocess - -print 'Downloading bootstrap' -file = urllib2.urlopen('http://nightly.ziade.org/distribute_setup.py') -f = open('distribute_setup.py', 'w') -f.write(file.read()) -f.close() - -# running it -args = [sys.executable] + ['distribute_setup.py'] -if is_jython: - res = subprocess.call(args) -else: - res = os.spawnv(os.P_WAIT, sys.executable, args) - -if res != 0: - print '**** Test failed, please send me the output at tarek@ziade.org' - os.remove('distribute_setup.py') - sys.exit(2) - -# now checking if Distribute is installed -script = """\ -import sys -try: - import setuptools -except ImportError: - sys.exit(0) - -sys.exit(hasattr(setuptools, "_distribute")) -""" - -root = 'script' -seed = 0 -script_name = '%s%d.py' % (root, seed) - -while os.path.exists(script_name): - seed += 1 - script_name = '%s%d.py' % (root, seed) - -f = open(script_name, 'w') -try: - f.write(script) -finally: - f.close() - -try: - args = [sys.executable] + [script_name] - if is_jython: - res = subprocess.call(args) - else: - res = os.spawnv(os.P_WAIT, sys.executable, args) - - print '\n\n' - if res: - print '**** Test is OK' - else: - print '**** Test failed, please send me the output at tarek@ziade.org' -finally: - if os.path.exists(script_name): - os.remove(script_name) - os.remove('distribute_setup.py') - diff --git a/tests/manual_test.py b/tests/manual_test.py index 0d5051f1..44cf2d06 100644 --- a/tests/manual_test.py +++ b/tests/manual_test.py @@ -51,9 +51,8 @@ eggs = extensions """ -BOOTSTRAP = 'http://python-distribute.org/bootstrap.py' +BOOTSTRAP = 'http://downloads.buildout.org/1/bootstrap.py' PYVER = sys.version.split()[0][:3] -DEV_URL = 'http://bitbucket.org/tarek/distribute/get/0.6-maintenance.zip#egg=distribute-dev' _VARS = {'base': '.', 'py_version_short': PYVER} @@ -66,26 +65,25 @@ else: @tempdir def test_virtualenv(): - """virtualenv with distribute""" + """virtualenv with setuptools""" purelib = os.path.abspath(Template(PURELIB).substitute(**_VARS)) - _system_call('virtualenv', '--no-site-packages', '.', '--distribute') - _system_call('bin/easy_install', 'distribute==dev') + _system_call('virtualenv', '--no-site-packages', '.') + _system_call('bin/easy_install', 'setuptools==dev') # linux specific site_pkg = os.listdir(purelib) site_pkg.sort() - assert 'distribute' in site_pkg[0] + assert 'setuptools' in site_pkg[0] easy_install = os.path.join(purelib, 'easy-install.pth') with open(easy_install) as f: res = f.read() - assert 'distribute' in res - assert 'setuptools' not in res + assert 'setuptools' in res @tempdir def test_full(): """virtualenv + pip + buildout""" _system_call('virtualenv', '--no-site-packages', '.') - _system_call('bin/easy_install', '-q', 'distribute==dev') - _system_call('bin/easy_install', '-qU', 'distribute==dev') + _system_call('bin/easy_install', '-q', 'setuptools==dev') + _system_call('bin/easy_install', '-qU', 'setuptools==dev') _system_call('bin/easy_install', '-q', 'pip') _system_call('bin/pip', 'install', '-q', 'zc.buildout') @@ -95,16 +93,16 @@ def test_full(): with open('bootstrap.py', 'w') as f: f.write(urlopen(BOOTSTRAP).read()) - _system_call('bin/python', 'bootstrap.py', '--distribute') + _system_call('bin/python', 'bootstrap.py') _system_call('bin/buildout', '-q') eggs = os.listdir('eggs') eggs.sort() assert len(eggs) == 3 - assert eggs[0].startswith('distribute') - assert eggs[1:] == ['extensions-0.3-py2.6.egg', - 'zc.recipe.egg-1.2.2-py2.6.egg'] + assert eggs[1].startswith('setuptools') + del eggs[1] + assert eggs == ['extensions-0.3-py2.6.egg', + 'zc.recipe.egg-1.2.2-py2.6.egg'] if __name__ == '__main__': test_virtualenv() test_full() - diff --git a/tests/test_distribute_setup.py b/tests/test_ez_setup.py index 1f3da058..922bd884 100644 --- a/tests/test_distribute_setup.py +++ b/tests/test_ez_setup.py @@ -9,10 +9,9 @@ CURDIR = os.path.abspath(os.path.dirname(__file__)) TOPDIR = os.path.split(CURDIR)[0] sys.path.insert(0, TOPDIR) -from distribute_setup import (use_setuptools, _build_egg, _python_cmd, - _do_download, _install, DEFAULT_URL, - DEFAULT_VERSION) -import distribute_setup +from ez_setup import (use_setuptools, _build_egg, _python_cmd, _do_download, + _install, DEFAULT_URL, DEFAULT_VERSION) +import ez_setup class TestSetup(unittest.TestCase): @@ -54,20 +53,11 @@ class TestSetup(unittest.TestCase): def test_install(self): def _faked(*args): return True - distribute_setup.python_cmd = _faked + ez_setup.python_cmd = _faked _install(self.tarball) def test_use_setuptools(self): self.assertEqual(use_setuptools(), None) - # make sure fake_setuptools is not called by default - import pkg_resources - del pkg_resources._distribute - def fake_setuptools(*args): - raise AssertionError - - pkg_resources._fake_setuptools = fake_setuptools - use_setuptools() - if __name__ == '__main__': unittest.main() diff --git a/tests/test_pkg_resources.py b/tests/test_pkg_resources.py index 7009b4ab..f3256173 100644 --- a/tests/test_pkg_resources.py +++ b/tests/test_pkg_resources.py @@ -5,6 +5,11 @@ import zipfile import pkg_resources +try: + unicode +except NameError: + unicode = str + class EggRemover(unicode): def __call__(self): if self in sys.path: @@ -59,3 +64,11 @@ class TestZipProvider(object): f = open(filename) assert f.read() == 'hello, world!' manager.cleanup_resources() + +class TestResourceManager(object): + def test_get_cache_path(self): + mgr = pkg_resources.ResourceManager() + path = mgr.get_cache_path('foo') + type_ = str(type(path)) + message = "Unexpected type from get_cache_path: " + type_ + assert isinstance(path, (unicode, str)), message |