diff options
89 files changed, 6260 insertions, 1435 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..21ec620a --- /dev/null +++ b/.hgignore @@ -0,0 +1,13 @@ +syntax: glob +*.pyc +*~ +*.swp +.coverage +distribute.egg-info +build +dist +lib +bin +include +\.Python +*.swp diff --git a/.hgtags b/.hgtags new file mode 100644 index 00000000..5f3ee48d --- /dev/null +++ b/.hgtags @@ -0,0 +1,46 @@ +1010d08fd8dfd2f1496843b557b5369a0beba82a 0.6 +4d114c5f2a3ecb4a0fa552075dbbb221b19e291b 0.6.1 +41415244ee90664042d277d0b1f0f59c04ddd0e4 0.6.2 +e033bf2d3d05f4a7130f5f8f5de152c4db9ff32e 0.6.3 +e06c416e911c61771708f5afbf3f35db0e12ba71 0.6.4 +2df182df8a0224d429402de3cddccdb97af6ea21 0.6.5 +f1fb564d6d67a6340ff33df2f5a74b89753f159d 0.6.6 +71f08668d050589b92ecd164a4f5a91f3484313b 0.6.7 +445547a5729ed5517cf1a9baad595420a8831ef8 0.6.8 +669ed9388b17ec461380cc41760a9a7384fb5284 0.6.9 +669ed9388b17ec461380cc41760a9a7384fb5284 0.6.9 +ac7d9b14ac43fecb8b65de548b25773553facaee 0.6.9 +0fd5c506037880409308f2b79c6e901d21e7fe92 0.6.10 +0fd5c506037880409308f2b79c6e901d21e7fe92 0.6.10 +f18396c6e1875476279d8bbffd8e6dadcc695136 0.6.10 +e00987890c0b386f09d0f6b73d8558b72f6367f1 0.6.11 +48a97bc89e2f65fc9b78b358d7dc89ba9ec9524a 0.6.12 +dae247400d0ca1fdfaf38db275622c9bec550b08 0.6.13 +2b9d9977ea75b8eb3766bab808ef31f192d2b1bc 0.6.14 +51a9d1a1f31a4be3107d06cf088aff8e182dc633 0.6.15 +3f1ff138e947bfc1c9bcfe0037030b7bfb4ab3a5 0.6.16 +9c40f23d0bda3f3f169686e27a422f853fa4d0fa 0.6.17 +9c40f23d0bda3f3f169686e27a422f853fa4d0fa 0.6.17 +4bbc01e4709ea7425cf0c186bbaf1d928cfa2a65 0.6.17 +4bbc01e4709ea7425cf0c186bbaf1d928cfa2a65 0.6.17 +0502d5117d8304ab21084912758ed28812a5a8f1 0.6.17 +74108d7f07343556a8db94e8122221a43243f586 0.6.18 +611910892a0421633d72677979f94a25ef590d54 0.6.19 +a7cf5ae137f1646adf86ce5d6b5d8b7bd6eab69f 0.6.20 +c4a375336d552129aef174486018ed09c212d684 0.6.20 +de44acab3cfce1f5bc811d6c0fa1a88ca0e9533f 0.6.21 +1a1ab844f03e10528ae693ad3cb45064e08f49e5 0.6.23 +1a1ab844f03e10528ae693ad3cb45064e08f49e5 0.6.23 +9406c5dac8429216f1a264e6f692fdc534476acd 0.6.23 +7fd7b6e30a0effa082baed1c4103a0efa56be98c 0.6.24 +6124053afb5c98f11e146ae62049b4c232d50dc5 0.6.25 +b69f072c000237435e17b8bbb304ba6f957283eb 0.6.26 +469c3b948e41ef28752b3cdf3c7fb9618355ebf5 0.6.27 +fc379e63586ad3c6838e1bda216548ba8270b8f0 0.6.28 +4f82563d0f5d1af1fb215c0ac87f38b16bb5c42d 0.6.29 +7464fc916fa4d8308e34e45a1198512fe04c97b4 0.6.30 +17bc972d67edd96c7748061910172e1200a73efe 0.6.31 +b1a7f86b315a1f8c20036d718d6dc641bb84cac6 0.6.32 +6acac3919ae9a7dba2cbecbe3d4b31ece25d5f09 0.6.33 +23c310bf4ae8e4616e37027f08891702f5a33bc9 0.6.34 +2abe1117543be0edbafb10c7c159d1bcb1cb1b87 0.6.35 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..cbc671e7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: python +python: + - 2.5 + - 2.6 + - 2.7 + - 3.2 +# command to run tests +script: python setup.py test diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 00000000..cae946e0 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,498 @@ +======= +CHANGES +======= + +------ +0.6.35 +------ + +Note this release is backward-incompatible with distribute 0.6.23-0.6.34 in +how it parses version numbers. + +* Issue #278: Restored compatibility with distribute 0.6.22 and setuptools + 0.6. Updated the documentation to match more closely with the version + parsing as intended in setuptools 0.6. + +------ +0.6.34 +------ + +* Issue #341: 0.6.33 fails to build under Python 2.4. + +------ +0.6.33 +------ + +* Fix 2 errors with Jython 2.5. +* Fix 1 failure with Jython 2.5 and 2.7. +* Disable workaround for Jython scripts on Linux systems. +* Issue #336: `setup.py` no longer masks failure exit code when tests fail. +* Fix issue in pkg_resources where try/except around a platform-dependent + import would trigger hook load failures on Mercurial. See pull request 32 + for details. +* Issue #341: Fix a ResourceWarning. + +------ +0.6.32 +------ + +* Fix test suite with Python 2.6. +* Fix some DeprecationWarnings and ResourceWarnings. +* Issue #335: Backed out `setup_requires` superceding installed requirements + until regression can be addressed. + +------ +0.6.31 +------ + +* Issue #303: Make sure the manifest only ever contains UTF-8 in Python 3. +* Issue #329: Properly close files created by tests for compatibility with + Jython. +* Work around Jython bugs `#1980 <http://bugs.jython.org/issue1980>`_ and + `#1981 <http://bugs.jython.org/issue1981>`_. +* Issue #334: Provide workaround for packages that reference `sys.__stdout__` + such as numpy does. This change should address + `virtualenv #359 <https://github.com/pypa/virtualenv/issues/359>`_ as long + as the system encoding is UTF-8 or the IO encoding is specified in the + environment, i.e.:: + + PYTHONIOENCODING=utf8 pip install numpy + +* Fix for encoding issue when installing from Windows executable on Python 3. +* Issue #323: Allow `setup_requires` requirements to supercede installed + requirements. Added some new keyword arguments to existing pkg_resources + methods. Also had to updated how __path__ is handled for namespace packages + to ensure that when a new egg distribution containing a namespace package is + placed on sys.path, the entries in __path__ are found in the same order they + would have been in had that egg been on the path when pkg_resources was + first imported. + +------ +0.6.30 +------ + +* Issue #328: Clean up temporary directories in distribute_setup.py. +* Fix fatal bug in distribute_setup.py. + +------ +0.6.29 +------ + +* Pull Request #14: Honor file permissions in zip files. +* Issue #327: Merged pull request #24 to fix a dependency problem with pip. +* Merged pull request #23 to fix https://github.com/pypa/virtualenv/issues/301. +* If Sphinx is installed, the `upload_docs` command now runs `build_sphinx` + to produce uploadable documentation. +* Issue #326: `upload_docs` provided mangled auth credentials under Python 3. +* Issue #320: Fix check for "createable" in distribute_setup.py. +* Issue #305: Remove a warning that was triggered during normal operations. +* Issue #311: Print metadata in UTF-8 independent of platform. +* Issue #303: Read manifest file with UTF-8 encoding under Python 3. +* Issue #301: Allow to run tests of namespace packages when using 2to3. +* Issue #304: Prevent import loop in site.py under Python 3.3. +* Issue #283: Reenable scanning of `*.pyc` / `*.pyo` files on Python 3.3. +* Issue #299: The develop command didn't work on Python 3, when using 2to3, + as the egg link would go to the Python 2 source. Linking to the 2to3'd code + in build/lib makes it work, although you will have to rebuild the module + before testing it. +* Issue #306: Even if 2to3 is used, we build in-place under Python 2. +* Issue #307: Prints the full path when .svn/entries is broken. +* Issue #313: Support for sdist subcommands (Python 2.7) +* Issue #314: test_local_index() would fail an OS X. +* Issue #310: Non-ascii characters in a namespace __init__.py causes errors. +* Issue #218: Improved documentation on behavior of `package_data` and + `include_package_data`. Files indicated by `package_data` are now included + in the manifest. +* `distribute_setup.py` now allows a `--download-base` argument for retrieving + distribute from a specified location. + +------ +0.6.28 +------ + +* Issue #294: setup.py can now be invoked from any directory. +* Scripts are now installed honoring the umask. +* Added support for .dist-info directories. +* Issue #283: Fix and disable scanning of `*.pyc` / `*.pyo` files on + Python 3.3. + +------ +0.6.27 +------ + +* Support current snapshots of CPython 3.3. +* Distribute now recognizes README.rst as a standard, default readme file. +* Exclude 'encodings' modules when removing modules from sys.modules. + Workaround for #285. +* Issue #231: Don't fiddle with system python when used with buildout + (bootstrap.py) + +------ +0.6.26 +------ + +* Issue #183: Symlinked files are now extracted from source distributions. +* Issue #227: Easy_install fetch parameters are now passed during the + installation of a source distribution; now fulfillment of setup_requires + dependencies will honor the parameters passed to easy_install. + +------ +0.6.25 +------ + +* Issue #258: Workaround a cache issue +* Issue #260: distribute_setup.py now accepts the --user parameter for + Python 2.6 and later. +* Issue #262: package_index.open_with_auth no longer throws LookupError + on Python 3. +* Issue #269: AttributeError when an exception occurs reading Manifest.in + on late releases of Python. +* Issue #272: Prevent TypeError when namespace package names are unicode + and single-install-externally-managed is used. Also fixes PIP issue + 449. +* Issue #273: Legacy script launchers now install with Python2/3 support. + +------ +0.6.24 +------ + +* Issue #249: Added options to exclude 2to3 fixers + +------ +0.6.23 +------ + +* Issue #244: Fixed a test +* Issue #243: Fixed a test +* Issue #239: Fixed a test +* Issue #240: Fixed a test +* Issue #241: Fixed a test +* Issue #237: Fixed a test +* Issue #238: easy_install now uses 64bit executable wrappers on 64bit Python +* Issue #208: Fixed parsed_versions, it now honors post-releases as noted in the documentation +* Issue #207: Windows cli and gui wrappers pass CTRL-C to child python process +* Issue #227: easy_install now passes its arguments to setup.py bdist_egg +* Issue #225: Fixed a NameError on Python 2.5, 2.4 + +------ +0.6.21 +------ + +* Issue #225: FIxed a regression on py2.4 + +------ +0.6.20 +------ + +* Issue #135: Include url in warning when processing URLs in package_index. +* Issue #212: Fix issue where easy_instal fails on Python 3 on windows installer. +* Issue #213: Fix typo in documentation. + +------ +0.6.19 +------ + +* Issue 206: AttributeError: 'HTTPMessage' object has no attribute 'getheaders' + +------ +0.6.18 +------ + +* Issue 210: Fixed a regression introduced by Issue 204 fix. + +------ +0.6.17 +------ + +* Support 'DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT' environment + variable to allow to disable installation of easy_install-${version} script. +* Support Python >=3.1.4 and >=3.2.1. +* Issue 204: Don't try to import the parent of a namespace package in + declare_namespace +* Issue 196: Tolerate responses with multiple Content-Length headers +* Issue 205: Sandboxing doesn't preserve working_set. Leads to setup_requires + problems. + +------ +0.6.16 +------ + +* Builds sdist gztar even on Windows (avoiding Issue 193). +* Issue 192: Fixed metadata omitted on Windows when package_dir + specified with forward-slash. +* Issue 195: Cython build support. +* Issue 200: Issues with recognizing 64-bit packages on Windows. + +------ +0.6.15 +------ + +* Fixed typo in bdist_egg +* Several issues under Python 3 has been solved. +* Issue 146: Fixed missing DLL files after easy_install of windows exe package. + +------ +0.6.14 +------ + +* Issue 170: Fixed unittest failure. Thanks to Toshio. +* Issue 171: Fixed race condition in unittests cause deadlocks in test suite. +* Issue 143: Fixed a lookup issue with easy_install. + Thanks to David and Zooko. +* Issue 174: Fixed the edit mode when its used with setuptools itself + +------ +0.6.13 +------ + +* Issue 160: 2.7 gives ValueError("Invalid IPv6 URL") +* Issue 150: Fixed using ~/.local even in a --no-site-packages virtualenv +* Issue 163: scan index links before external links, and don't use the md5 when + comparing two distributions + +------ +0.6.12 +------ + +* Issue 149: Fixed various failures on 2.3/2.4 + +------ +0.6.11 +------ + +* Found another case of SandboxViolation - fixed +* Issue 15 and 48: Introduced a socket timeout of 15 seconds on url openings +* Added indexsidebar.html into MANIFEST.in +* Issue 108: Fixed TypeError with Python3.1 +* Issue 121: Fixed --help install command trying to actually install. +* Issue 112: Added an os.makedirs so that Tarek's solution will work. +* Issue 133: Added --no-find-links to easy_install +* Added easy_install --user +* Issue 100: Fixed develop --user not taking '.' in PYTHONPATH into account +* Issue 134: removed spurious UserWarnings. Patch by VanLindberg +* Issue 138: cant_write_to_target error when setup_requires is used. +* Issue 147: respect the sys.dont_write_bytecode flag + +------ +0.6.10 +------ + +* Reverted change made for the DistributionNotFound exception because + zc.buildout uses the exception message to get the name of the + distribution. + +----- +0.6.9 +----- + +* Issue 90: unknown setuptools version can be added in the working set +* Issue 87: setupt.py doesn't try to convert distribute_setup.py anymore + Initial Patch by arfrever. +* Issue 89: added a side bar with a download link to the doc. +* Issue 86: fixed missing sentence in pkg_resources doc. +* Added a nicer error message when a DistributionNotFound is raised. +* Issue 80: test_develop now works with Python 3.1 +* Issue 93: upload_docs now works if there is an empty sub-directory. +* Issue 70: exec bit on non-exec files +* Issue 99: now the standalone easy_install command doesn't uses a + "setup.cfg" if any exists in the working directory. It will use it + only if triggered by ``install_requires`` from a setup.py call + (install, develop, etc). +* Issue 101: Allowing ``os.devnull`` in Sandbox +* Issue 92: Fixed the "no eggs" found error with MacPort + (platform.mac_ver() fails) +* Issue 103: test_get_script_header_jython_workaround not run + anymore under py3 with C or POSIX local. Contributed by Arfrever. +* Issue 104: remvoved the assertion when the installation fails, + with a nicer message for the end user. +* Issue 100: making sure there's no SandboxViolation when + the setup script patches setuptools. + +----- +0.6.8 +----- + +* Added "check_packages" in dist. (added in Setuptools 0.6c11) +* Fixed the DONT_PATCH_SETUPTOOLS state. + +----- +0.6.7 +----- + +* Issue 58: Added --user support to the develop command +* Issue 11: Generated scripts now wrap their call to the script entry point + in the standard "if name == 'main'" +* Added the 'DONT_PATCH_SETUPTOOLS' environment variable, so virtualenv + can drive an installation that doesn't patch a global setuptools. +* Reviewed unladen-swallow specific change from + http://code.google.com/p/unladen-swallow/source/detail?spec=svn875&r=719 + and determined that it no longer applies. Distribute should work fine with + Unladen Swallow 2009Q3. +* Issue 21: Allow PackageIndex.open_url to gracefully handle all cases of a + httplib.HTTPException instead of just InvalidURL and BadStatusLine. +* Removed virtual-python.py from this distribution and updated documentation + to point to the actively maintained virtualenv instead. +* Issue 64: use_setuptools no longer rebuilds the distribute egg every + time it is run +* use_setuptools now properly respects the requested version +* use_setuptools will no longer try to import a distribute egg for the + wrong Python version +* Issue 74: no_fake should be True by default. +* Issue 72: avoid a bootstrapping issue with easy_install -U + +----- +0.6.6 +----- + +* Unified the bootstrap file so it works on both py2.x and py3k without 2to3 + (patch by Holger Krekel) + +----- +0.6.5 +----- + +* Issue 65: cli.exe and gui.exe are now generated at build time, + depending on the platform in use. + +* Issue 67: Fixed doc typo (PEP 381/382) + +* Distribute no longer shadows setuptools if we require a 0.7-series + setuptools. And an error is raised when installing a 0.7 setuptools with + distribute. + +* When run from within buildout, no attempt is made to modify an existing + setuptools egg, whether in a shared egg directory or a system setuptools. + +* Fixed a hole in sandboxing allowing builtin file to write outside of + the sandbox. + +----- +0.6.4 +----- + +* Added the generation of `distribute_setup_3k.py` during the release. + 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. + +* Fixed a bootstrap bug on the use_setuptools() API. + +----- +0.6.3 +----- + +setuptools +========== + +* Fixed a bunch of calls to file() that caused crashes on Python 3. + +bootstrapping +============= + +* Fixed a bug in sorting that caused bootstrap to fail on Python 3. + +----- +0.6.2 +----- + +setuptools +========== + +* Added Python 3 support; see docs/python3.txt. + This closes http://bugs.python.org/setuptools/issue39. + +* Added option to run 2to3 automatically when installing on Python 3. + This closes issue #31. + +* Fixed invalid usage of requirement.parse, that broke develop -d. + This closes http://bugs.python.org/setuptools/issue44. + +* Fixed script launcher for 64-bit Windows. + This closes http://bugs.python.org/setuptools/issue2. + +* KeyError when compiling extensions. + This closes http://bugs.python.org/setuptools/issue41. + +bootstrapping +============= + +* Fixed bootstrap not working on Windows. This closes issue #49. + +* Fixed 2.6 dependencies. This closes issue #50. + +* Make sure setuptools is patched when running through easy_install + This closes http://bugs.python.org/setuptools/issue40. + +----- +0.6.1 +----- + +setuptools +========== + +* package_index.urlopen now catches BadStatusLine and malformed url errors. + This closes issue #16 and issue #18. + +* zip_ok is now False by default. This closes + http://bugs.python.org/setuptools/issue33. + +* Fixed invalid URL error catching. http://bugs.python.org/setuptools/issue20. + +* Fixed invalid bootstraping with easy_install installation (issue #40). + Thanks to Florian Schulze for the help. + +* Removed buildout/bootstrap.py. A new repository will create a specific + bootstrap.py script. + + +bootstrapping +============= + +* The boostrap process leave setuptools alone if detected in the system + and --root or --prefix is provided, but is not in the same location. + This closes issue #10. + +--- +0.6 +--- + +setuptools +========== + +* Packages required at build time where not fully present at install time. + This closes issue #12. + +* Protected against failures in tarfile extraction. This closes issue #10. + +* Made Jython api_tests.txt doctest compatible. This closes issue #7. + +* sandbox.py replaced builtin type file with builtin function open. This + closes issue #6. + +* Immediately close all file handles. This closes issue #3. + +* Added compatibility with Subversion 1.6. This references issue #1. + +pkg_resources +============= + +* Avoid a call to /usr/bin/sw_vers on OSX and use the official platform API + instead. Based on a patch from ronaldoussoren. This closes issue #5. + +* Fixed a SandboxViolation for mkdir that could occur in certain cases. + This closes issue #13. + +* Allow to find_on_path on systems with tight permissions to fail gracefully. + This closes issue #9. + +* Corrected inconsistency between documentation and code of add_entry. + This closes issue #8. + +* Immediately close all file handles. This closes issue #3. + +easy_install +============ + +* Immediately close all file handles. This closes issue #3. + diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 00000000..a5f050cd --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,31 @@ +============ +Contributors +============ + +* Alex Grönholm +* Alice Bevan-McGregor +* Arfrever Frehtes Taifersar Arahesis +* Christophe Combelles +* Daniel Stutzbach +* Daniel Holth +* Hanno Schlichting +* Jannis Leidel +* Jason R. Coombs +* Jim Fulton +* Jonathan Lange +* Justin Azoff +* Lennart Regebro +* Marc Abramowitz +* Martin von Löwis +* Noufal Ibrahim +* Pete Hollobon +* Philip J. Eby +* Philip Jenvey +* Reinout van Rees +* Robert Myers +* Stefan H. Holek +* Tarek Ziadé +* Toshio Kuratomi + +If you think you name is missing, please add it (alpha order by first name) + diff --git a/DEVGUIDE.txt b/DEVGUIDE.txt new file mode 100644 index 00000000..db556480 --- /dev/null +++ b/DEVGUIDE.txt @@ -0,0 +1,22 @@ +============================ +Quick notes for contributors +============================ + +Setuptools is developed using the DVCS Mercurial. + +Grab the code at bitbucket:: + + $ hg clone https://bitbucket.org/jaraco/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 +on bitbucket. If you make some changes, don't forget to: + +- add a note in CHANGES.txt + +Please commit bug-fixes against the current maintenance branch and new +features to the default branch. + +You can run the tests via:: + + $ python setup.py test diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..9837747a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +recursive-include setuptools *.py *.txt *.exe +recursive-include tests *.py *.c *.pyx *.txt +recursive-include setuptools/tests *.html +recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html +recursive-include _markerlib *.py +include *.py +include *.txt +include MANIFEST.in +include launcher.c diff --git a/MERGE.txt b/MERGE.txt new file mode 100644 index 00000000..550273fb --- /dev/null +++ b/MERGE.txt @@ -0,0 +1,41 @@ +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. +* Sort order of Distributions in pkg_resources now prefers PyPI to external + links (Distribute issue 163). + +Minor Changes +------------- + +* Wording of some output has changed to replace contractions with their + canonical form (i.e. prefer "could not" to "couldn't"). + +Differences from Distribute 0.6.35: + +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. @@ -5,6 +5,36 @@ Installing and Using Setuptools .. contents:: **Table of Contents** +---------------------------------- +Security Issues - Read this First! +---------------------------------- + +Setuptools and ``easy_install`` currently default to allowing automated +download and execution of code from anywhere on the internet, without actually +verifying the owners of the websites or the authors of the code. If you want +your installation to be more secure, you will need to: + + 1. Manually install the `requests <https://pypi.python.org/pypi/requests>`_ + library **after** installing setuptools, using an SSL-enabled browser or + other tool. (This will enable SSL certificate verification.) + + 2. Configure your default ``--allow-hosts`` setting so that ``easy_install`` + will only download from sites you trust. (E.g., to only download from + ``pypi.python.org`` or some other trusted package index.) + + 3. If you are using a Python version less than 2.6, you will also need to + install the `SSL backport module <https://pypi.python.org/pypi/requests>`_ + to enable SSL downloads from PyPI. (Unfortunately, the ``requests`` + package does not support older versions of Python at this time, so SSL + certificate verification will not be enabled. But at least you'll still be + able to use PyPI, which is in the process of switching to an all-SSL policy + for downloads. + +For more information on how to do all of the above, and for other security- +related information, please see the full `setuptools security documentation +<http://peak.telecommunity.com/DevCenter/SetuptoolsSecurity>`_. + + ------------------------- Installation Instructions ------------------------- @@ -12,7 +42,17 @@ Installation Instructions Windows ======= -Install setuptools using the provided ``.exe`` installer. If you've previously +32-bit version of Python + Install setuptools using the provided ``.exe`` installer. + +64-bit versions of Python + Download `ez_setup.py`_ and run it; it will download the appropriate .egg file and install it for you. (Currently, the provided ``.exe`` installer does not support 64-bit versions of Python for Windows, due to a `distutils installer compatibility issue`_ + +.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py +.. _distutils installer compatibility issue: http://bugs.python.org/issue6792 + + +NOTE: Regardless of what sort of Python you're using, if you've previously installed older versions of setuptools, please delete all ``setuptools*.egg`` and ``setuptools.pth`` files from your system's ``site-packages`` directory (and any other ``sys.path`` directories) FIRST. @@ -90,7 +130,7 @@ Downloads 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. -.. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools +.. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools#files In addition to the PyPI downloads, the development version of ``setuptools`` is available from the `Python SVN sandbox`_, and in-development versions of the @@ -159,3 +199,4 @@ Credits "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!) +.. _files: diff --git a/_markerlib/__init__.py b/_markerlib/__init__.py new file mode 100644 index 00000000..e2b237b1 --- /dev/null +++ b/_markerlib/__init__.py @@ -0,0 +1,16 @@ +try: + import ast + from _markerlib.markers import default_environment, compile, interpret +except ImportError: + if 'ast' in globals(): + raise + def default_environment(): + return {} + def compile(marker): + def marker_fn(environment=None, override=None): + # 'empty markers are True' heuristic won't install extra deps. + return not marker.strip() + marker_fn.__doc__ = marker + return marker_fn + def interpret(marker, environment=None, override=None): + return compile(marker)() diff --git a/_markerlib/markers.py b/_markerlib/markers.py new file mode 100644 index 00000000..c93d7f3b --- /dev/null +++ b/_markerlib/markers.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +"""Interpret PEP 345 environment markers. + +EXPR [in|==|!=|not in] EXPR [or|and] ... + +where EXPR belongs to any of those: + + python_version = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + python_full_version = sys.version.split()[0] + os.name = os.name + sys.platform = sys.platform + platform.version = platform.version() + platform.machine = platform.machine() + platform.python_implementation = platform.python_implementation() + a free string, like '2.6', or 'win32' +""" + +__all__ = ['default_environment', 'compile', 'interpret'] + +import ast +import os +import platform +import sys +import weakref + +_builtin_compile = compile + +try: + from platform import python_implementation +except ImportError: + if os.name == "java": + # Jython 2.5 has ast module, but not platform.python_implementation() function. + def python_implementation(): + return "Jython" + else: + raise + + +# restricted set of variables +_VARS = {'sys.platform': sys.platform, + 'python_version': '%s.%s' % sys.version_info[:2], + # FIXME parsing sys.platform is not reliable, but there is no other + # way to get e.g. 2.7.2+, and the PEP is defined with sys.version + 'python_full_version': sys.version.split(' ', 1)[0], + 'os.name': os.name, + 'platform.version': platform.version(), + 'platform.machine': platform.machine(), + 'platform.python_implementation': python_implementation(), + 'extra': None # wheel extension + } + +def default_environment(): + """Return copy of default PEP 385 globals dictionary.""" + return dict(_VARS) + +class ASTWhitelist(ast.NodeTransformer): + def __init__(self, statement): + self.statement = statement # for error messages + + ALLOWED = (ast.Compare, ast.BoolOp, ast.Attribute, ast.Name, ast.Load, ast.Str) + # Bool operations + ALLOWED += (ast.And, ast.Or) + # Comparison operations + ALLOWED += (ast.Eq, ast.Gt, ast.GtE, ast.In, ast.Is, ast.IsNot, ast.Lt, ast.LtE, ast.NotEq, ast.NotIn) + + def visit(self, node): + """Ensure statement only contains allowed nodes.""" + if not isinstance(node, self.ALLOWED): + raise SyntaxError('Not allowed in environment markers.\n%s\n%s' % + (self.statement, + (' ' * node.col_offset) + '^')) + return ast.NodeTransformer.visit(self, node) + + def visit_Attribute(self, node): + """Flatten one level of attribute access.""" + new_node = ast.Name("%s.%s" % (node.value.id, node.attr), node.ctx) + return ast.copy_location(new_node, node) + +def parse_marker(marker): + tree = ast.parse(marker, mode='eval') + new_tree = ASTWhitelist(marker).generic_visit(tree) + return new_tree + +def compile_marker(parsed_marker): + return _builtin_compile(parsed_marker, '<environment marker>', 'eval', + dont_inherit=True) + +_cache = weakref.WeakValueDictionary() + +def compile(marker): + """Return compiled marker as a function accepting an environment dict.""" + try: + return _cache[marker] + except KeyError: + pass + if not marker.strip(): + def marker_fn(environment=None, override=None): + """""" + return True + else: + compiled_marker = compile_marker(parse_marker(marker)) + def marker_fn(environment=None, override=None): + """override updates environment""" + if override is None: + override = {} + if environment is None: + environment = default_environment() + environment.update(override) + return eval(compiled_marker, environment) + marker_fn.__doc__ = marker + _cache[marker] = marker_fn + return _cache[marker] + +def interpret(marker, environment=None): + return compile(marker)(environment) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..30bf10a9 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,75 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview over all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + +clean: + -rm -rf build/* + +html: + mkdir -p build/html build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html + @echo + @echo "Build finished. The HTML pages are in build/html." + +pickle: + mkdir -p build/pickle build/doctrees + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +web: pickle + +json: + mkdir -p build/json build/doctrees + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + mkdir -p build/htmlhelp build/doctrees + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in build/htmlhelp." + +latex: + mkdir -p build/latex build/doctrees + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex + @echo + @echo "Build finished; the LaTeX files are in build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + mkdir -p build/changes build/doctrees + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes + @echo + @echo "The overview file is in build/changes." + +linkcheck: + mkdir -p build/linkcheck build/doctrees + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in build/linkcheck/output.txt." diff --git a/docs/_templates/indexsidebar.html b/docs/_templates/indexsidebar.html new file mode 100644 index 00000000..9d49ae5d --- /dev/null +++ b/docs/_templates/indexsidebar.html @@ -0,0 +1,8 @@ +<h3>Download</h3> + +<p>Current version: <b>{{ version }}</b></p> +<p>Get Setuptools from the <a href="http://pypi.python.org/pypi/setuptools"> Python Package Index</a> + +<h3>Questions? Suggestions? Contributions?</h3> + +<p>Visit the <a href="http://bitbucket.org/jaraco/setuptools">Setuptools project page</a> </p> diff --git a/docs/_theme/nature/static/nature.css_t b/docs/_theme/nature/static/nature.css_t new file mode 100644 index 00000000..1a654264 --- /dev/null +++ b/docs/_theme/nature/static/nature.css_t @@ -0,0 +1,237 @@ +/** + * Sphinx stylesheet -- default theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Arial, sans-serif; + font-size: 100%; + background-color: #111111; + color: #555555; + margin: 0; + padding: 0; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 300px; +} + +hr{ + border: 1px solid #B1B4B6; +} + +div.document { + background-color: #fafafa; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 1em 30px 30px 30px; + font-size: 0.9em; +} + +div.footer { + color: #555; + width: 100%; + padding: 13px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #444444; +} + +div.related { + background-color: #6BA81E; + line-height: 36px; + color: #ffffff; + text-shadow: 0px 1px 0 #444444; + font-size: 1.1em; +} + +div.related a { + color: #E2F3CC; +} + +div.related .right { + font-size: 0.9em; +} + +div.sphinxsidebar { + font-size: 0.9em; + line-height: 1.5em; + width: 300px; +} + +div.sphinxsidebarwrapper{ + padding: 20px 0; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Arial, sans-serif; + color: #222222; + font-size: 1.2em; + font-weight: bold; + margin: 0; + padding: 5px 10px; + text-shadow: 1px 1px 0 white +} + +div.sphinxsidebar h3 a { + color: #444444; +} + +div.sphinxsidebar p { + color: #888888; + padding: 5px 20px; + margin: 0.5em 0px; +} + +div.sphinxsidebar p.topless { +} + +div.sphinxsidebar ul { + margin: 10px 10px 10px 20px; + padding: 0; + color: #000000; +} + +div.sphinxsidebar a { + color: #444444; +} + +div.sphinxsidebar a:hover { + color: #E32E00; +} + +div.sphinxsidebar input { + border: 1px solid #cccccc; + font-family: sans-serif; + font-size: 1.1em; + padding: 0.15em 0.3em; +} + +div.sphinxsidebar input[type=text]{ + margin-left: 20px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #005B81; + text-decoration: none; +} + +a:hover { + color: #E32E00; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Arial, sans-serif; + font-weight: normal; + color: #212224; + margin: 30px 0px 10px 0px; + padding: 5px 0 5px 0px; + text-shadow: 0px 1px 0 white; + border-bottom: 1px solid #C8D5E3; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 150%; } +div.body h3 { font-size: 120%; } +div.body h4 { font-size: 110%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + line-height: 1.8em; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.highlight{ + background-color: white; +} + +div.note { + background-color: #eeeeee; + border: 1px solid #cccccc; +} + +div.seealso { + background-color: #ffffcc; + border: 1px solid #ffff66; +} + +div.topic { + background-color: #fafafa; + border-width: 0; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #ff6666; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 10px; + background-color: #fafafa; + color: #222222; + line-height: 1.5em; + font-size: 1.1em; + margin: 1.5em 0 1.5em 0; + -webkit-box-shadow: 0px 0px 4px #d8d8d8; + -moz-box-shadow: 0px 0px 4px #d8d8d8; + box-shadow: 0px 0px 4px #d8d8d8; +} + +tt { + color: #222222; + padding: 1px 2px; + font-size: 1.2em; + font-family: monospace; +} + +#table-of-contents ul { + padding-left: 2em; +} + diff --git a/docs/_theme/nature/static/pygments.css b/docs/_theme/nature/static/pygments.css new file mode 100644 index 00000000..652b7612 --- /dev/null +++ b/docs/_theme/nature/static/pygments.css @@ -0,0 +1,54 @@ +.c { color: #999988; font-style: italic } /* Comment */ +.k { font-weight: bold } /* Keyword */ +.o { font-weight: bold } /* Operator */ +.cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.cp { color: #999999; font-weight: bold } /* Comment.preproc */ +.c1 { color: #999988; font-style: italic } /* Comment.Single */ +.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #aa0000 } /* Generic.Error */ +.gh { color: #999999 } /* Generic.Heading */ +.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.go { color: #111 } /* Generic.Output */ +.gp { color: #555555 } /* Generic.Prompt */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #aaaaaa } /* Generic.Subheading */ +.gt { color: #aa0000 } /* Generic.Traceback */ +.kc { font-weight: bold } /* Keyword.Constant */ +.kd { font-weight: bold } /* Keyword.Declaration */ +.kp { font-weight: bold } /* Keyword.Pseudo */ +.kr { font-weight: bold } /* Keyword.Reserved */ +.kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.m { color: #009999 } /* Literal.Number */ +.s { color: #bb8844 } /* Literal.String */ +.na { color: #008080 } /* Name.Attribute */ +.nb { color: #999999 } /* Name.Builtin */ +.nc { color: #445588; font-weight: bold } /* Name.Class */ +.no { color: #ff99ff } /* Name.Constant */ +.ni { color: #800080 } /* Name.Entity */ +.ne { color: #990000; font-weight: bold } /* Name.Exception */ +.nf { color: #990000; font-weight: bold } /* Name.Function */ +.nn { color: #555555 } /* Name.Namespace */ +.nt { color: #000080 } /* Name.Tag */ +.nv { color: purple } /* Name.Variable */ +.ow { font-weight: bold } /* Operator.Word */ +.mf { color: #009999 } /* Literal.Number.Float */ +.mh { color: #009999 } /* Literal.Number.Hex */ +.mi { color: #009999 } /* Literal.Number.Integer */ +.mo { color: #009999 } /* Literal.Number.Oct */ +.sb { color: #bb8844 } /* Literal.String.Backtick */ +.sc { color: #bb8844 } /* Literal.String.Char */ +.sd { color: #bb8844 } /* Literal.String.Doc */ +.s2 { color: #bb8844 } /* Literal.String.Double */ +.se { color: #bb8844 } /* Literal.String.Escape */ +.sh { color: #bb8844 } /* Literal.String.Heredoc */ +.si { color: #bb8844 } /* Literal.String.Interpol */ +.sx { color: #bb8844 } /* Literal.String.Other */ +.sr { color: #808000 } /* Literal.String.Regex */ +.s1 { color: #bb8844 } /* Literal.String.Single */ +.ss { color: #bb8844 } /* Literal.String.Symbol */ +.bp { color: #999999 } /* Name.Builtin.Pseudo */ +.vc { color: #ff99ff } /* Name.Variable.Class */ +.vg { color: #ff99ff } /* Name.Variable.Global */ +.vi { color: #ff99ff } /* Name.Variable.Instance */ +.il { color: #009999 } /* Literal.Number.Integer.Long */
\ No newline at end of file diff --git a/docs/_theme/nature/theme.conf b/docs/_theme/nature/theme.conf new file mode 100644 index 00000000..1cc40044 --- /dev/null +++ b/docs/_theme/nature/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = nature.css +pygments_style = tango diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..4f878f64 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# +# 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. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.txt' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +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.36' +# The full version, including alpha/beta/rc tags. +release = '0.6.36' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'nature' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['_theme'] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +html_title = "Setuptools documentation" + +# A shorter title for the navigation bar. Default is the same as html_title. +html_short_title = "Setuptools" + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = {'index': 'indexsidebar.html'} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +html_use_index = False + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Setuptoolsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Setuptools.tex', ur'Setuptools Documentation', + ur'The fellowship of the packaging', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/EasyInstall.txt b/docs/easy_install.txt index b0341e88..dac0fa8f 100755..100644 --- a/EasyInstall.txt +++ b/docs/easy_install.txt @@ -93,8 +93,8 @@ Note that instead of changing your ``PATH`` to include the Python scripts 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 ``ez_setup.py`` to -control where ``easy_install.exe`` will be installed. +can pass command line options (such as ``--script-dir``) to +``ez_setup.py`` to control where ``easy_install.exe`` will be installed. @@ -144,6 +144,10 @@ and Viewing Source Packages`_ below for more info.):: easy_install --editable --build-directory ~/projects SQLObject +**Example 7**. (New in 0.6.11) Install a distribution within your home dir:: + + easy_install --user SQLAlchemy + Easy Install accepts URLs, filenames, PyPI package names (i.e., ``distutils`` "distribution" names), and package+version specifiers. In each case, it will attempt to locate the latest available version that meets your criteria. @@ -330,7 +334,9 @@ to restrict downloading to hosts in your own intranet. See the section below on `Command-Line Options`_ for more details on the ``--allow-hosts`` option. By default, there are no host restrictions in effect, but you can change this -default by editing the appropriate `configuration files`_ and adding:: +default by editing the appropriate `configuration files`_ and adding: + +.. code-block:: ini [easy_install] allow_hosts = *.myintranet.example.com,*.python.org @@ -411,7 +417,9 @@ generated directory listing (such as the Apache web server provides). If you are setting up an intranet site for package downloads, you may want to configure the target machines to use your download site by default, adding -something like this to their `configuration files`_:: +something like this to their `configuration files`_: + +.. code-block:: ini [easy_install] find_links = http://mypackages.example.com/somedir/ @@ -423,7 +431,7 @@ on multiple lines if necessary (as long as the subsequent lines are indented. If you are more ambitious, you can also create an entirely custom package index or PyPI mirror. See the ``--index-url`` option under `Command-Line Options`_, -below, and also the section on the `Package Index "API"`_. +below, and also the section on `Package Index "API"`_. Password-Protected Sites @@ -445,7 +453,9 @@ Controlling Build Options EasyInstall respects standard distutils `Configuration Files`_, so you can use them to configure build options for packages that it installs from source. For example, if you are on Windows using the MinGW compiler, you can configure the -default compiler by putting something like this:: +default compiler by putting something like this: + +.. code-block:: ini [build] compiler = mingw32 @@ -593,7 +603,9 @@ distutils configuration files, under the command heading ``easy_install``. EasyInstall will look first for a ``setup.cfg`` file in the current directory, then a ``~/.pydistutils.cfg`` or ``$HOME\\pydistutils.cfg`` (on Unix-like OSes and Windows, respectively), and finally a ``distutils.cfg`` file in the -``distutils`` package directory. Here's a simple example:: +``distutils`` package directory. Here's a simple example: + +.. code-block:: ini [easy_install] @@ -620,6 +632,9 @@ until and unless you override them explicitly in an ``[easy_install]`` section. For more information, see also the current Python documentation on the `use and location of distutils configuration files <http://docs.python.org/inst/config-syntax.html>`_. +Notice that ``easy_install`` will use the ``setup.cfg`` from the current +working directory only if it was triggered from ``setup.py`` through the +``install_requires`` option. The standalone command will not use that file. Command-Line Options -------------------- @@ -705,6 +720,10 @@ Command-Line Options versions of a package, but do not want to reset the version that will be run by scripts that are already installed. +``--user`` (New in 0.6.11) + Use the the user-site-packages as specified in :pep:`370` + instead of the global site-packages. + ``--always-copy, -a`` (New in 0.5a4) Copy all needed distributions to the installation directory, even if they are already present in a directory on sys.path. In older versions of @@ -757,6 +776,13 @@ 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. + 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!) @@ -851,9 +877,6 @@ Command-Line Options judgment and force an installation directory to be treated as if it supported ``.pth`` files. - (If you want to *make* a non-``PYTHONPATH`` directory support ``.pth`` - files, please see the `Administrator Installation`_ section below.) - ``--no-deps, -N`` (New in 0.6a6) Don't install any dependencies. This is intended as a convenience for tools that wrap eggs in a platform-specific packaging system. (We don't @@ -896,7 +919,7 @@ Command-Line Options projects, not in-development ones, because such projects may not have a currently-valid version number. So, it usually only installs them when their ``setup.py`` directory is explicitly passed on the command line. - + However, if this option is used, then any in-development projects that were installed using the ``setup.py develop`` command, will be used to build eggs, effectively upgrading the "in-development" project to a snapshot @@ -916,210 +939,64 @@ Command-Line Options Custom Installation Locations ----------------------------- -EasyInstall manages what packages are active using Python ``.pth`` files, which -are normally only usable in Python's main ``site-packages`` directory. On some -platforms (such as Mac OS X), there are additional ``site-packages`` -directories that you can use besides the main one, but usually there is only -one directory on the system where you can install packages without extra steps. - -There are many reasons, however, why you might want to install packages -somewhere other than the ``site-packages`` directory. For example, you might -not have write access to that directory. You may be working with unstable -versions of packages that you don't want to install system-wide. And so on. - -The following sections describe various approaches to custom installation; feel -free to choose which one best suits your system and needs. - -`Administrator Installation`_ - This approach is for when you have write access to ``site-packages`` (or - another directory where ``.pth`` files are processed), but don't want to - install packages there. This can also be used by a system administrator - to enable each user having their own private directories that EasyInstall - will use to install packages. - -`Mac OS X "User" Installation`_ - This approach produces a result similar to an administrator installation - that gives each user their own private package directory, but on Mac OS X - the hard part has already been done for you. This is probably the best - approach for Mac OS X users. - -`Creating a "Virtual" Python`_ - This approach is for when you don't have "root" or access to write to the - ``site-packages`` directory, and would like to be able to set up one or - more "virtual python" executables for your projects. This approach - gives you the benefits of multiple Python installations, but without having - to actually install Python more than once and use up lots of disk space. - (Only the Python executable is copied; the libraries will be symlinked - from the systemwide Python.) - - If you don't already have any ``PYTHONPATH`` customization or - special distutils configuration, and you can't use either of the preceding - approaches, this is probably the best one for you. - -`"Traditional" PYTHONPATH-based Installation`_ - If you already have a custom ``PYTHONPATH``, and/or a custom distutils - configuration, and don't want to change any of your existing setup, you may - be interested in this approach. (If you're using a custom ``.pth`` file to - point to your custom installation location, however, you should use - `Administrator Installation`_ to enable ``.pth`` processing in the custom - location instead, as that is easier and more flexible than this approach.) - - -Administrator Installation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you have root access to your machine, you can easily configure it to allow -each user to have their own directory where Python packages can be installed -and managed by EasyInstall. - -First, create an ``altinstall.pth`` file in Python's ``site-packages`` -directory, containing the following line (substituting the correct Python -version):: - - import os, site; site.addsitedir(os.path.expanduser('~/lib/python2.3')) - -This will automatically add each user's ``~/lib/python2.X`` directory to -``sys.path`` (if it exists), *and* it will process any ``.pth`` files in that -directory -- which is what makes it usable with EasyInstall. - -The next step is to create or modify ``distutils.cfg`` in the ``distutils`` -directory of your Python library. The correct directory will be something like -``/usr/lib/python2.X/distutils`` on most Posix systems and something like -``C:\\Python2X\Lib\distutils`` on Windows machines. Add the following lines -to the file, substituting the correct Python version if necessary:: - - [install] - install_lib = ~/lib/python2.3 - - # This next line is optional but often quite useful; it directs EasyInstall - # and the distutils to install scripts in the user's "bin" directory. For - # Mac OS X framework Python builds, you should use /usr/local/bin instead, - # because neither ~/bin nor the default script installation location are on - # the system PATH. - # - install_scripts = ~/bin - -This will configure the distutils and EasyInstall to install packages to the -user's home directory by default. - -Of course, you aren't limited to using a ``~/lib/python2.X`` directory with -this approach. You can substitute a specific systemwide directory if you like. -You can also edit ``~/.pydistutils.cfg`` (or ``~/pydistutils.cfg`` on Windows) -instead of changing the master ``distutils.cfg`` file. The true keys of this -approach are simply that: - -1. any custom installation directory must be added to ``sys.path`` using a - ``site.addsitedir()`` call from a working ``.pth`` file or - ``sitecustomize.py``. - -2. The active distutils configuration file(s) or ``easy_install`` command line - should include the custom directory in the ``--site-dirs`` option, so that - EasyInstall knows that ``.pth`` files will work in that location. (This is - because Python does not keep track of what directories are or aren't enabled - for ``.pth`` processing, in any way that EasyInstall can find out.) - -As long as both of these things have been done, your custom installation -location is good to go. - - -Mac OS X "User" Installation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you are on a Mac OS X machine, you should just use the -``~/Library/Python/2.x/site-packages`` directory as your custom installation -location, because it is already configured to process ``.pth`` files, and -EasyInstall already knows this. - -Before installing EasyInstall/setuptools, just create a ``~/.pydistutils.cfg`` -file with the following contents (or add this to the existing contents):: +By default, EasyInstall installs python packages into Python's main ``site-packages`` directory, +and manages them using a custom ``.pth`` file in that same directory. - [install] - install_lib = ~/Library/Python/$py_version_short/site-packages - install_scripts = ~/bin +Very often though, a user or developer wants ``easy_install`` to install and manage python packages +in an alternative location, usually for one of 3 reasons: -This will tell the distutils and EasyInstall to always install packages in -your personal ``site-packages`` directory, and scripts to ``~/bin``. (Note: do -*not* replace ``$py_version_short`` with an actual Python version in the -configuration file! The distutils will substitute the correct value at -runtime, so that the above configuration file should work correctly no matter -what Python version you use, now or in the future.) +1. They don't have access to write to the main Python site-packages directory. -Once you have done this, you can follow the normal `installation instructions`_ -and use ``easy_install`` without any other special options or steps. +2. They want a user-specific stash of packages, that is not visible to other users. -(Note, however, that ``~/bin`` is not in the default ``PATH``, so you may have -to refer to scripts by their full location. You may want to modify your shell -startup script (likely ``.bashrc`` or ``.profile``) or your -``~/.MacOSX/environment.plist`` to include ``~/bin`` in your ``PATH``. +3. They want to isolate a set of packages to a specific python application, usually to minimize + the possibility of version conflicts. +Historically, there have been many approaches to achieve custom installation. +The following section lists only the easiest and most relevant approaches [1]_. -Creating a "Virtual" Python -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`Use the "--user" option`_ -If you are on a Linux, BSD, Cygwin, or other similar Unix-like operating -system, but don't have root access, you can create your own "virtual" -Python installation, which uses its own library directories and some symlinks -to the site-wide Python. +`Use the "--user" option and customize "PYTHONUSERBASE"`_ -In the simplest case, your virtual Python installation will live under the -``~/lib/python2.x``, ``~/include/python2.x``, and ``~/bin`` directories. Just -download `virtual-python.py`_ and run it using the site-wide Python. If you -want to customize the location, you can use the ``--prefix`` option to specify -an installation base directory in place of ``~``. (Use ``--help`` to get the -complete list of options.) +`Use "virtualenv"`_ -.. _virtual-python.py: http://peak.telecommunity.com/dist/virtual-python.py +.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by `PEP-370`_ in Python 2.6. -When you're done, you'll have a ``~/bin/python`` executable that's linked to -the local Python installation and inherits all its current libraries, but which -allows you to add as many new libraries as you want. Simply use this new -Python in place of your system-defined one, and you can modify it as you like -without breaking anything that relies on the system Python. You'll also still -need to follow the standard `installation instructions`_ to install setuptools -and EasyInstall, using your new ``~/bin/python`` executable in place of the -system Python. +.. _PEP-370: http://www.python.org/dev/peps/pep-0370/ -Note that if you were previously setting a ``PYTHONPATH`` and/or had other -special configuration options in your ``~/.pydistutils.cfg``, you may need to -remove these settings *before* running ``virtual-python.py``. This is because -your new Python executable will not need *any* custom configuration for the -distutils or EasyInstall; everything will go to the correct ``~/lib`` and -``~/bin`` directories automatically. -You should, however, also make sure that the ``bin`` subdirectory of your -installation prefix (e.g. ``~/bin``) is on your ``PATH``, because that is where -EasyInstall and the distutils will install new Python scripts. - - -"Traditional" ``PYTHONPATH``-based Installation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Use the "--user" option +~~~~~~~~~~~~~~~~~~~~~~~ +With Python 2.6 came the User scheme for installation, which means that all +python distributions support an alternative install location that is specific to a user [2]_ [3]_. +The Default location for each OS is explained in the python documentation +for the ``site.USER_BASE`` variable. This mode of installation can be turned on by +specifying the ``--user`` option to ``setup.py install`` or ``easy_install``. +This approach serves the need to have a user-specific stash of packages. -This installation method is not as robust or as flexible as `creating a -"virtual" python`_ installation, as it uses various tricks to fool Python into -processing ``.pth`` files where it normally wouldn't. We suggest you at least -consider using one of the other approaches, as they will generally result in -a cleaner, more usable Python configuration. However, if for some reason you -can't or won't use one of the other approaches, here's how to do it. +.. [2] Prior to Python2.6, Mac OS X offered a form of the User scheme. That is now subsumed into the User scheme introduced in Python 2.6. +.. [3] Prior to the User scheme, there was the Home scheme, which is still available, but requires more effort than the User scheme to get packages recognized. -Assuming that you want to install packages in a directory called ``~/py-lib``, -and scripts in ``~/bin``, here's what you need to do: +Use the "--user" option and customize "PYTHONUSERBASE" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The User scheme install location can be customized by setting the ``PYTHONUSERBASE`` environment +variable, which updates the value of ``site.USER_BASE``. To isolate packages to a specific +application, simply set the OS environment of that application to a specific value of +``PYTHONUSERBASE``, that contains just those packages. -First, edit ``~/.pydistutils.cfg`` to include these settings, if you don't -already have them:: +Use "virtualenv" +~~~~~~~~~~~~~~~~ +"virtualenv" is a 3rd-party python package that effectively "clones" a python installation, thereby +creating an isolated location to intall packages. The evolution of "virtualenv" started before the existence +of the User installation scheme. "virtualenv" provides a version of ``easy_install`` that is +scoped to the cloned python install and is used in the normal way. "virtualenv" does offer various features +that the User installation scheme alone does not provide, e.g. the ability to hide the main python site-packages. - [install] - install_lib = ~/py-lib - install_scripts = ~/bin +Please refer to the `virtualenv`_ documentation for more details. -Be sure to do this *before* you try to run the ``ez_setup.py`` installation -script. Then, follow the standard `installation instructions`_, but make -sure that ``~/py-lib`` is listed in your ``PYTHONPATH`` environment variable. +.. _virtualenv: http://pypi.python.org/pypi/virtualenv -Your library installation directory *must* be in listed in ``PYTHONPATH``, -not only when you install packages with EasyInstall, but also when you use -any packages that are installed using EasyInstall. You will probably want to -edit your ``~/.profile`` or other configuration file(s) to ensure that it is -set, if you haven't already got this set up on your machine. Package Index "API" @@ -1213,11 +1090,10 @@ displayed MD5 info (broken onto two lines for readability):: <a href="([^"#]+)">([^<]+)</a>\n\s+\(<a href="[^?]+\?:action=show_md5 &digest=([0-9a-f]{32})">md5</a>\) +History +======= -Release Notes/Change History -============================ - -0.6final +0.6c9 * Fixed ``win32.exe`` support for .pth files, so unnecessary directory nesting is flattened out in the resulting egg. (There was a case-sensitivity problem that affected some distributions, notably ``pywin32``.) @@ -1267,7 +1143,7 @@ Release Notes/Change History installed using ``setup.py develop``. * Fixed not HTML-decoding URLs scraped from web pages - + 0.6c5 * Fixed ``.dll`` files on Cygwin not having executable permisions when an egg is installed unzipped. diff --git a/docs/index.txt b/docs/index.txt new file mode 100644 index 00000000..21442b93 --- /dev/null +++ b/docs/index.txt @@ -0,0 +1,24 @@ +Welcome to Setuptools' documentation! +===================================== + +Setuptools is a fully-featured, actively-maintained, and stable library +designed to facilitate packaging Python projects, where packaging includes: + + - Python package and module definitions + - Distribution package metadata + - Test hooks + - Project installation + - Platform-specific details + - Python 3 support + +Documentation content: + +.. toctree:: + :maxdepth: 2 + + roadmap + python3 + using + setuptools + easy_install + pkg_resources diff --git a/pkg_resources.txt b/docs/pkg_resources.txt index 7d8afd37..480f9547 100755..100644 --- a/pkg_resources.txt +++ b/docs/pkg_resources.txt @@ -140,6 +140,9 @@ directly alter ``sys.path`` at runtime, you may find these APIs useful: is invoked, it checks for the presence of namespace packages and updates their ``__path__`` contents accordingly. +Applications that manipulate namespace packages or directly alter ``sys.path`` +at runtime may also need to use this API function: + ``fixup_namespace_packages(path_item)`` Declare that `path_item` is a newly added item on ``sys.path`` that may need to be used to update existing namespace packages. Ordinarily, this is @@ -182,7 +185,7 @@ not provide any way to detect arbitrary changes to a list object like the constructor is called. Note that you will not normally construct ``WorkingSet`` instances - yourbut instead you will implicitly or explicitly use the global + yourself, but instead you will implicitly or explicitly use the global ``working_set`` instance. For the most part, the ``pkg_resources`` API is designed so that the ``working_set`` is used by default, such that you don't have to explicitly refer to it most of the time. @@ -269,7 +272,7 @@ instance: the global ``working_set`` to reflect the change. This method is also called by the ``WorkingSet()`` constructor during initialization. - This method uses ``find_distributions(entry,False)`` to find distributions + This method uses ``find_distributions(entry,True)`` to find distributions corresponding to the path entry, and then ``add()`` them. `entry` is always appended to the ``entries`` attribute, even if it is already present, however. (This is because ``sys.path`` can contain the same value @@ -1591,8 +1594,9 @@ Parsing Utilities ``Requirement`` string, as a distribution name, or a PyPI project name. All non-alphanumeric runs are condensed to single "-" characters, such that a name like "The $$$ Tree" becomes "The-Tree". Note that if you are - generating a filename from this value you should replace the "-" characters - with underscores ("_") because setuptools and the distutils + generating a filename from this value you should combine it with a call to + ``to_filename()`` so all dashes ("-") are replaced by underscores ("_"). + See ``to_filename()``. ``safe_version(version)`` Similar to ``safe_name()`` except that spaces in the input become dots, and @@ -1687,19 +1691,17 @@ File/Path Utilities reflect the platform's case-sensitivity, so there is always the possibility of two apparently-different paths being equal on such platforms. +History +------- ----------------------------- -Release Notes/Change History ----------------------------- - -0.6final +0.6c9 * Fix ``resource_listdir('')`` always returning an empty list for zipped eggs. - + 0.6c7 * Fix package precedence problem where single-version eggs installed in ``site-packages`` would take precedence over ``.egg`` files (or directories) installed in ``site-packages``. - + 0.6c6 * Fix extracted C extensions not having executable permissions under Cygwin. @@ -1707,7 +1709,7 @@ Release Notes/Change History * Fix cache dir defaults on Windows when multiple environment vars are needed to construct a path. - + 0.6c4 * Fix "dev" versions being considered newer than release candidates. diff --git a/docs/python3.txt b/docs/python3.txt new file mode 100644 index 00000000..1e019951 --- /dev/null +++ b/docs/python3.txt @@ -0,0 +1,124 @@ +===================================================== +Supporting both Python 2 and Python 3 with Setuptools +===================================================== + +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. + + +Setuptools as help during porting +================================= + +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``. + +See :ref:`test` for more information on this. + +Once you have the tests running under Python 2, you can add the use_2to3 +keyword parameters to setup(), and start running the tests under Python 3. +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. + +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 Setuptools will convert them as +well. + +By default, the conversion uses all fixers in the ``lib2to3.fixers`` package. +To use additional fixers, the parameter ``use_2to3_fixers`` can be set +to a list of names of packages containing fixers. To exclude fixers, the +parameter ``use_2to3_exclude_fixers`` can be set to fixer names to be +skipped. + +A typical setup.py can look something like this:: + + from setuptools import setup + + setup( + name='your.module', + version = '1.0', + description='This is your awesome module', + author='You', + author_email='your@email', + package_dir = {'': 'src'}, + packages = ['your', 'you.module'], + test_suite = 'your.module.tests', + use_2to3 = True, + convert_2to3_doctests = ['src/your/module/README.txt'], + use_2to3_fixers = ['your.fixers'], + use_2to3_exclude_fixers = ['lib2to3.fixes.fix_import'], + ) + +Differential conversion +----------------------- + +Note that a file will only be copied and converted during the build process +if the source file has been changed. If you add a file to the doctests +that should be converted, it will not be converted the next time you run +the tests, since it hasn't been modified. You need to remove it from the +build directory. Also if you run the build, install or test commands before +adding the use_2to3 parameter, you will have to remove the build directory +before you run the test command, as the files otherwise will seem updated, +and no conversion will happen. + +In general, if code doesn't seem to be converted, deleting the build directory +and trying again is a good saferguard against the build directory getting +"out of sync" with the source directory. + +Distributing Python 3 modules +============================= + +You can distribute your modules with Python 3 support in different ways. A +normal source distribution will work, but can be slow in installing, as the +2to3 process will be run during the install. But you can also distribute +the module in binary format, such as a binary egg. That egg will contain the +already converted code, and hence no 2to3 conversion is needed during install. + +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 older versions of setuptools +======================================================= + +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 +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():: + + from setuptools import setup + import sys + + extra = {} + if sys.version_info >= (3,): + extra['use_2to3'] = True + extra['convert_2to3_doctests'] = ['src/your/module/README.txt'] + extra['use_2to3_fixers'] = ['your.fixers'] + + setup( + name='your.module', + version = '1.0', + description='This is your awesome module', + author='You', + author_email='your@email', + package_dir = {'': 'src'}, + packages = ['your', 'you.module'], + test_suite = 'your.module.tests', + **extra + ) + +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 new file mode 100644 index 00000000..44bcdb0f --- /dev/null +++ b/docs/roadmap.txt @@ -0,0 +1,14 @@ +======= +Roadmap +======= + +Setuptools has merged with Distribute and to provide a unified codebase for +ongoing development. + +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/setuptools.txt b/docs/setuptools.txt index d1b25302..2504dcda 100755..100644 --- a/setuptools.txt +++ b/docs/setuptools.txt @@ -1,8 +1,8 @@ -====================================================== -Building and Distributing Packages with ``setuptools`` -====================================================== +================================================== +Building and Distributing Packages with Setuptools +================================================== -``setuptools`` 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://peak.telecommunity.com/dist/ez_setup.py +.. _bootstrap module: http://bitbucket.org/jaraco/setuptools/downloads/ez_setup.py Feature Highlights: @@ -72,7 +72,7 @@ is available from the `Python SVN sandbox`_, and in-development versions of the .. contents:: **Table of Contents** -.. _ez_setup.py: `bootstrap module`_ +.. _distribute_setup.py: `bootstrap module`_ ----------------- @@ -95,7 +95,7 @@ other than Python's ``site-packages`` directory. If you want the current in-development version of setuptools, you should first install a stable version, and then run:: - ez_setup.py setuptools==dev + distribute_setup.py setuptools==dev This will download and install the latest development (i.e. unstable) version of setuptools from the Python Subversion sandbox. @@ -141,7 +141,7 @@ dependencies, and perhaps some data files and scripts:: '': ['*.txt', '*.rst'], # And include any *.msg files found in the 'hello' package, too: 'hello': ['*.msg'], - } + }, # metadata for upload to PyPI author = "Me", @@ -187,10 +187,11 @@ than ``2.4.1`` (which has a higher release number). A pre-release tag is a series of letters that are alphabetically before "final". Some examples of prerelease tags would include ``alpha``, ``beta``, -``a``, ``c``, ``dev``, and so on. You do not have to place a dot before -the prerelease tag if it's immediately after a number, but it's okay to do -so if you prefer. Thus, ``2.4c1`` and ``2.4.c1`` both represent release -candidate 1 of version ``2.4``, and are treated as identical by setuptools. +``a``, ``c``, ``dev``, and so on. You do not have to place a dot or dash +before the prerelease tag if it's immediately after a number, but it's okay to +do so if you prefer. Thus, ``2.4c1`` and ``2.4.c1`` and ``2.4-c1`` all +represent release candidate 1 of version ``2.4``, and are treated as identical +by setuptools. In addition, there are three special prerelease tags that are treated as if they were the letter ``c``: ``pre``, ``preview``, and ``rc``. So, version @@ -216,13 +217,6 @@ a post-release tag, so this version is *newer* than ``0.6a9.dev``. For the most part, setuptools' interpretation of version numbers is intuitive, but here are a few tips that will keep you out of trouble in the corner cases: -* Don't use ``-`` or any other character than ``.`` as a separator, unless you - really want a post-release. Remember that ``2.1-rc2`` means you've - *already* released ``2.1``, whereas ``2.1rc2`` and ``2.1.c2`` are candidates - you're putting out *before* ``2.1``. If you accidentally distribute copies - of a post-release that you meant to be a pre-release, the only safe fix is to - bump your main release number (e.g. to ``2.1.1``) and re-release the project. - * Don't stick adjoining pre-release tags together without a dot or number between them. Version ``1.9adev`` is the ``adev`` prerelease of ``1.9``, *not* a development pre-release of ``1.9a``. Use ``.dev`` instead, as in @@ -239,7 +233,7 @@ but here are a few tips that will keep you out of trouble in the corner cases: >>> parse_version('1.9.a.dev') == parse_version('1.9a0dev') True >>> parse_version('2.1-rc2') < parse_version('2.1') - False + True >>> parse_version('0.6a9dev-r41475') < parse_version('0.6a9') True @@ -338,8 +332,8 @@ unless you need the associated ``setuptools`` feature. merge such subpackages into a single parent package at runtime, as long as you declare them in each project that contains any subpackages of the namespace package, and as long as the namespace package's ``__init__.py`` - does not contain any code. See the section below on `Namespace Packages`_ - for more information. + does not contain any code other than a namespace declaration. See the + section below on `Namespace Packages`_ for more information. ``test_suite`` A string naming a ``unittest.TestCase`` subclass (or a package or module @@ -404,6 +398,18 @@ unless you need the associated ``setuptools`` feature. mess with it. For more details on how this argument works, see the section below on `Automatic Resource Extraction`_. +``use_2to3`` + Convert the source code from Python 2 to Python 3 with 2to3 during the + build process. See :doc:`python3` for more details. + +``convert_2to3_doctests`` + List of doctest source files that need to be converted with 2to3. + See :doc:`python3` for more details. + +``use_2to3_fixers`` + A list of modules to search for additional fixers to be used during + the 2to3 conversion. See :doc:`python3` for more details. + Using ``find_packages()`` ------------------------- @@ -523,7 +529,7 @@ Python must be available via the ``PATH`` environment variable, under its "long" name. That is, if the egg is built for Python 2.3, there must be a ``python2.3`` executable present in a directory on ``PATH``. -This feature is primarily intended to support bootstrapping the installation of +This feature is primarily intended to support distribute_setup the installation of setuptools itself on non-Windows platforms, but may also be useful for other projects as well. @@ -604,14 +610,20 @@ Dependencies that aren't in PyPI If your project depends on packages that aren't registered in PyPI, you may still be able to depend on them, as long as they are available for download -as an egg, in the standard distutils ``sdist`` format, or as a single ``.py`` -file. You just need to add some URLs to the ``dependency_links`` argument to +as: + +- an egg, in the standard distutils ``sdist`` format, +- a single ``.py`` file, or +- a VCS repository (Subversion, Mercurial, or Git). + +You just need to add some URLs to the ``dependency_links`` argument to ``setup()``. The URLs must be either: -1. direct download URLs, or -2. the URLs of web pages that contain direct download links +1. direct download URLs, +2. the URLs of web pages that contain direct download links, or +3. the repository's URL In general, it's better to link to web pages, because it is usually less complex to update a web page than to release a new version of your project. @@ -625,6 +637,27 @@ by replacing them with underscores.) EasyInstall will recognize this suffix and automatically create a trivial ``setup.py`` to wrap the single ``.py`` file as an egg. +In the case of a VCS checkout, you should also append ``#egg=project-version`` +in order to identify for what package that checkout should be used. You can +append ``@REV`` to the URL's path (before the fragment) to specify a revision. +Additionally, you can also force the VCS being used by prepending the URL with +a certain prefix. Currently available are: + +- ``svn+URL`` for Subversion, +- ``git+URL`` for Git, and +- ``hg+URL`` for Mercurial + +A more complete example would be: + + ``vcs+proto://host/path@revision#egg=project-version`` + +Be careful with the version. It should match the one inside the project files. +If you want do disregard the version, you have to omit it both in the +``requires`` and in the URL's fragment. + +This will do a checkout (or a clone, in Git and Mercurial parlance) to a +temporary folder and run ``setup.py bdist_egg``. + The ``dependency_links`` option takes the form of a list of URL strings. For example, the below will cause EasyInstall to search the specified page for eggs or source distributions, if the package's dependencies aren't already @@ -744,17 +777,18 @@ e.g.:: include_package_data = True ) -This tells setuptools to install any data files it finds in your packages. The -data files must be under CVS or Subversion control, or else they must be +This tells setuptools to install any data files it finds in your packages. +The data files must be under CVS or Subversion control, or else they must be specified via the distutils' ``MANIFEST.in`` file. (They can also be tracked by another revision control system, using an appropriate plugin. See the section below on `Adding Support for Other Revision Control Systems`_ for information on how to write such plugins.) -If you want finer-grained control over what files are included (for example, if -you have documentation files in your package directories and want to exclude -them from installation), then you can also use the ``package_data`` keyword, -e.g.:: +If the data files are not under version control, or are not in a supported +version control system, or if you want finer-grained control over what files +are included (for example, if you have documentation files in your package +directories and want to exclude them from installation), then you can also use +the ``package_data`` keyword, e.g.:: from setuptools import setup, find_packages setup( @@ -810,7 +844,10 @@ converts slashes to appropriate platform-specific separators at build time. (Note: although the ``package_data`` argument was previously only available in ``setuptools``, it was also added to the Python ``distutils`` package as of Python 2.4; there is `some documentation for the feature`__ available on the -python.org website.) +python.org website. If using the setuptools-specific ``include_package_data`` +argument, files specified by ``package_data`` will *not* be automatically +added to the manifest unless they are tracked by a supported version control +system, or are listed in the MANIFEST.in file.) __ http://docs.python.org/dist/node11.html @@ -1070,6 +1107,14 @@ update the ``easy-install.pth`` file to include your project's source code, thereby making it available on ``sys.path`` for all programs using that Python installation. +If you have enabled the ``use_2to3`` flag, then of course the ``.egg-link`` +will not link directly to your source code when run under Python 3, since +that source code would be made for Python 2 and not work under Python 3. +Instead the ``setup.py develop`` will build Python 3 code under the ``build`` +directory, and link there. This means that after doing code changes you will +have to run ``setup.py build`` before these changes are picked up by your +Python 3 installation. + In addition, the ``develop`` command creates wrapper scripts in the target script directory that will run your in-development scripts after ensuring that all your ``install_requires`` packages are available on ``sys.path``. @@ -1102,18 +1147,20 @@ Using ``setuptools``... Without bundling it! Your users might not have ``setuptools`` installed on their machines, or even if they do, it might not be the right version. Fixing this is easy; just -download `ez_setup.py`_, and put it in the same directory as your ``setup.py`` +download `distribute_setup.py`_, and put it in the same directory as your ``setup.py`` script. (Be sure to add it to your revision control system, too.) Then add these two lines to the very top of your setup script, before the script imports -anything from setuptools:: +anything from setuptools: + +.. code-block:: python - import ez_setup - ez_setup.use_setuptools() + import distribute_setup + distribute_setup.use_setuptools() -That's it. The ``ez_setup`` module will automatically download a matching +That's it. The ``distribute_setup`` module will automatically download a matching version of ``setuptools`` from PyPI, if it isn't present on the target system. Whenever you install an updated version of setuptools, you should also update -your projects' ``ez_setup.py`` files, so that a matching version gets installed +your projects' ``distribute_setup.py`` files, so that a matching version gets installed on the target machine(s). By the way, setuptools supports the new PyPI "upload" command, so you can use @@ -1143,8 +1190,8 @@ relevant to your project and your target audience isn't already familiar with setuptools and ``easy_install``. Network Access - If your project is using ``ez_setup``, you should inform users of the need - to either have network access, or to preinstall the correct version of + If your project is using ``distribute_setup``, you should inform users of the + need to either have network access, or to preinstall the correct version of setuptools using the `EasyInstall installation instructions`_. Those instructions also have tips for dealing with firewalls as well as how to manually download and install setuptools. @@ -1214,7 +1261,7 @@ Creating System Packages this kind of patching to work with setuptools. If you or your users have a problem building a usable system package for - your project, please report the problem via the `mailing list`_ so that + your project, please report the problem via the mailing list so that either the "bdist" tool in question or setuptools can be modified to resolve the issue. @@ -1223,27 +1270,28 @@ Creating System Packages Managing Multiple Projects -------------------------- -If you're managing several projects that need to use ``ez_setup``, and you are -using Subversion as your revision control system, you can use the -"svn:externals" property to share a single copy of ``ez_setup`` between +If you're managing several projects that need to use ``distribute_setup``, and you +are using Subversion as your revision control system, you can use the +"svn:externals" property to share a single copy of ``distribute_setup`` between projects, so that it will always be up-to-date whenever you check out or update an individual project, without having to manually update each project to use a new version. However, because Subversion only supports using directories as externals, you -have to turn ``ez_setup.py`` into ``ez_setup/__init__.py`` in order to do this, -then create "externals" definitions that map the ``ez_setup`` directory into -each project. Also, if any of your projects use ``find_packages()`` on their -setup directory, you will need to exclude the resulting ``ez_setup`` package, -to keep it from being included in your distributions, e.g.:: +have to turn ``distribute_setup.py`` into ``distribute_setup/__init__.py`` in order +to do this, then create "externals" definitions that map the ``distribute_setup`` +directory into each project. Also, if any of your projects use +``find_packages()`` on their setup directory, you will need to exclude the +resulting ``distribute_setup`` package, to keep it from being included in your +distributions, e.g.:: setup( ... - packages = find_packages(exclude=['ez_setup']), + packages = find_packages(exclude=['distribute_setup']), ) -Of course, the ``ez_setup`` package will still be included in your packages' -source distributions, as it needs to be. +Of course, the ``distribute_setup`` package will still be included in your +packages' source distributions, as it needs to be. For your convenience, you may use the following external definition, which will track the latest version of setuptools:: @@ -1560,7 +1608,9 @@ Managing "Continuous Releases" Using Subversion If you expect your users to track in-development versions of your project via Subversion, there are a few additional steps you should take to ensure that things work smoothly with EasyInstall. First, you should add the following -to your project's ``setup.cfg`` file:: +to your project's ``setup.cfg`` file: + +.. code-block:: ini [egg_info] tag_build = .dev @@ -1590,7 +1640,9 @@ their checkout URL (as described in the previous section) with an to download ``projectname==dev`` in order to get the latest in-development code. Note that if your project depends on such in-progress code, you may wish to specify your ``install_requires`` (or other requirements) to include -``==dev``, e.g.:: +``==dev``, e.g.: + +.. code-block:: python install_requires = ["OtherProject>=0.2a1.dev-r143,==dev"] @@ -2356,10 +2408,72 @@ The ``upload`` command has a few options worth noting: The URL of the repository to upload to. Defaults to http://pypi.python.org/pypi (i.e., the main PyPI installation). +.. _upload_docs: + +``upload_docs`` - Upload package documentation to PyPI +====================================================== + +PyPI now supports uploading project documentation to the dedicated URL +http://packages.python.org/<project>/. + +The ``upload_docs`` command will create the necessary zip file out of a +documentation directory and will post to the repository. + +Note that to upload the documentation of a project, the corresponding version +must already be registered with PyPI, using the distutils ``register`` +command -- just like the ``upload`` command. + +Assuming there is an ``Example`` project with documentation in the +subdirectory ``docs``, e.g.:: + + Example/ + |-- example.py + |-- setup.cfg + |-- setup.py + |-- docs + | |-- build + | | `-- html + | | | |-- index.html + | | | `-- tips_tricks.html + | |-- conf.py + | |-- index.txt + | `-- tips_tricks.txt + +You can simply pass the documentation directory path to the ``upload_docs`` +command:: + + python setup.py upload_docs --upload-dir=docs/build/html + +If no ``--upload-dir`` is given, ``upload_docs`` will attempt to run the +``build_sphinx`` command to generate uploadable documentation. +For the command to become available, `Sphinx <http://sphinx.pocoo.org/>`_ +must be installed in the same environment as distribute. + +As with other ``setuptools``-based commands, you can define useful +defaults in the ``setup.cfg`` of your Python project, e.g.: + +.. code-block:: ini + + [upload_docs] + upload-dir = docs/build/html + +The ``upload_docs`` command has the following options: + +``--upload-dir`` + The directory to be uploaded to the repository. + +``--show-response`` + Display the full response text from server; this is useful for debugging + PyPI problems. + +``--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). + ------------------------------------- -Extending and Reusing ``setuptools`` ------------------------------------- +-------------------------------- +Extending and Reusing Distribute +-------------------------------- Creating ``distutils`` Extensions ================================= @@ -2443,7 +2557,7 @@ a non-None value. Here's an example validation function:: Your function should accept three arguments: the ``Distribution`` object, the attribute name, and the attribute value. It should raise a -``DistutilsSetupError`` (from the ``distutils.error`` module) if the argument +``DistutilsSetupError`` (from the ``distutils.errors`` module) if the argument is invalid. Remember, your function will only be called with non-None values, and the default value of arguments defined this way is always None. So, your commands should always be prepared for the possibility that the attribute will @@ -2526,7 +2640,9 @@ all the filenames within that directory (and any subdirectories thereof) that are under revision control. For example, if you were going to create a plugin for a revision control system -called "foobar", you would write a function something like this:: +called "foobar", you would write a function something like this: + +.. code-block:: python def find_files_for_foobar(dirname): # loop to yield paths that start with `dirname` @@ -2583,8 +2699,8 @@ XXX Reusing ``setuptools`` Code =========================== -``ez_setup`` ------------- +``distribute_setup`` +-------------------- XXX @@ -2606,12 +2722,10 @@ XXX XXX +History +======= ----------------------------- -Release Notes/Change History ----------------------------- - -0.6final +0.6c9 * Fixed a missing files problem when using Windows source distributions on non-Windows platforms, due to distutils not handling manifest file line endings correctly. @@ -2650,7 +2764,7 @@ Release Notes/Change History C-based module, instead of getting a ``NameError``). 0.6c7 - * Fixed ``distutils.filelist.findall()`` crashing on broken symlinks, and + * Fixed ``distutils.filelist.findall()`` crashing on broken symlinks, and ``egg_info`` command failing on new, uncommitted SVN directories. * Fix import problems with nested namespace packages installed via @@ -2677,7 +2791,7 @@ Release Notes/Change History * Fix ``find_packages()`` treating ``ez_setup`` and directories with ``.`` in their names as packages. - + 0.6c5 * Fix uploaded ``bdist_rpm`` packages being described as ``bdist_egg`` packages under Python versions less than 2.5. @@ -3103,7 +3217,6 @@ Release Notes/Change History 0.3a1 * Initial release. - Mailing List and Bug Tracker ============================ diff --git a/docs/using.txt b/docs/using.txt new file mode 100644 index 00000000..6f93c386 --- /dev/null +++ b/docs/using.txt @@ -0,0 +1,10 @@ +================================ +Using Setuptools in your project +================================ + +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 ez_setup import use_setuptools + use_setuptools() diff --git a/ez_setup.py b/ez_setup.py index d24e845e..d311a0b9 100755..100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -13,264 +13,262 @@ 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 -DEFAULT_VERSION = "0.6c9" -DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', - 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', - 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', - 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', - 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', - 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', - 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', - 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', - 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', - 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', - 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', - 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', - 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', - 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', - 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', - 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', - 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', - 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', - 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', - 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', -} - -import sys, os -try: from hashlib import md5 -except ImportError: from md5 import md5 - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `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 setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules - def do_download(): - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg +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.36" +DEFAULT_URL = "http://pypi.python.org/packages/source/s/setuptools/" + + +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() + return _do_download(version, download_base, to_dir, download_delay) try: - pkg_resources.require("setuptools>="+version); return - except pkg_resources.VersionConflict, e: + pkg_resources.require("setuptools>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] if was_imported: - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first, using 'easy_install -U setuptools'." - "\n\n(Currently using %r)" - ) % (version, e.args[0]) + 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() + return _do_download(version, download_base, to_dir, + download_delay) except pkg_resources.DistributionNotFound: - return do_download() + 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 -): + +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. + `delay` is the number of seconds to pause before an actual download + attempt. """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) + # 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: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) - src = urllib2.urlopen(url) + src = urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) + data = src.read() + dst = open(saveto, "wb") + dst.write(data) finally: - if src: src.close() - if dst: dst.close() + if src: + src.close() + if dst: + dst.close() return os.path.realpath(saveto) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) +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: - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) + directories.sort(key=operator.attrgetter('name'), reverse=True) - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - + 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()) @@ -25,9 +25,12 @@ #include <stdlib.h> #include <stdio.h> -#include <unistd.h> +#include <string.h> +#include <windows.h> +#include <tchar.h> #include <fcntl.h> -#include "windows.h" + +int child_pid=0; int fail(char *format, char *data) { /* Print error message to stderr and return 2 */ @@ -35,10 +38,6 @@ int fail(char *format, char *data) { return 2; } - - - - char *quoted(char *data) { int i, ln = strlen(data), nb; @@ -81,17 +80,18 @@ char *quoted(char *data) { char *loadable_exe(char *exename) { - HINSTANCE hPython; /* DLL handle for python executable */ + /* HINSTANCE hPython; DLL handle for python executable */ char *result; - hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - if (!hPython) return NULL; + /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!hPython) return NULL; */ /* Return the absolute filename for spawnv */ result = calloc(MAX_PATH, sizeof(char)); - if (result) GetModuleFileName(hPython, result, MAX_PATH); + strncpy(result, exename, MAX_PATH); + /*if (result) GetModuleFileNameA(hPython, result, MAX_PATH); - FreeLibrary(hPython); + FreeLibrary(hPython); */ return result; } @@ -159,8 +159,82 @@ char **parse_argv(char *cmdline, int *argc) } while (1); } +void pass_control_to_child(DWORD control_type) { + /* + * distribute-issue207 + * passes the control event to child process (Python) + */ + if (!child_pid) { + return; + } + GenerateConsoleCtrlEvent(child_pid,0); +} + +BOOL control_handler(DWORD control_type) { + /* + * distribute-issue207 + * control event handler callback function + */ + switch (control_type) { + case CTRL_C_EVENT: + pass_control_to_child(0); + break; + } + return TRUE; +} + +int create_and_wait_for_subprocess(char* command) { + /* + * distribute-issue207 + * launches child process (Python) + */ + DWORD return_value = 0; + LPSTR commandline = command; + STARTUPINFOA s_info; + PROCESS_INFORMATION p_info; + ZeroMemory(&p_info, sizeof(p_info)); + ZeroMemory(&s_info, sizeof(s_info)); + s_info.cb = sizeof(STARTUPINFO); + // set-up control handler callback funciotn + SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_handler, TRUE); + if (!CreateProcessA(NULL, commandline, NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info)) { + fprintf(stderr, "failed to create process.\n"); + return 0; + } + child_pid = p_info.dwProcessId; + // wait for Python to exit + WaitForSingleObject(p_info.hProcess, INFINITE); + if (!GetExitCodeProcess(p_info.hProcess, &return_value)) { + fprintf(stderr, "failed to get exit code from process.\n"); + return 0; + } + return return_value; +} +char* join_executable_and_args(char *executable, char **args, int argc) +{ + /* + * distribute-issue207 + * CreateProcess needs a long string of the executable and command-line arguments, + * so we need to convert it from the args that was built + */ + int len,counter; + char* cmdline; + + len=strlen(executable)+2; + for (counter=1; counter<argc; counter++) { + len+=strlen(args[counter])+1; + } + cmdline = (char*)calloc(len, sizeof(char)); + sprintf(cmdline, "%s", executable); + len=strlen(executable); + for (counter=1; counter<argc; counter++) { + sprintf(cmdline+len, " %s", args[counter]); + len+=strlen(args[counter])+1; + } + return cmdline; +} int run(int argc, char **argv, int is_gui) { @@ -172,10 +246,11 @@ int run(int argc, char **argv, int is_gui) { char **newargs, **newargsp, **parsedargs; /* argument array for exec */ char *ptr, *end; /* working pointers for string manipulation */ + char *cmdline; int i, parsedargc; /* loop counter */ /* compute script name from our .exe name*/ - GetModuleFileName(NULL, script, sizeof(script)); + GetModuleFileNameA(NULL, script, sizeof(script)); end = script + strlen(script); while( end>script && *end != '.') *end-- = '\0'; @@ -235,12 +310,18 @@ int run(int argc, char **argv, int is_gui) { return fail("Could not exec %s", ptr); /* shouldn't get here! */ } - /* We *do* need to wait for a CLI to finish, so use spawn */ - return spawnv(P_WAIT, ptr, (const char * const *)(newargs)); + /* + * distribute-issue207: using CreateProcessA instead of spawnv + */ + cmdline = join_executable_and_args(ptr, newargs, parsedargc + argc); + return create_and_wait_for_subprocess(cmdline); } - int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) { return run(__argc, __argv, GUI); } +int main(int argc, char** argv) { + return run(argc, argv, GUI); +} + diff --git a/msvc-build-launcher.cmd b/msvc-build-launcher.cmd new file mode 100644 index 00000000..3666d723 --- /dev/null +++ b/msvc-build-launcher.cmd @@ -0,0 +1,15 @@ +@echo off
+
+REM VCVARSALL may be in Program Files or Program Files (x86)
+PATH=C:\Program Files\Microsoft Visual Studio 9.0\VC;%PATH%
+PATH=C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC;%PATH%
+
+REM set up the environment to compile to x86
+call VCVARSALL x86
+cl /D "GUI=0" /D "WIN32_LEAN_AND_MEAN" launcher.c /O2 /link /MACHINE:x86 /out:setuptools/cli-32.exe
+cl /D "GUI=1" /D "WIN32_LEAN_AND_MEAN" launcher.c /O2 /link /MACHINE:x86 /out:setuptools/gui-32.exe
+
+REM now for 64-bit
+call VCVARSALL x86_amd64
+cl /D "GUI=0" /D "WIN32_LEAN_AND_MEAN" launcher.c /O2 /link /MACHINE:x64 /out:setuptools/cli-64.exe
+cl /D "GUI=1" /D "WIN32_LEAN_AND_MEAN" launcher.c /O2 /link /MACHINE:x64 /out:setuptools/gui-64.exe
\ No newline at end of file diff --git a/pkg_resources.py b/pkg_resources.py index abb90b1a..2ba0febf 100644 --- a/pkg_resources.py +++ b/pkg_resources.py @@ -14,6 +14,7 @@ method. """ import sys, os, zipimport, time, re, imp +from urlparse import urlparse, urlunparse try: frozenset @@ -21,13 +22,27 @@ except NameError: from sets import ImmutableSet as frozenset # capture these to bypass sandboxing -from os import utime, rename, unlink, mkdir +from os import utime +try: + from os import mkdir, rename, unlink + WRITE_SUPPORT = True +except ImportError: + # no write support, probably under GAE + WRITE_SUPPORT = False + from os import open as os_open from os.path import isdir, split +# Avoid try/except due to potential problems with delayed import mechanisms. +if sys.version_info >= (3, 3) and sys.implementation.name == "cpython": + import importlib._bootstrap as importlib_bootstrap +else: + importlib_bootstrap = None def _bypass_ensure_directory(name, mode=0777): # Sandbox-bypassing version of ensure_directory() + if not WRITE_SUPPORT: + raise IOError('"os.mkdir" not supported on this platform.') dirname, filename = split(name) if dirname and filename and not isdir(dirname): _bypass_ensure_directory(dirname) @@ -73,8 +88,6 @@ _sget_none = _sset_none = lambda *args: None - - def get_supported_platform(): """Return this platform's maximum compatible version. @@ -159,7 +172,8 @@ __all__ = [ ] class ResolutionError(Exception): """Abstract base for dependency resolution errors""" - def __repr__(self): return self.__class__.__name__+repr(self.args) + def __repr__(self): + return self.__class__.__name__+repr(self.args) class VersionConflict(ResolutionError): """An already-installed version conflicts with the requested version""" @@ -170,6 +184,7 @@ class DistributionNotFound(ResolutionError): class UnknownExtra(ResolutionError): """Distribution doesn't have an "extra feature" of the given name""" _provider_factories = {} + PY_MAJOR = sys.version[:3] EGG_DIST = 3 BINARY_DIST = 2 @@ -200,8 +215,19 @@ def get_provider(moduleOrReq): def _macosx_vers(_cache=[]): if not _cache: - from platform import mac_ver - _cache.append(mac_ver()[0].split('.')) + import platform + version = platform.mac_ver()[0] + # fallback for MacPorts + if version == '': + import plistlib + plist = '/System/Library/CoreServices/SystemVersion.plist' + if os.path.exists(plist): + if hasattr(plistlib, 'readPlist'): + plist_content = plistlib.readPlist(plist) + if 'ProductVersion' in plist_content: + version = plist_content['ProductVersion'] + + _cache.append(version.split('.')) return _cache[0] def _macosx_arch(machine): @@ -213,7 +239,11 @@ def get_build_platform(): XXX Currently this is the same as ``distutils.util.get_platform()``, but it needs some hacks for Linux and Mac OS X. """ - from distutils.util import get_platform + try: + from distutils.util import get_platform + except ImportError: + from sysconfig import get_platform + plat = get_platform() if sys.platform == "darwin" and not plat.startswith('macosx-'): try: @@ -237,8 +267,6 @@ get_platform = get_build_platform # XXX backward compat - - def compatible_platforms(provided,required): """Can code for the `provided` platform run on the `required` platform? @@ -353,15 +381,6 @@ class IMetadataProvider: - - - - - - - - - class IResourceProvider(IMetadataProvider): """An object that provides access to package resources""" @@ -493,6 +512,10 @@ class WorkingSet(object): """ seen = {} for item in self.entries: + if item not in self.entry_keys: + # workaround a cache issue + continue + for key in self.entry_keys[item]: if key not in seen: seen[key]=1 @@ -557,7 +580,13 @@ class WorkingSet(object): env = Environment(self.entries) dist = best[req.key] = env.best_match(req, self, installer) if dist is None: - raise DistributionNotFound(req) # XXX put more info here + #msg = ("The '%s' distribution was not found on this " + # "system, and is required by this application.") + #raise DistributionNotFound(msg % req) + + # unfortunately, zc.buildout uses a str(err) + # to get the name of the distribution here.. + raise DistributionNotFound(req) to_activate.append(dist) if dist not in req: # Oops, the "best" so far conflicts with a dependency @@ -578,7 +607,7 @@ class WorkingSet(object): Environment(plugin_dirlist) ) map(working_set.add, distributions) # add plugins+libs to sys.path - print "Couldn't load", errors # display errors + print 'Could not load', errors # display errors The `plugin_env` should be an ``Environment`` instance that contains only distributions that are in the project's "plugin directory" or @@ -1167,10 +1196,16 @@ class NullProvider: def has_metadata(self, name): return self.egg_info and self._has(self._fn(self.egg_info,name)) - def get_metadata(self, name): - if not self.egg_info: - return "" - return self._get(self._fn(self.egg_info,name)) + if sys.version_info <= (3,): + def get_metadata(self, name): + if not self.egg_info: + return "" + return self._get(self._fn(self.egg_info,name)) + else: + def get_metadata(self, name): + if not self.egg_info: + return "" + return self._get(self._fn(self.egg_info,name)).decode("utf-8") def get_metadata_lines(self, name): return yield_lines(self.get_metadata(name)) @@ -1288,6 +1323,9 @@ class DefaultProvider(EggProvider): register_loader_type(type(None), DefaultProvider) +if importlib_bootstrap is not None: + register_loader_type(importlib_bootstrap.SourceFileLoader, DefaultProvider) + class EmptyProvider(NullProvider): """Provider that returns nothing for all requests""" @@ -1364,6 +1402,10 @@ class ZipProvider(EggProvider): timestamp = time.mktime(date_time) try: + if not WRITE_SUPPORT: + raise IOError('"os.rename" and "os.unlink" are not supported ' + 'on this platform') + real_path = manager.get_cache_path( self.egg_name, self._parts(zip_path) ) @@ -1489,7 +1531,10 @@ class FileMetadata(EmptyProvider): def get_metadata(self,name): if name=='PKG-INFO': - return open(self.path,'rU').read() + f = open(self.path,'rU') + metadata = f.read() + f.close() + return metadata raise KeyError("No metadata except PKG-INFO is available") def get_metadata_lines(self,name): @@ -1694,7 +1739,7 @@ def find_on_path(importer, path_item, only=False): # scan for .egg and .egg-info in directory for entry in os.listdir(path_item): lower = entry.lower() - if lower.endswith('.egg-info'): + if lower.endswith('.egg-info') or lower.endswith('.dist-info'): fullpath = os.path.join(path_item, entry) if os.path.isdir(fullpath): # egg-info directory, allow getting metadata @@ -1708,15 +1753,24 @@ def find_on_path(importer, path_item, only=False): for dist in find_distributions(os.path.join(path_item, entry)): yield dist elif not only and lower.endswith('.egg-link'): - for line in file(os.path.join(path_item, entry)): + entry_file = open(os.path.join(path_item, entry)) + try: + entry_lines = entry_file.readlines() + finally: + entry_file.close() + for line in entry_lines: if not line.strip(): continue for item in find_distributions(os.path.join(path_item,line.rstrip())): yield item break register_finder(ImpWrapper,find_on_path) -_declare_state('dict', _namespace_handlers = {}) -_declare_state('dict', _namespace_packages = {}) +if importlib_bootstrap is not None: + register_finder(importlib_bootstrap.FileFinder, find_on_path) + +_declare_state('dict', _namespace_handlers={}) +_declare_state('dict', _namespace_packages={}) + def register_namespace_handler(importer_type, namespace_handler): """Register `namespace_handler` to declare namespace packages @@ -1768,7 +1822,8 @@ def declare_namespace(packageName): if '.' in packageName: parent = '.'.join(packageName.split('.')[:-1]) declare_namespace(parent) - __import__(parent) + if parent not in _namespace_packages: + __import__(parent) try: path = sys.modules[parent].__path__ except AttributeError: @@ -1812,6 +1867,9 @@ def file_ns_handler(importer, path_item, packageName, module): register_namespace_handler(ImpWrapper,file_ns_handler) register_namespace_handler(zipimport.zipimporter,file_ns_handler) +if importlib_bootstrap is not None: + register_namespace_handler(importlib_bootstrap.FileFinder, file_ns_handler) + def null_ns_handler(importer, path_item, packageName, module): return None @@ -2040,12 +2098,19 @@ class EntryPoint(object): parse_map = classmethod(parse_map) - - +def _remove_md5_fragment(location): + if not location: + return '' + parsed = urlparse(location) + if parsed[-1].startswith('md5='): + return urlunparse(parsed[:-1] + ('',)) + return location class Distribution(object): """Wrap an actual or potential sys.path entry w/metadata""" + PKG_INFO = 'PKG-INFO' + def __init__(self, location=None, metadata=None, project_name=None, version=None, py_version=PY_MAJOR, platform=None, precedence = EGG_DIST @@ -2063,27 +2128,47 @@ class Distribution(object): def from_location(cls,location,basename,metadata=None,**kw): project_name, version, py_version, platform = [None]*4 basename, ext = os.path.splitext(basename) - if ext.lower() in (".egg",".egg-info"): + if ext.lower() in _distributionImpl: + # .dist-info gets much metadata differently match = EGG_NAME(basename) if match: project_name, version, py_version, platform = match.group( 'name','ver','pyver','plat' ) + cls = _distributionImpl[ext.lower()] return cls( location, metadata, project_name=project_name, version=version, py_version=py_version, platform=platform, **kw ) from_location = classmethod(from_location) + hashcmp = property( lambda self: ( - getattr(self,'parsed_version',()), self.precedence, self.key, - -len(self.location or ''), self.location, self.py_version, + getattr(self,'parsed_version',()), + self.precedence, + self.key, + _remove_md5_fragment(self.location), + self.py_version, self.platform ) ) - def __cmp__(self, other): return cmp(self.hashcmp, other) def __hash__(self): return hash(self.hashcmp) + def __lt__(self, other): + return self.hashcmp < other.hashcmp + def __le__(self, other): + return self.hashcmp <= other.hashcmp + def __gt__(self, other): + return self.hashcmp > other.hashcmp + def __ge__(self, other): + return self.hashcmp >= other.hashcmp + def __eq__(self, other): + if not isinstance(other, self.__class__): + # It's not a Distribution, so they are not equal + return False + return self.hashcmp == other.hashcmp + def __ne__(self, other): + return not self == other # These properties have to be lazy so that we don't have to load any # metadata until/unless it's actually needed. (i.e., some distributions @@ -2113,13 +2198,13 @@ class Distribution(object): try: return self._version except AttributeError: - for line in self._get_metadata('PKG-INFO'): + for line in self._get_metadata(self.PKG_INFO): if line.lower().startswith('version:'): self._version = safe_version(line.split(':',1)[1].strip()) return self._version else: raise ValueError( - "Missing 'Version:' header and/or PKG-INFO file", self + "Missing 'Version:' header and/or %s file" % self.PKG_INFO, self ) version = property(version) @@ -2301,9 +2386,11 @@ class Distribution(object): or modname in _namespace_packages ): continue - + if modname in ('pkg_resources', 'setuptools', 'site'): + continue fn = getattr(sys.modules[modname], '__file__', None) - if fn and (normalize_path(fn).startswith(loc) or fn.startswith(loc)): + if fn and (normalize_path(fn).startswith(loc) or + fn.startswith(self.location)): continue issue_warning( "Module %s was already imported from %s, but %s is being added" @@ -2337,6 +2424,74 @@ class Distribution(object): extras = property(extras) +class DistInfoDistribution(Distribution): + """Wrap an actual or potential sys.path entry w/metadata, .dist-info style""" + PKG_INFO = 'METADATA' + EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") + + @property + def _parsed_pkg_info(self): + """Parse and cache metadata""" + try: + return self._pkg_info + except AttributeError: + from email.parser import Parser + self._pkg_info = Parser().parsestr(self.get_metadata(self.PKG_INFO)) + return self._pkg_info + + @property + def _dep_map(self): + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._compute_dependencies() + return self.__dep_map + + def _preparse_requirement(self, requires_dist): + """Convert 'Foobar (1); baz' to ('Foobar ==1', 'baz') + Split environment marker, add == prefix to version specifiers as + necessary, and remove parenthesis. + """ + parts = requires_dist.split(';', 1) + [''] + distvers = parts[0].strip() + mark = parts[1].strip() + distvers = re.sub(self.EQEQ, r"\1==\2\3", distvers) + distvers = distvers.replace('(', '').replace(')', '') + return (distvers, mark) + + def _compute_dependencies(self): + """Recompute this distribution's dependencies.""" + from _markerlib import compile as compile_marker + dm = self.__dep_map = {None: []} + + reqs = [] + # Including any condition expressions + for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: + distvers, mark = self._preparse_requirement(req) + parsed = parse_requirements(distvers).next() + parsed.marker_fn = compile_marker(mark) + reqs.append(parsed) + + def reqs_for_extra(extra): + for req in reqs: + if req.marker_fn(override={'extra':extra}): + yield req + + common = frozenset(reqs_for_extra(None)) + dm[None].extend(common) + + for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: + extra = safe_extra(extra.strip()) + dm[extra] = list(frozenset(reqs_for_extra(extra)) - common) + + return dm + + +_distributionImpl = {'.egg': Distribution, + '.egg-info': Distribution, + '.dist-info': DistInfoDistribution } + + def issue_warning(*args,**kw): level = 1 g = globals() @@ -2485,8 +2640,9 @@ class Requirement: elif isinstance(item,basestring): item = parse_version(item) last = None + compare = lambda a, b: (a > b) - (a < b) # -1, 0, 1 for parsed,trans,op,ver in self.index: - action = trans[cmp(item,parsed)] + action = trans[compare(item,parsed)] # Indexing: 0, 1, -1 if action=='F': return False elif action=='T': return True elif action=='+': last = True diff --git a/release.py b/release.py new file mode 100644 index 00000000..46720a8e --- /dev/null +++ b/release.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python + +""" +Script to fully automate the release process. Requires Python 2.6+ +with sphinx installed and the 'hg' command on the path. +""" + +from __future__ import print_function + +import subprocess +import shutil +import os +import sys +import urllib2 +import getpass +import collections + +try: + import keyring +except Exception: + pass + +VERSION = '0.6.36' + +def get_next_version(): + digits = map(int, VERSION.split('.')) + digits[-1] += 1 + return '.'.join(map(str, digits)) + +NEXT_VERSION = get_next_version() + +files_with_versions = ('docs/conf.py', 'setup.py', 'release.py', + 'README.txt', 'distribute_setup.py') + +def get_repo_name(): + """ + Get the repo name from the hgrc default path. + """ + default = subprocess.check_output('hg paths default').strip() + parts = default.split('/') + if parts[-1] == '': + parts.pop() + return '/'.join(parts[-2:]) + +def get_mercurial_creds(system='https://bitbucket.org', username=None): + """ + Return named tuple of username,password in much the same way that + Mercurial would (from the keyring). + """ + # todo: consider getting this from .hgrc + username = username or getpass.getuser() + keyring_username = '@@'.join((username, system)) + system = '@'.join((keyring_username, 'Mercurial')) + password = ( + keyring.get_password(system, keyring_username) + if 'keyring' in globals() + else None + ) + if not password: + password = getpass.getpass() + Credential = collections.namedtuple('Credential', 'username password') + return Credential(username, password) + +def add_milestone_and_version(version=NEXT_VERSION): + auth = 'Basic ' + ':'.join(get_mercurial_creds()).encode('base64').strip() + headers = { + 'Authorization': auth, + } + base = 'https://api.bitbucket.org' + for type in 'milestones', 'versions': + url = (base + '/1.0/repositories/{repo}/issues/{type}' + .format(repo = get_repo_name(), type=type)) + req = urllib2.Request(url = url, headers = headers, + data='name='+version) + try: + urllib2.urlopen(req) + except urllib2.HTTPError as e: + print(e.fp.read()) + +def bump_versions(): + list(map(bump_version, files_with_versions)) + +def bump_version(filename): + with open(filename, 'rb') as f: + lines = [line.replace(VERSION, NEXT_VERSION) for line in f] + with open(filename, 'wb') as f: + f.writelines(lines) + +def do_release(): + assert all(map(os.path.exists, files_with_versions)), ( + "Expected file(s) missing") + + assert has_sphinx(), "You must have Sphinx installed to release" + + res = raw_input('Have you read through the SCM changelog and ' + 'confirmed the changelog is current for releasing {VERSION}? ' + .format(**globals())) + if not res.lower().startswith('y'): + print("Please do that") + raise SystemExit(1) + + print("Travis-CI tests: http://travis-ci.org/#!/jaraco/distribute") + res = raw_input('Have you or has someone verified that the tests ' + 'pass on this revision? ') + if not res.lower().startswith('y'): + print("Please do that") + raise SystemExit(2) + + subprocess.check_call(['hg', 'tag', VERSION]) + + subprocess.check_call(['hg', 'update', VERSION]) + + has_docs = build_docs() + if os.path.isdir('./dist'): + shutil.rmtree('./dist') + cmd = [sys.executable, 'setup.py', '-q', 'egg_info', '-RD', '-b', '', + 'sdist', 'register', 'upload'] + if has_docs: + cmd.append('upload_docs') + 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: + devnull = open(os.path.devnull, 'wb') + subprocess.Popen(['sphinx-build', '--version'], stdout=devnull, + stderr=subprocess.STDOUT).wait() + except Exception: + return False + return True + +def build_docs(): + if not os.path.isdir('docs'): + return + if os.path.isdir('docs/build'): + shutil.rmtree('docs/build') + subprocess.check_call([ + 'sphinx-build', + '-b', 'html', + '-d', 'build/doctrees', + '.', + 'build/html', + ], + 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.") + +if __name__ == '__main__': + do_release() diff --git a/release.sh b/release.sh deleted file mode 100755 index c53357d9..00000000 --- a/release.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -# This script is for PJE's working environment only, to upload -# releases to PyPI, telecommunity, eby-sarna SVN, update local -# project checkouts, etc. -# -# If your initials aren't PJE, don't run it. :) -# - -export VERSION="0.6c9" - -python2.3 setup.py -q release source --target-version=2.3 upload && \ -python2.4 setup.py -q release binary --target-version=2.4 upload && \ -python2.5 setup.py -q release binary --target-version=2.5 upload && \ -python2.3 ez_setup.py --md5update dist/setuptools-$VERSION*-py2.?.egg && \ - cp ez_setup.py virtual-python.py ~/distrib/ && \ - cp ez_setup.py ~/projects/ez_setup/__init__.py && \ - svn ci -m "Update ez_setup for setuptools $VERSION" \ - ~/projects/ez_setup/__init__.py #&& \ - #svn up ~/projects/*/ez_setup - -# update wiki pages from EasyInstall.txt, setuptools.txt, & -# pkg_resources.txt -python2.5 setup.py wikiup -c "Released version: $VERSION" @@ -4,13 +4,16 @@ tag_svn_revision = 1 [aliases] release = egg_info -RDb '' -source = bdist_rpm register binary -binary = bdist_egg bdist_wininst +source = register sdist binary +binary = bdist_egg upload --show-response -[upload] -show_response = 1 +[build_sphinx] +source-dir = docs/ +build-dir = docs/build +all_files = 1 -[bdist_rpm] -source_only = 1 -doc_files = setuptools.txt EasyInstall.txt pkg_resources.txt -requires = python-devel +[upload_docs] +upload-dir = docs/build/html + +[sdist] +formats=gztar @@ -1,30 +1,148 @@ #!/usr/bin/env python """Distutils setup file, used to install or test 'setuptools'""" +import sys +import os +import textwrap +import re + +# Allow to run setup.py from another directory. +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +src_root = None +if sys.version_info >= (3,): + tmp_src = os.path.join("build", "src") + from distutils.filelist import FileList + from distutils import dir_util, file_util, util, log + log.set_verbosity(1) + fl = FileList() + manifest_file = open("MANIFEST.in") + for line in manifest_file: + fl.process_template_line(line) + manifest_file.close() + dir_util.create_tree(tmp_src, fl.files) + outfiles_2to3 = [] + 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: + outfiles_2to3.append(outf) + if copied and outf.endswith('api_tests.txt'): + # XXX support this in distutils as well + from lib2to3.main import main + main('lib2to3.fixes', ['-wd', os.path.join(tmp_src, 'tests', 'api_tests.txt')]) + + util.run_2to3(outfiles_2to3) + + # arrange setup to use the copy + sys.path.insert(0, os.path.abspath(tmp_src)) + src_root = tmp_src from distutils.util import convert_path d = {} -execfile(convert_path('setuptools/command/__init__.py'), d) +init_path = convert_path('setuptools/command/__init__.py') +init_file = open(init_path) +exec(init_file.read(), d) +init_file.close() SETUP_COMMANDS = d['__all__'] -VERSION = "0.6c9" +VERSION = "0.6.36" from setuptools import setup, find_packages -import sys +from setuptools.command.build_py import build_py as _build_py +from setuptools.command.test import test as _test + scripts = [] -setup( +console_scripts = ["easy_install = setuptools.command.easy_install:main"] +if os.environ.get("DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT") is None: + console_scripts.append("easy_install-%s = setuptools.command.easy_install:main" % sys.version[:3]) + +# specific command that is used to generate windows .exe files +class build_py(_build_py): + def build_package_data(self): + """Copy data files into build directory""" + lastdir = None + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + srcfile = os.path.join(src_dir, filename) + outf, copied = self.copy_file(srcfile, target) + srcfile = os.path.abspath(srcfile) + + # avoid a bootstrapping issue with easy_install -U (when the + # previous version doesn't have convert_2to3_doctests) + if not hasattr(self.distribution, 'convert_2to3_doctests'): + continue + + if copied and srcfile in self.distribution.convert_2to3_doctests: + self.__doctests_2to3.append(outf) + +class test(_test): + """Specific test class to avoid rewriting the entry_points.txt""" + def run(self): + entry_points = os.path.join('setuptools.egg-info', 'entry_points.txt') + + if not os.path.exists(entry_points): + _test.run(self) + return # even though _test.run will raise SystemExit + + f = open(entry_points) + + # running the test + try: + ep_content = f.read() + finally: + f.close() + + try: + _test.run(self) + finally: + # restoring the file + f = open(entry_points, 'w') + try: + f.write(ep_content) + finally: + f.close() + + +# return contents of reStructureText file with linked issue references +def _linkified(rst_path): + bitroot = 'http://bitbucket.org/tarek/distribute' + revision = re.compile(r'\b(issue\s+#?\d+)\b', re.M | re.I) + + rst_file = open(rst_path) + rst_content = rst_file.read() + rst_file.close() + + anchors = revision.findall(rst_content) # ['Issue #43', ...] + anchors = sorted(set(anchors)) + rst_content = revision.sub(r'`\1`_', rst_content) + rst_content += "\n" + for x in anchors: + issue = re.findall(r'\d+', x)[0] + rst_content += '.. _`%s`: %s/issue/%s\n' % (x, bitroot, issue) + rst_content += "\n" + return rst_content + +readme_file = open('README.txt') +long_description = readme_file.read() + _linkified('CHANGES.txt') +readme_file.close() + +dist = setup( name="setuptools", version=VERSION, - description="Download, build, install, upgrade, and uninstall Python " - "packages -- easily!", - author="Phillip J. Eby", + description="Easily download, build, install, upgrade, and uninstall " + "Python packages", + author="The fellowship of the packaging", author_email="distutils-sig@python.org", license="PSF or ZPL", - long_description = open('README.txt').read(), + long_description = long_description, keywords = "CPAN PyPI distutils eggs package management", url = "http://pypi.python.org/pypi/setuptools", test_suite = 'setuptools.tests', + src_root = src_root, packages = find_packages(), package_data = {'setuptools':['*.exe']}, @@ -32,6 +150,7 @@ setup( zip_safe = (sys.version>="2.5"), # <2.5 needs unzipped for -m to work + cmdclass = {'test': test}, entry_points = { "distutils.commands" : [ @@ -40,19 +159,24 @@ setup( ], "distutils.setup_keywords": [ - "eager_resources = setuptools.dist:assert_string_list", - "namespace_packages = setuptools.dist:check_nsp", - "extras_require = setuptools.dist:check_extras", - "install_requires = setuptools.dist:check_requirements", - "tests_require = setuptools.dist:check_requirements", - "entry_points = setuptools.dist:check_entry_points", - "test_suite = setuptools.dist:check_test_suite", - "zip_safe = setuptools.dist:assert_bool", - "package_data = setuptools.dist:check_package_data", - "exclude_package_data = setuptools.dist:check_package_data", - "include_package_data = setuptools.dist:assert_bool", - "dependency_links = setuptools.dist:assert_string_list", - "test_loader = setuptools.dist:check_importable", + "eager_resources = setuptools.dist:assert_string_list", + "namespace_packages = setuptools.dist:check_nsp", + "extras_require = setuptools.dist:check_extras", + "install_requires = setuptools.dist:check_requirements", + "tests_require = setuptools.dist:check_requirements", + "entry_points = setuptools.dist:check_entry_points", + "test_suite = setuptools.dist:check_test_suite", + "zip_safe = setuptools.dist:assert_bool", + "package_data = setuptools.dist:check_package_data", + "exclude_package_data = setuptools.dist:check_package_data", + "include_package_data = setuptools.dist:assert_bool", + "packages = setuptools.dist:check_packages", + "dependency_links = setuptools.dist:assert_string_list", + "test_loader = setuptools.dist:check_importable", + "use_2to3 = setuptools.dist:assert_bool", + "convert_2to3_doctests = setuptools.dist:assert_string_list", + "use_2to3_fixers = setuptools.dist:assert_string_list", + "use_2to3_exclude_fixers = setuptools.dist:assert_string_list", ], "egg_info.writers": [ @@ -66,11 +190,7 @@ setup( "dependency_links.txt = setuptools.command.egg_info:overwrite_arg", ], - "console_scripts": [ - "easy_install = setuptools.command.easy_install:main", - "easy_install-%s = setuptools.command.easy_install:main" - % sys.version[:3] - ], + "console_scripts": console_scripts, "setuptools.file_finders": ["svn_cvs = setuptools.command.sdist:_default_revctrl"], @@ -80,44 +200,24 @@ setup( }, - classifiers = [f.strip() for f in """ - Development Status :: 3 - Alpha - Intended Audience :: Developers - License :: OSI Approved :: Python Software Foundation License - License :: OSI Approved :: Zope Public License - Operating System :: OS Independent - Programming Language :: Python - Topic :: Software Development :: Libraries :: Python Modules - Topic :: System :: Archiving :: Packaging - Topic :: System :: Systems Administration - Topic :: Utilities""".splitlines() if f.strip()], + classifiers = textwrap.dedent(""" + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Python Software Foundation License + License :: OSI Approved :: Zope Public License + Operating System :: OS Independent + Programming Language :: Python :: 2.4 + Programming Language :: Python :: 2.5 + Programming Language :: Python :: 2.6 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.1 + Programming Language :: Python :: 3.2 + Programming Language :: Python :: 3.3 + Topic :: Software Development :: Libraries :: Python Modules + Topic :: System :: Archiving :: Packaging + Topic :: System :: Systems Administration + Topic :: Utilities + """).strip().splitlines(), scripts = scripts, - - # uncomment for testing - # setup_requires = ['setuptools>=0.6a0'], ) - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/setuptools.egg-info/entry_points.txt b/setuptools.egg-info/entry_points.txt index f7367e0d..663882d6 100755..100644 --- a/setuptools.egg-info/entry_points.txt +++ b/setuptools.egg-info/entry_points.txt @@ -7,6 +7,7 @@ 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 @@ -31,7 +32,7 @@ depends.txt = setuptools.command.egg_info:warn_depends_obsolete [console_scripts] easy_install = setuptools.command.easy_install:main -easy_install-2.5 = setuptools.command.easy_install:main +easy_install-2.7 = setuptools.command.easy_install:main [setuptools.file_finders] svn_cvs = setuptools.command.sdist:_default_revctrl @@ -40,8 +41,11 @@ svn_cvs = setuptools.command.sdist:_default_revctrl 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 @@ -49,6 +53,8 @@ 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] diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 56cbf767..08de4349 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -5,9 +5,10 @@ import distutils.core, setuptools.command from setuptools.depends import Require from distutils.core import Command as _Command from distutils.util import convert_path -import os.path +import os +import sys -__version__ = '0.6c9' +__version__ = '0.6' __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'find_packages' @@ -15,6 +16,12 @@ __all__ = [ bootstrap_install_from = None +# If we run 2to3 on .py files, should we also convert docstrings? +# Default: yes; assume that we can detect doctests reliably +run_2to3_on_doctests = True +# Standard package names for fixer packages +lib2to3_fixer_packages = ['lib2to3.fixes'] + def find_packages(where='.', exclude=()): """Return a list all Python packages found within directory 'where' @@ -40,7 +47,7 @@ def find_packages(where='.', exclude=()): return out setup = distutils.core.setup - + _Command = _get_unpatched(_Command) class Command(_Command): @@ -53,7 +60,7 @@ class Command(_Command): _Command.__init__(self,dist) for k,v in kw.items(): setattr(self,k,v) - + def reinitialize_command(self, command, reinit_subcommands=0, **kw): cmd = _Command.reinitialize_command(self, command, reinit_subcommands) for k,v in kw.items(): @@ -79,4 +86,9 @@ def findall(dir = os.curdir): import distutils.filelist distutils.filelist.findall = findall # fix findall bug in distutils. - +# sys.dont_write_bytecode was introduced in Python 2.6. +if ((hasattr(sys, "dont_write_bytecode") and sys.dont_write_bytecode) or + (not hasattr(sys, "dont_write_bytecode") and os.environ.get("PYTHONDONTWRITEBYTECODE"))): + _dont_write_bytecode = True +else: + _dont_write_bytecode = False diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py index 511f05ad..e22b25c0 100755 --- a/setuptools/archive_util.py +++ b/setuptools/archive_util.py @@ -158,6 +158,9 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): finally: f.close() del data + unix_attributes = info.external_attr >> 16 + if unix_attributes: + os.chmod(target, unix_attributes) finally: z.close() @@ -180,16 +183,22 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): try: tarobj.chown = lambda *args: None # don't do any chowning! for member in tarobj: - if member.isfile() or member.isdir(): - name = member.name - # don't extract absolute paths or ones with .. in them - if not name.startswith('/') and '..' not in name: - dst = os.path.join(extract_dir, *name.split('/')) - dst = progress_filter(name, dst) - if dst: - if dst.endswith(os.sep): - dst = dst[:-1] - tarobj._extract_member(member,dst) # XXX Ugh + name = member.name + # don't extract absolute paths or ones with .. in them + if not name.startswith('/') and '..' not in name: + 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) return True finally: tarobj.close() diff --git a/setuptools/cli-32.exe b/setuptools/cli-32.exe Binary files differnew file mode 100644 index 00000000..9b7717b7 --- /dev/null +++ b/setuptools/cli-32.exe diff --git a/setuptools/cli-64.exe b/setuptools/cli-64.exe Binary files differnew file mode 100644 index 00000000..265585af --- /dev/null +++ b/setuptools/cli-64.exe diff --git a/setuptools/cli.exe b/setuptools/cli.exe Binary files differindex 3173b2b2..9b7717b7 100755..100644 --- a/setuptools/cli.exe +++ b/setuptools/cli.exe diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index f898822b..b063fa19 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -2,10 +2,12 @@ __all__ = [ 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', 'sdist', 'setopt', 'test', 'upload', 'install_egg_info', 'install_scripts', - 'register', 'bdist_wininst', + 'register', 'bdist_wininst', 'upload_docs', ] +from setuptools.command import install_scripts import sys + if sys.version>='2.5': # In Python 2.5 and above, distutils includes its own upload command __all__.remove('upload') diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 9e852a3f..17fae984 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -6,7 +6,12 @@ Build .egg distributions""" import sys, os, marshal from setuptools import Command from distutils.dir_util import remove_tree, mkpath -from distutils.sysconfig import get_python_version, get_python_lib +try: + from distutils.sysconfig import get_python_version, get_python_lib +except ImportError: + from sysconfig import get_python_version + from distutils.sysconfig import get_python_lib + from distutils import log from distutils.errors import DistutilsSetupError from pkg_resources import get_build_platform, Distribution, ensure_directory @@ -327,7 +332,11 @@ class bdist_egg(Command): def copy_metadata_to(self, target_dir): - prefix = os.path.join(self.egg_info,'') + "Copy metadata (egg info) to the target_dir" + # normalize the path (so that a forward-slash in egg_info will + # match using startswith below) + norm_egg_info = os.path.normpath(self.egg_info) + prefix = os.path.join(norm_egg_info,'') for path in self.ei_cmd.filelist.files: if path.startswith(prefix): target = os.path.join(target_dir, path[len(prefix):]) @@ -401,7 +410,7 @@ def write_safety_flag(egg_dir, safe): if safe is None or bool(safe)<>flag: os.unlink(fn) elif safe is not None and bool(safe)==flag: - f=open(fn,'wb'); f.write('\n'); f.close() + f=open(fn,'wt'); f.write('\n'); f.close() safety_flags = { True: 'zip-safe', @@ -416,8 +425,12 @@ def scan_module(egg_dir, base, name, stubs): return True # Extension module pkg = base[len(egg_dir)+1:].replace(os.sep,'.') module = pkg+(pkg and '.' or '')+os.path.splitext(name)[0] - f = open(filename,'rb'); f.read(8) # skip magic & date - code = marshal.load(f); f.close() + if sys.version_info < (3, 3): + skip = 8 # skip magic & date + else: + skip = 12 # skip magic & date & file size + f = open(filename,'rb'); f.read(skip) + code = marshal.load(f); f.close() safe = True symbols = dict.fromkeys(iter_symbols(code)) for bad in ['__file__', '__path__']: @@ -525,9 +538,11 @@ def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None, compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)] if not dry_run: z = zipfile.ZipFile(zip_filename, mode, compression=compression) - os.path.walk(base_dir, visit, z) + for dirname, dirs, files in os.walk(base_dir): + visit(z, dirname, files) z.close() else: - os.path.walk(base_dir, visit, None) + for dirname, dirs, files in os.walk(base_dir): + visit(None, dirname, files) return zip_filename # diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index c0aaa8e8..4a94572c 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -82,6 +82,8 @@ 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) @@ -111,6 +113,11 @@ class build_ext(_build_ext): for ext in self.extensions: fullname = ext._full_name self.ext_map[fullname] = ext + + # distutils 3.1 will also ask for module names + # XXX what to do with conflicts? + self.ext_map[fullname.split('.')[-1]] = ext + ltd = ext._links_to_dynamic = \ self.shlibs and self.links_to_dynamic(ext) or False ext._needs_stub = ltd and use_stubs and not isinstance(ext,Library) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 79570bc2..8751acd4 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -3,7 +3,64 @@ from distutils.command.build_py import build_py as _build_py from distutils.util import convert_path from glob import glob -class build_py(_build_py): +try: + from distutils.util import Mixin2to3 as _Mixin2to3 + # add support for converting doctests that is missing in 3.1 distutils + from distutils import log + from lib2to3.refactor import RefactoringTool, get_fixers_from_package + import setuptools + class DistutilsRefactoringTool(RefactoringTool): + def log_error(self, msg, *args, **kw): + log.error(msg, *args) + + def log_message(self, msg, *args): + log.info(msg, *args) + + def log_debug(self, msg, *args): + log.debug(msg, *args) + + class Mixin2to3(_Mixin2to3): + def run_2to3(self, files, doctests = False): + # See of the distribution option has been set, otherwise check the + # setuptools default. + if self.distribution.use_2to3 is not True: + return + if not files: + return + log.info("Fixing "+" ".join(files)) + self.__build_fixer_names() + self.__exclude_fixers() + if doctests: + if setuptools.run_2to3_on_doctests: + r = DistutilsRefactoringTool(self.fixer_names) + r.refactor(files, write=True, doctests_only=True) + else: + _Mixin2to3.run_2to3(self, files) + + def __build_fixer_names(self): + if self.fixer_names: return + self.fixer_names = [] + for p in setuptools.lib2to3_fixer_packages: + self.fixer_names.extend(get_fixers_from_package(p)) + if self.distribution.use_2to3_fixers is not None: + for p in self.distribution.use_2to3_fixers: + self.fixer_names.extend(get_fixers_from_package(p)) + + def __exclude_fixers(self): + excluded_fixers = getattr(self, 'exclude_fixers', []) + if self.distribution.use_2to3_exclude_fixers is not None: + excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers) + for fixer_name in excluded_fixers: + if fixer_name in self.fixer_names: + self.fixer_names.remove(fixer_name) + +except ImportError: + class Mixin2to3: + def run_2to3(self, files, doctests=True): + # Nothing done in 2.x + pass + +class build_py(_build_py, Mixin2to3): """Enhanced 'build_py' command that includes data files with packages The data files are specified via a 'package_data' argument to 'setup()'. @@ -17,6 +74,8 @@ class build_py(_build_py): self.package_data = self.distribution.package_data self.exclude_package_data = self.distribution.exclude_package_data or {} if 'data_files' in self.__dict__: del self.__dict__['data_files'] + self.__updated_files = [] + self.__doctests_2to3 = [] def run(self): """Build modules, packages, and copy data files to build directory""" @@ -30,6 +89,10 @@ class build_py(_build_py): self.build_packages() self.build_package_data() + self.run_2to3(self.__updated_files, False) + self.run_2to3(self.__updated_files, True) + self.run_2to3(self.__doctests_2to3, True) + # Only compile actual .py files, using our base class' idea of what our # output files are. self.byte_compile(_build_py.get_outputs(self, include_bytecode=0)) @@ -39,6 +102,12 @@ class build_py(_build_py): self.data_files = files = self._get_data_files(); return files return _build_py.__getattr__(self,attr) + def build_module(self, module, module_file, package): + outfile, copied = _build_py.build_module(self, module, module_file, package) + if copied: + self.__updated_files.append(outfile) + return outfile, copied + def _get_data_files(self): """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" self.analyze_manifest() @@ -77,7 +146,11 @@ class build_py(_build_py): for filename in filenames: target = os.path.join(build_dir, filename) self.mkpath(os.path.dirname(target)) - self.copy_file(os.path.join(src_dir, filename), target) + srcfile = os.path.join(src_dir, filename) + outf, copied = self.copy_file(srcfile, target) + srcfile = os.path.abspath(srcfile) + if copied and srcfile in self.distribution.convert_2to3_doctests: + self.__doctests_2to3.append(outf) def analyze_manifest(self): @@ -140,8 +213,8 @@ class build_py(_build_py): else: return init_py - f = open(init_py,'rU') - if 'declare_namespace' not in f.read(): + f = open(init_py,'rbU') + if 'declare_namespace'.encode() not in f.read(): from distutils import log log.warn( "WARNING: %s is a namespace package, but its __init__.py does\n" @@ -157,9 +230,11 @@ class build_py(_build_py): _build_py.initialize_options(self) - - - + def get_package_dir(self, package): + res = _build_py.get_package_dir(self, package) + if self.distribution.src_root is not None: + return os.path.join(self.distribution.src_root, res) + return res def exclude_data_files(self, package, src_dir, files): diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index f128b803..1d500040 100755 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -1,9 +1,9 @@ from setuptools.command.easy_install import easy_install -from distutils.util import convert_path +from distutils.util import convert_path, subst_vars from pkg_resources import Distribution, PathMetadata, normalize_path from distutils import log -from distutils.errors import * -import sys, os, setuptools, glob +from distutils.errors import DistutilsError, DistutilsOptionError +import os, sys, setuptools, glob class develop(easy_install): """Set up package for development""" @@ -36,9 +36,6 @@ class develop(easy_install): - - - def finalize_options(self): ei = self.get_finalized_command("egg_info") if ei.broken_egg_info: @@ -46,8 +43,14 @@ class develop(easy_install): "Please rename %r to %r before using 'develop'" % (ei.egg_info, ei.broken_egg_info) ) - self.args = [ei.egg_name] + self.args = [ei.egg_name] + + + + easy_install.finalize_options(self) + self.expand_basedirs() + self.expand_dirs() # pick up setup-dir .egg files only: no .egg-info self.package_index.scan(glob.glob('*.egg')) @@ -62,7 +65,7 @@ class develop(easy_install): "--egg-path must be a relative path from the install" " directory to "+target ) - + # Make a distribution for the package's source self.dist = Distribution( target, @@ -81,11 +84,35 @@ class develop(easy_install): " installation directory", p, normalize_path(os.curdir)) def install_for_development(self): - # Ensure metadata is up-to-date - self.run_command('egg_info') - # Build extensions in-place - self.reinitialize_command('build_ext', inplace=1) - self.run_command('build_ext') + if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False): + # If we run 2to3 we can not do this inplace: + + # Ensure metadata is up-to-date + self.reinitialize_command('build_py', inplace=0) + self.run_command('build_py') + bpy_cmd = self.get_finalized_command("build_py") + build_path = normalize_path(bpy_cmd.build_lib) + + # Build extensions + self.reinitialize_command('egg_info', egg_base=build_path) + self.run_command('egg_info') + + self.reinitialize_command('build_ext', inplace=0) + self.run_command('build_ext') + + # Fixup egg-link and easy-install.pth + ei_cmd = self.get_finalized_command("egg_info") + self.egg_path = build_path + self.dist.location = build_path + self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info) # XXX + else: + # Without 2to3 inplace works fine: + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + self.install_site_py() # ensure that target dir is site-safe if setuptools.bootstrap_install_from: self.easy_install(setuptools.bootstrap_install_from) @@ -105,7 +132,9 @@ class develop(easy_install): def uninstall_link(self): if os.path.exists(self.egg_link): log.info("Removing %s (link to %s)", self.egg_link, self.egg_base) - contents = [line.rstrip() for line in file(self.egg_link)] + egg_link_file = open(self.egg_link) + contents = [line.rstrip() for line in egg_link_file] + egg_link_file.close() if contents not in ([self.egg_path], [self.egg_path, self.setup_path]): log.warn("Link points to %s: uninstall aborted", contents) return @@ -117,10 +146,6 @@ class develop(easy_install): # XXX should also check for entry point scripts! log.warn("Note: you must uninstall or replace scripts manually!") - - - - def install_egg_scripts(self, dist): if dist is not self.dist: # Installing a dependency, so fall back to normal behavior @@ -129,7 +154,7 @@ class develop(easy_install): # create wrapper scripts in the script dir, pointing to dist.scripts # new-style... - self.install_wrapper_scripts(dist) + self.install_wrapper_scripts(dist) # ...and old-style for script_name in self.distribution.scripts or []: @@ -140,25 +165,3 @@ class develop(easy_install): f.close() self.install_script(dist, script_name, script_text, script_path) - - - - - - - - - - - - - - - - - - - - - - diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index f06b6ddd..47c1a3d8 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -7,21 +7,40 @@ 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://peak.telecommunity.com/DevCenter/EasyInstall +__ http://packages.python.org/setuptools/easy_install.html + """ -import sys, os.path, zipimport, shutil, tempfile, zipfile, re, stat, random +import sys +import os +import zipimport +import shutil +import tempfile +import zipfile +import re +import stat +import random from glob import glob -from setuptools import Command +from setuptools import Command, _dont_write_bytecode from setuptools.sandbox import run_setup from distutils import log, dir_util -from distutils.sysconfig import get_python_lib +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 + DistutilsError, DistutilsPlatformError +from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS +from setuptools.command import setopt from setuptools.archive_util import unpack_archive -from setuptools.package_index import PackageIndex, parse_bdist_wininst +from setuptools.package_index import PackageIndex from setuptools.package_index import URL_SCHEME from setuptools.command import bdist_egg, egg_info -from pkg_resources import * +from pkg_resources import yield_lines, normalize_path, resource_string, \ + ensure_directory, get_distribution, find_distributions, \ + Environment, Requirement, Distribution, \ + PathMetadata, EggMetadata, WorkingSet, \ + DistributionNotFound, VersionConflict, \ + DEVELOP_DIST + sys_executable = os.path.normpath(sys.executable) __all__ = [ @@ -29,6 +48,13 @@ __all__ = [ 'main', 'get_exe_prefixes', ] +import site +HAS_USER_SITE = not sys.version < "2.6" and site.ENABLE_USER_SITE + +import struct +def is_64bit(): + return struct.calcsize("P") == 8 + def samefile(p1,p2): if hasattr(os.path,'samefile') and ( os.path.exists(p1) and os.path.exists(p2) @@ -39,6 +65,25 @@ def samefile(p1,p2): os.path.normpath(os.path.normcase(p2)) ) +if sys.version_info <= (3,): + def _to_ascii(s): + return s + def isascii(s): + try: + unicode(s, 'ascii') + return True + except UnicodeError: + return False +else: + def _to_ascii(s): + return s.encode('ascii') + def isascii(s): + try: + s.encode('ascii') + return True + except UnicodeError: + return False + class easy_install(Command): """Manage a download/build/install process""" description = "Find/get/install Python packages" @@ -71,16 +116,32 @@ class easy_install(Command): ('no-deps', 'N', "don't install dependencies"), ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), ('local-snapshots-ok', 'l', "allow building eggs from local checkouts"), + ('version', None, "print version information and exit"), + ('no-find-links', None, + "Don't load find-links defined in packages being installed") ] boolean_options = [ 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', 'delete-conflicting', 'ignore-conflicts-at-my-risk', 'editable', - 'no-deps', 'local-snapshots-ok', + 'no-deps', 'local-snapshots-ok', 'version' ] + + if HAS_USER_SITE: + user_options.append(('user', None, + "install in user site-package '%s'" % site.USER_SITE)) + boolean_options.append('user') + + negative_opt = {'always-unzip': 'zip-ok'} create_index = PackageIndex def initialize_options(self): + if HAS_USER_SITE: + whereami = os.path.abspath(__file__) + self.user = whereami.startswith(site.USER_SITE) + else: + self.user = 0 + self.zip_ok = self.local_snapshots_ok = None self.install_dir = self.script_dir = self.exclude_scripts = None self.index_url = None @@ -91,6 +152,22 @@ class easy_install(Command): self.upgrade = self.always_copy = self.multi_version = None self.editable = self.no_deps = self.allow_hosts = None self.root = self.prefix = self.no_report = None + self.version = None + self.install_purelib = None # for pure module distributions + self.install_platlib = None # non-pure (dists w/ extensions) + self.install_headers = None # for C/C++ headers + self.install_lib = None # set to either purelib or platlib + self.install_scripts = None + self.install_data = None + self.install_base = None + self.install_platbase = None + if HAS_USER_SITE: + self.install_userbase = site.USER_BASE + self.install_usersite = site.USER_SITE + else: + self.install_userbase = None + self.install_usersite = None + self.no_find_links = None # Options not specifiable via command line self.package_index = None @@ -122,12 +199,56 @@ class easy_install(Command): os.unlink(filename) def finalize_options(self): + if self.version: + print 'setuptools %s' % get_distribution('setuptools').version + sys.exit() + + py_version = sys.version.split()[0] + prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') + + self.config_vars = {'dist_name': self.distribution.get_name(), + 'dist_version': self.distribution.get_version(), + 'dist_fullname': self.distribution.get_fullname(), + 'py_version': py_version, + 'py_version_short': py_version[0:3], + 'py_version_nodot': py_version[0] + py_version[2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + # Only python 3.2+ has abiflags + 'abiflags': getattr(sys, 'abiflags', ''), + } + + if HAS_USER_SITE: + self.config_vars['userbase'] = self.install_userbase + self.config_vars['usersite'] = self.install_usersite + + # fix the install_dir if "--user" was used + #XXX: duplicate of the code in the setup command + if self.user and HAS_USER_SITE: + self.create_home_path() + if self.install_userbase is None: + raise DistutilsPlatformError( + "User base directory is not specified") + self.install_base = self.install_platbase = self.install_userbase + if os.name == 'posix': + self.select_scheme("unix_user") + else: + self.select_scheme(os.name + "_user") + + self.expand_basedirs() + self.expand_dirs() + self._expand('install_dir','script_dir','build_directory','site_dirs') # If a non-default installation directory was specified, default the # script directory to match it. if self.script_dir is None: self.script_dir = self.install_dir + if self.no_find_links is None: + self.no_find_links = False + # Let install_dir get set by install_lib command, which in turn # gets its info from the install command, and takes into account # --prefix and --home and all that other crud. @@ -138,6 +259,10 @@ class easy_install(Command): self.set_undefined_options('install_scripts', ('install_dir', 'script_dir') ) + + if self.user and self.install_purelib: + self.install_dir = self.install_purelib + self.script_dir = self.install_scripts # default --record from the install command self.set_undefined_options('install', ('record', 'record')) normpath = map(normalize_path, sys.path) @@ -179,7 +304,8 @@ class easy_install(Command): self.find_links = [] if self.local_snapshots_ok: self.package_index.scan_egg_links(self.shadow_path+sys.path) - self.package_index.add_find_links(self.find_links) + if not self.no_find_links: + self.package_index.add_find_links(self.find_links) self.set_undefined_options('install_lib', ('optimize','optimize')) if not isinstance(self.optimize,int): try: @@ -203,8 +329,29 @@ class easy_install(Command): self.outputs = [] + + def _expand_attrs(self, attrs): + for attr in attrs: + val = getattr(self, attr) + if val is not None: + if os.name == 'posix' or os.name == 'nt': + val = os.path.expanduser(val) + val = subst_vars(val, self.config_vars) + setattr(self, attr, val) + + def expand_basedirs(self): + """Calls `os.path.expanduser` on install_base, install_platbase and + root.""" + self._expand_attrs(['install_base', 'install_platbase', 'root']) + + def expand_dirs(self): + """Calls `os.path.expanduser` on install dirs.""" + self._expand_attrs(['install_purelib', 'install_platlib', + 'install_lib', 'install_headers', + 'install_scripts', 'install_data',]) + def run(self): - if self.verbose<>self.distribution.verbose: + if self.verbose != self.distribution.verbose: log.set_verbosity(self.verbose) try: for spec in self.args: @@ -246,6 +393,7 @@ class easy_install(Command): def check_site_dir(self): """Verify that self.install_dir is .pth-capable dir, if needed""" + instdir = normalize_path(self.install_dir) pth_file = os.path.join(instdir,'easy-install.pth') @@ -317,7 +465,7 @@ variable. For information on other options, you may wish to consult the documentation at: - http://peak.telecommunity.com/EasyInstall.html + http://packages.python.org/setuptools/easy_install.html Please make the appropriate changes for your system and try again. """ @@ -335,12 +483,15 @@ Please make the appropriate changes for your system and try again. ok_exists = os.path.exists(ok_file) try: if ok_exists: os.unlink(ok_file) + dirname = os.path.dirname(ok_file) + if not os.path.exists(dirname): + os.makedirs(dirname) f = open(pth_file,'w') except (OSError,IOError): self.cant_write_to_target() else: try: - f.write("import os;open(%r,'w').write('OK')\n" % (ok_file,)) + f.write("import os; f = open(%r, 'w'); f.write('OK'); f.close()\n" % (ok_file,)) f.close(); f=None executable = sys.executable if os.name=='nt': @@ -430,7 +581,8 @@ Please make the appropriate changes for your system and try again. self.check_editable(spec) dist = self.package_index.fetch_distribution( - spec, tmpdir, self.upgrade, self.editable, not self.always_copy + spec, tmpdir, self.upgrade, self.editable, not self.always_copy, + self.local_index ) if dist is None: @@ -487,6 +639,15 @@ Please make the appropriate changes for your system and try again. + def select_scheme(self, name): + """Sets the install directories by applying the install schemes.""" + # it's the caller's problem if they supply a bad name! + scheme = INSTALL_SCHEMES[name] + for key in SCHEME_KEYS: + attrname = 'install_' + key + if getattr(self, attrname) is None: + setattr(self, attrname, scheme[key]) + @@ -497,7 +658,8 @@ Please make the appropriate changes for your system and try again. 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'): + if (dist.has_metadata('dependency_links.txt') and + not self.no_find_links): self.package_index.add_find_links( dist.get_metadata_lines('dependency_links.txt') ) @@ -577,23 +739,27 @@ Please make the appropriate changes for your system and try again. spec = str(dist.as_requirement()) is_script = is_python_script(script_text, script_name) - if is_script and dev_path: - script_text = get_script_header(script_text) + ( - "# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r\n" - "__requires__ = %(spec)r\n" - "from pkg_resources import require; require(%(spec)r)\n" - "del require\n" - "__file__ = %(dev_path)r\n" - "execfile(__file__)\n" - ) % locals() - elif is_script: - script_text = get_script_header(script_text) + ( - "# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r\n" - "__requires__ = %(spec)r\n" - "import pkg_resources\n" - "pkg_resources.run_script(%(spec)r, %(script_name)r)\n" - ) % locals() - self.write_script(script_name, script_text, 'b') + def get_template(filename): + """ + There are a couple of template scripts in the package. This + function loads one of them and prepares it for use. + + These templates use triple-quotes to escape variable + substitutions so the scripts get the 2to3 treatment when build + on Python 3. The templates cannot use triple-quotes naturally. + """ + raw_bytes = resource_string('setuptools', template_name) + template_str = raw_bytes.decode('utf-8') + clean_template = template_str.replace('"""', '') + return clean_template + + if is_script: + template_name = 'script template.py' + if dev_path: + template_name = template_name.replace('.py', ' (dev).py') + script_text = (get_script_header(script_text) + + get_template(template_name) % locals()) + self.write_script(script_name, _to_ascii(script_text), 'b') def write_script(self, script_name, contents, mode="t", blockers=()): """Write an executable file to the scripts directory""" @@ -603,12 +769,13 @@ Please make the appropriate changes for your system and try again. target = os.path.join(self.script_dir, script_name) self.add_output(target) + mask = current_umask() if not self.dry_run: ensure_directory(target) f = open(target,"w"+mode) f.write(contents) f.close() - chmod(target,0755) + chmod(target, 0777-mask) @@ -705,7 +872,7 @@ Please make the appropriate changes for your system and try again. # Create a dummy distribution object until we build the real distro dist = Distribution(None, project_name=cfg.get('metadata','name'), - version=cfg.get('metadata','version'), platform="win32" + version=cfg.get('metadata','version'), platform=get_platform() ) # Convert the .exe to an unpacked egg @@ -781,7 +948,9 @@ Please make the appropriate changes for your system and try again. if locals()[name]: txt = os.path.join(egg_tmp, 'EGG-INFO', name+'.txt') if not os.path.exists(txt): - open(txt,'w').write('\n'.join(locals()[name])+'\n') + f = open(txt,'w') + f.write('\n'.join(locals()[name])+'\n') + f.close() def check_conflicts(self, dist): """Verify that there are no conflicting "old-style" packages""" @@ -922,11 +1091,14 @@ See the setuptools documentation for the "develop" command for more info. def build_and_install(self, setup_script, setup_base): args = ['bdist_egg', '--dist-dir'] + dist_dir = tempfile.mkdtemp( prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) ) try: + self._set_fetcher_options(os.path.dirname(setup_script)) args.append(dist_dir) + self.run_setup(setup_script, setup_base, args) all_eggs = Environment([dist_dir]) eggs = [] @@ -941,6 +1113,30 @@ See the setuptools documentation for the "develop" command for more info. rmtree(dist_dir) log.set_verbosity(self.verbose) # restore our log verbosity + def _set_fetcher_options(self, base): + """ + When easy_install is about to run bdist_egg on a source dist, that + source dist might have 'setup_requires' directives, requiring + additional fetching. Ensure the fetcher options given to easy_install + are available to that command as well. + """ + # find the fetch options from easy_install and write them out + # to the setup.cfg file. + ei_opts = self.distribution.get_option_dict('easy_install').copy() + fetch_directives = ( + 'find_links', 'site_dirs', 'index_url', 'optimize', + 'site_dirs', 'allow_hosts', + ) + fetch_options = {} + for key, val in ei_opts.iteritems(): + if key not in fetch_directives: continue + fetch_options[key.replace('_', '-')] = val[1] + # create a settings dictionary suitable for `edit_config` + settings = dict(easy_install=fetch_options) + cfg_filename = os.path.join(base, 'setup.cfg') + setopt.edit_config(cfg_filename, settings) + + def update_pth(self,dist): if self.pth_file is None: return @@ -1002,6 +1198,10 @@ See the setuptools documentation for the "develop" command for more info. chmod(f, mode) def byte_compile(self, to_compile): + if _dont_write_bytecode: + self.warn('byte-compiling is disabled, skipping.') + return + from distutils.util import byte_compile try: # try to make the byte compile messages quieter @@ -1049,7 +1249,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://peak.telecommunity.com/EasyInstall.html#custom-installation-locations + http://packages.python.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','') @@ -1076,7 +1276,13 @@ Please make the appropriate changes for your system and try again.""" % ( if os.path.exists(sitepy): log.debug("Checking existing site.py in %s", self.install_dir) - current = open(sitepy,'rb').read() + f = open(sitepy,'rb') + current = f.read() + # we want str, not bytes + if sys.version_info >= (3,): + current = current.decode() + + f.close() if not current.startswith('def __boot():'): raise DistutilsError( "%s is not a setuptools-generated site.py; please" @@ -1097,7 +1303,15 @@ Please make the appropriate changes for your system and try again.""" % ( - + def create_home_path(self): + """Create directories under ~.""" + if not self.user: + return + home = convert_path(os.path.expanduser("~")) + for name, path in self.config_vars.iteritems(): + if path.startswith(home) and not os.path.isdir(path): + self.debug_print("os.makedirs('%s', 0700)" % path) + os.makedirs(path, 0700) @@ -1183,7 +1397,11 @@ def get_site_dirs(): site_lib = get_python_lib(plat_specific) if site_lib not in sitedirs: sitedirs.append(site_lib) + if HAS_USER_SITE: + sitedirs.append(site.USER_SITE) + sitedirs = map(normalize_path, sitedirs) + return sitedirs @@ -1252,7 +1470,19 @@ def extract_wininst_cfg(dist_filename): f.seek(prepended-(12+cfglen)) cfg = ConfigParser.RawConfigParser({'version':'','target_version':''}) try: - cfg.readfp(StringIO.StringIO(f.read(cfglen).split(chr(0),1)[0])) + part = f.read(cfglen) + # part is in bytes, but we need to read up to the first null + # byte. + if sys.version_info >= (2,6): + null_byte = bytes([0]) + else: + null_byte = chr(0) + config = part.split(null_byte, 1)[0] + # Now the config is in bytes, but on Python 3, it must be + # unicode for the RawConfigParser, so decode it. Is this the + # right encoding? + config = config.decode('ascii') + cfg.readfp(StringIO.StringIO(config)) except ConfigParser.Error: return None if not cfg.has_section('metadata') or not cfg.has_section('Setup'): @@ -1275,7 +1505,8 @@ def get_exe_prefixes(exe_filename): prefixes = [ ('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''), ('PLATLIB/', ''), - ('SCRIPTS/', 'EGG-INFO/scripts/') + ('SCRIPTS/', 'EGG-INFO/scripts/'), + ('DATA/LIB/site-packages', ''), ] z = zipfile.ZipFile(exe_filename) try: @@ -1291,7 +1522,10 @@ def get_exe_prefixes(exe_filename): if name.endswith('-nspkg.pth'): continue if parts[0].upper() in ('PURELIB','PLATLIB'): - for pth in yield_lines(z.read(name)): + contents = z.read(name) + if sys.version_info >= (3,): + contents = contents.decode() + for pth in yield_lines(contents): pth = pth.strip().replace('\\','/') if not pth.startswith('import'): prefixes.append((('%s/%s/' % (parts[0],pth)), '')) @@ -1327,7 +1561,8 @@ class PthDistributions(Environment): saw_import = False seen = dict.fromkeys(self.sitedirs) if os.path.isfile(self.filename): - for line in open(self.filename,'rt'): + f = open(self.filename,'rt') + for line in f: if line.startswith('import'): saw_import = True continue @@ -1345,6 +1580,7 @@ class PthDistributions(Environment): self.dirty = True # we cleaned up, so we're dirty now :) continue seen[path] = 1 + f.close() if self.paths and not saw_import: self.dirty = True # ensure anything we touch has import wrappers @@ -1370,7 +1606,7 @@ class PthDistributions(Environment): if os.path.islink(self.filename): os.unlink(self.filename) - f = open(self.filename,'wb') + f = open(self.filename,'wt') f.write(data); f.close() elif os.path.exists(self.filename): @@ -1381,8 +1617,12 @@ class PthDistributions(Environment): def add(self,dist): """Add `dist` to the distribution map""" - if dist.location not in self.paths and dist.location not in self.sitedirs: - self.paths.append(dist.location); self.dirty = True + if (dist.location not in self.paths and ( + dist.location not in self.sitedirs or + dist.location == os.getcwd() #account for '.' being in PYTHONPATH + )): + self.paths.append(dist.location) + self.dirty = True Environment.add(self,dist) def remove(self,dist): @@ -1410,6 +1650,11 @@ class PthDistributions(Environment): def get_script_header(script_text, executable=sys_executable, wininst=False): """Create a #! line, getting options (if any) from script_text""" from distutils.command.build_scripts import first_line_re + + # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern. + if not isinstance(first_line_re.pattern, str): + first_line_re = re.compile(first_line_re.pattern.decode()) + first = (script_text+'\n').splitlines()[0] match = first_line_re.match(first) options = '' @@ -1421,7 +1666,7 @@ def get_script_header(script_text, executable=sys_executable, wininst=False): else: executable = nt_quote_arg(executable) hdr = "#!%(executable)s%(options)s\n" % locals() - if unicode(hdr,'ascii','ignore').encode('ascii') != hdr: + if not isascii(hdr): # Non-ascii path to sys.executable, use -x to prevent warnings if options: if options.strip().startswith('-'): @@ -1543,6 +1788,11 @@ def chmod(path, mode): def fix_jython_executable(executable, options): if sys.platform.startswith('java') and is_sh(executable): + # Workaround for Jython is not needed on Linux systems. + import java + if java.lang.System.getProperty("os.name") == "Linux": + return executable + # Workaround Jython's sys.executable being a .sh (an invalid # shebang line interpreter) if options: @@ -1561,16 +1811,18 @@ def get_script_args(dist, executable=sys_executable, wininst=False): spec = str(dist.as_requirement()) header = get_script_header("", executable, wininst) for group in 'console_scripts', 'gui_scripts': - for name,ep in dist.get_entry_map(group).items(): + for name, ep in dist.get_entry_map(group).items(): script_text = ( "# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r\n" "__requires__ = %(spec)r\n" "import sys\n" "from pkg_resources import load_entry_point\n" "\n" - "sys.exit(\n" - " load_entry_point(%(spec)r, %(group)r, %(name)r)()\n" - ")\n" + "if __name__ == '__main__':" + "\n" + " sys.exit(\n" + " load_entry_point(%(spec)r, %(group)r, %(name)r)()\n" + " )\n" ) % locals() if sys.platform=='win32' or wininst: # On Windows/wininst, add a .py extension and an .exe launcher @@ -1582,7 +1834,10 @@ def get_script_args(dist, executable=sys_executable, wininst=False): ext, launcher = '-script.py', 'cli.exe' old = ['.py','.pyc','.pyo'] new_header = re.sub('(?i)pythonw.exe','python.exe',header) - + if is_64bit(): + launcher = launcher.replace(".", "-64.") + else: + launcher = launcher.replace(".", "-32.") if os.path.exists(new_header[2:-1]) or sys.platform!='win32': hdr = new_header else: @@ -1632,12 +1887,16 @@ def rmtree(path, ignore_errors=False, onerror=auto_chmod): except os.error: onerror(os.rmdir, path, sys.exc_info()) +def current_umask(): + tmp = os.umask(022) + os.umask(tmp) + return tmp + def bootstrap(): # This function is called when setuptools*.egg is run using /bin/sh import setuptools; argv0 = os.path.dirname(setuptools.__path__[0]) sys.argv[0] = argv0; sys.argv.append(argv0); main() - def main(argv=None, **kw): from setuptools import setup from setuptools.dist import Distribution @@ -1662,6 +1921,7 @@ usage: %(script)s [options] requirement_or_url ... class DistributionWithoutHelpCommands(Distribution): common_usage = "" + def _show_help(self,*args,**kw): with_ei_usage(lambda: Distribution._show_help(self,*args,**kw)) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 9741e26a..0c2ea0cc 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -3,13 +3,13 @@ Create a distribution's .egg-info directory and contents""" # This module should be kept compatible with Python 2.3 -import os, re +import os, re, sys from setuptools import Command from distutils.errors import * from distutils import log from setuptools.command.sdist import sdist from distutils.util import convert_path -from distutils.filelist import FileList +from distutils.filelist import FileList as _FileList from pkg_resources import parse_requirements, safe_name, parse_version, \ safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename from sdist import walk_revctrl @@ -148,6 +148,8 @@ class egg_info(Command): to the file. """ log.info("writing %s to %s", what, filename) + if sys.version_info >= (3,): + data = data.encode("utf-8") if not self.dry_run: f = open(filename, 'wb') f.write(data) @@ -160,7 +162,12 @@ class egg_info(Command): os.unlink(filename) def tagged_version(self): - return safe_version(self.distribution.get_version() + self.vtags) + version = self.distribution.get_version() + # egg_info may be called more than once for a distribution, + # in which case the version string already contains all tags. + if self.vtags and version.endswith(self.vtags): + return safe_version(version) + return safe_version(version + self.vtags) def run(self): self.mkpath(self.egg_info) @@ -217,9 +224,9 @@ class egg_info(Command): data = f.read() f.close() - if data.startswith('9') or data.startswith('8'): + if data.startswith('10') or data.startswith('9') or data.startswith('8'): data = map(str.splitlines,data.split('\n\x0c\n')) - del data[0][0] # get rid of the '8' or '9' + 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'): @@ -267,16 +274,28 @@ class egg_info(Command): self.broken_egg_info = self.egg_info self.egg_info = bei # make it work for now -class FileList(FileList): +class FileList(_FileList): """File list that accepts only existing, platform-independent paths""" def append(self, item): if item.endswith('\r'): # Fix older sdists built on Windows item = item[:-1] path = convert_path(item) - if os.path.exists(path): - self.files.append(path) + if sys.version_info >= (3,): + try: + if os.path.exists(path) or os.path.exists(path.encode('utf-8')): + self.files.append(path) + except UnicodeEncodeError: + # Accept UTF-8 filenames even if LANG=C + if os.path.exists(path.encode('utf-8')): + self.files.append(path) + else: + log.warn("'%s' not %s encodable -- skipping", path, + sys.getfilesystemencoding()) + else: + if os.path.exists(path): + self.files.append(path) @@ -316,6 +335,18 @@ class manifest_maker(sdist): by 'add_defaults()' and 'read_template()') to the manifest file named by 'self.manifest'. """ + # The manifest must be UTF-8 encodable. See #303. + if sys.version_info >= (3,): + files = [] + for file in self.filelist.files: + try: + file.encode("utf-8") + except UnicodeEncodeError: + log.warn("'%s' not UTF-8 encodable -- skipping" % file) + else: + files.append(file) + self.filelist.files = files + files = self.filelist.files if os.sep!='/': files = [f.replace(os.sep,'/') for f in files] @@ -351,8 +382,11 @@ def write_file (filename, contents): """Create a file with the specified name and write 'contents' (a sequence of strings without line terminators) to it. """ + contents = "\n".join(contents) + if sys.version_info >= (3,): + contents = contents.encode("utf-8") f = open(filename, "wb") # always write POSIX-style manifest - f.write("\n".join(contents)) + f.write(contents) f.close() @@ -444,6 +478,7 @@ def get_pkg_info_revision(): match = re.match(r"Version:.*-r(\d+)\s*$", line) if match: return int(match.group(1)) + f.close() return 0 diff --git a/setuptools/command/install.py b/setuptools/command/install.py index a150c435..247c4f25 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -18,9 +18,6 @@ class install(_install): ('install_scripts', lambda self: True), ] _nc = dict(new_commands) - sub_commands = [ - cmd for cmd in _install.sub_commands if cmd[0] not in _nc - ] + new_commands def initialize_options(self): _install.initialize_options(self) @@ -104,6 +101,10 @@ class install(_install): cmd.run() setuptools.bootstrap_install_from = None +# XXX Python 3.1 doesn't see _nc if this is inside the class +install.sub_commands = [ + cmd for cmd in _install.sub_commands if cmd[0] not in install._nc + ] + install.new_commands diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py index 939340c5..f44b34b5 100755 --- a/setuptools/command/install_egg_info.py +++ b/setuptools/command/install_egg_info.py @@ -87,8 +87,10 @@ class install_egg_info(Command): filename += '-nspkg.pth'; self.outputs.append(filename) log.info("Installing %s",filename) if not self.dry_run: - f = open(filename,'wb') + f = open(filename,'wt') for pkg in nsp: + # ensure pkg is not a unicode string under Python 2.7 + pkg = str(pkg) pth = tuple(pkg.split('.')) trailer = '\n' if '.' in pkg: @@ -97,12 +99,12 @@ class install_egg_info(Command): % ('.'.join(pth[:-1]), pth[-1]) ) f.write( - "import sys,new,os; " + "import sys,types,os; " "p = os.path.join(sys._getframe(1).f_locals['sitedir'], " "*%(pth)r); " "ie = os.path.exists(os.path.join(p,'__init__.py')); " "m = not ie and " - "sys.modules.setdefault(%(pkg)r,new.module(%(pkg)r)); " + "sys.modules.setdefault(%(pkg)r,types.ModuleType(%(pkg)r)); " "mp = (m or []) and m.__dict__.setdefault('__path__',[]); " "(p not in mp) and mp.append(p)%(trailer)s" % locals() diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index c2dc2d59..82456035 100755 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -1,6 +1,5 @@ from distutils.command.install_scripts import install_scripts \ as _install_scripts -from easy_install import get_script_args, sys_executable, chmod from pkg_resources import Distribution, PathMetadata, ensure_directory import os from distutils import log @@ -11,8 +10,11 @@ class install_scripts(_install_scripts): def initialize_options(self): _install_scripts.initialize_options(self) self.no_ep = False - + def run(self): + from setuptools.command.easy_install import get_script_args + from setuptools.command.easy_install import sys_executable + self.run_command("egg_info") if self.distribution.scripts: _install_scripts.run(self) # run first to set up self.outfiles @@ -20,9 +22,9 @@ class install_scripts(_install_scripts): self.outfiles = [] if self.no_ep: # don't install entry point scripts into .egg file! - return + return - ei_cmd = self.get_finalized_command("egg_info") + ei_cmd = self.get_finalized_command("egg_info") dist = Distribution( ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info), ei_cmd.egg_name, ei_cmd.egg_version, @@ -35,48 +37,18 @@ class install_scripts(_install_scripts): for args in get_script_args(dist, executable, is_wininst): self.write_script(*args) - - - - def write_script(self, script_name, contents, mode="t", *ignored): """Write an executable file to the scripts directory""" + from setuptools.command.easy_install import chmod, current_umask log.info("Installing %s script to %s", script_name, self.install_dir) target = os.path.join(self.install_dir, script_name) self.outfiles.append(target) + mask = current_umask() if not self.dry_run: ensure_directory(target) f = open(target,"w"+mode) f.write(contents) f.close() - chmod(target,0755) - - - - - - - - - - - - - - - - - - - - - - - - - - - - + chmod(target, 0777-mask) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 5bd5ebd4..b8c8495e 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -2,6 +2,9 @@ from distutils.command.sdist import sdist as _sdist from distutils.util import convert_path from distutils import log import os, re, sys, pkg_resources +from glob import glob + +READMES = ('README', 'README.rst', 'README.txt') entities = [ ("<","<"), (">", ">"), (""", '"'), ("'", "'"), @@ -59,7 +62,7 @@ def _default_revctrl(dirname=''): def externals_finder(dirname, filename): """Find any 'svn:externals' directories""" found = False - f = open(filename,'rb') + f = open(filename,'rt') for line in iter(f.readline, ''): # can't use direct iter! parts = line.split() if len(parts)==2: @@ -86,8 +89,9 @@ def entries_finder(dirname, filename): f = open(filename,'rU') data = f.read() f.close() - if data.startswith('9') or data.startswith('8'): # subversion 1.5/1.4 + if data.startswith('10') or data.startswith('9') or data.startswith('8'): 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]) @@ -95,7 +99,7 @@ def entries_finder(dirname, filename): for match in entries_pattern.finditer(data): yield joinpath(dirname,unescape(match.group(1))) else: - log.warn("unrecognized .svn/entries format in %s", dirname) + log.warn("unrecognized .svn/entries format in %s", os.path.abspath(dirname)) finders = [ @@ -143,7 +147,17 @@ class sdist(_sdist): self.filelist = ei_cmd.filelist self.filelist.append(os.path.join(ei_cmd.egg_info,'SOURCES.txt')) self.check_readme() - self.check_metadata() + + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + # Call check_metadata only if no 'check' command + # (distutils <= 2.6) + import distutils.command + if 'check' not in distutils.command.__all__: + self.check_metadata() + self.make_distribution() dist_files = getattr(self.distribution,'dist_files',[]) @@ -152,24 +166,86 @@ class sdist(_sdist): if data not in dist_files: dist_files.append(data) - def read_template(self): + 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: - # grody hack to close the template file (MANIFEST.in) - # this prevents easy_install's attempt at deleting the file from - # dying and thus masking the real error 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] + for fn in standards: + if isinstance(fn, tuple): + alts = fn + got_it = 0 + for fn in alts: + if os.path.exists(fn): + got_it = 1 + self.filelist.append(fn) + break + + if not got_it: + self.warn("standard file not found: should have one of " + + ', '.join(alts)) + else: + if os.path.exists(fn): + self.filelist.append(fn) + else: + self.warn("standard file '%s' not found" % fn) + + optional = ['test/test*.py', 'setup.cfg'] + for pattern in optional: + files = filter(os.path.isfile, glob(pattern)) + if files: + self.filelist.extend(files) + + # getting python files + if self.distribution.has_pure_modules(): + build_py = self.get_finalized_command('build_py') + self.filelist.extend(build_py.get_source_files()) + # This functionality is incompatible with include_package_data, and + # will in fact create an infinite recursion if include_package_data + # is True. Use of include_package_data will imply that + # distutils-style automatic handling of package_data is disabled + if not self.distribution.include_package_data: + for _, src_dir, _, filenames in build_py.data_files: + self.filelist.extend([os.path.join(src_dir, filename) + for filename in filenames]) + + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + self.filelist.extend(build_ext.get_source_files()) + + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.filelist.extend(build_clib.get_source_files()) + + if self.distribution.has_scripts(): + build_scripts = self.get_finalized_command('build_scripts') + self.filelist.extend(build_scripts.get_source_files()) def check_readme(self): - alts = ("README", "README.txt") - for f in alts: + for f in READMES: if os.path.exists(f): return else: self.warn( - "standard file not found: should have one of " +', '.join(alts) + "standard file not found: should have one of " +', '.join(READMES) ) @@ -186,7 +262,39 @@ class sdist(_sdist): self.get_finalized_command('egg_info').save_version_info(dest) + def _manifest_is_not_generated(self): + # check for special comment used in 2.7.1 and higher + if not os.path.isfile(self.manifest): + return False + fp = open(self.manifest, 'rbU') + try: + first_line = fp.readline() + finally: + fp.close() + return first_line != '# file GENERATED by distutils, do NOT edit\n'.encode() + + def read_manifest(self): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + log.info("reading manifest file '%s'", self.manifest) + manifest = open(self.manifest, 'rbU') + for line in manifest: + # The manifest must contain UTF-8. See #303. + if sys.version_info >= (3,): + try: + line = line.decode('UTF-8') + except UnicodeDecodeError: + log.warn("%r not UTF-8 decodable -- skipping" % line) + continue + # ignore comments and blank lines + line = line.strip() + if line.startswith('#') or not line: + continue + self.filelist.append(line) + manifest.close() diff --git a/setuptools/command/test.py b/setuptools/command/test.py index db918dae..a02ac142 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -2,6 +2,7 @@ from setuptools import Command from distutils.errors import DistutilsOptionError import sys from pkg_resources import * +from pkg_resources import _namespace_packages from unittest import TestLoader, main class ScanningLoader(TestLoader): @@ -81,12 +82,28 @@ class test(Command): def with_project_on_sys_path(self, func): - # Ensure metadata is up-to-date - self.run_command('egg_info') + if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False): + # If we run 2to3 we can not do this inplace: - # Build extensions in-place - self.reinitialize_command('build_ext', inplace=1) - self.run_command('build_ext') + # Ensure metadata is up-to-date + self.reinitialize_command('build_py', inplace=0) + self.run_command('build_py') + bpy_cmd = self.get_finalized_command("build_py") + build_path = normalize_path(bpy_cmd.build_lib) + + # Build extensions + self.reinitialize_command('egg_info', egg_base=build_path) + self.run_command('egg_info') + + self.reinitialize_command('build_ext', inplace=0) + self.run_command('build_ext') + else: + # Without 2to3 inplace works fine: + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') ei_cmd = self.get_finalized_command("egg_info") @@ -123,11 +140,28 @@ class test(Command): def run_tests(self): import unittest + + # Purge modules under test from sys.modules. The test loader will + # re-import them from the build location. Required when 2to3 is used + # with namespace packages. + if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False): + module = self.test_args[-1].split('.')[0] + if module in _namespace_packages: + del_modules = [] + if module in sys.modules: + del_modules.append(module) + module += '.' + for name in sys.modules: + if name.startswith(module): + del_modules.append(name) + map(sys.modules.__delitem__, del_modules) + loader_ep = EntryPoint.parse("x="+self.test_loader) loader_class = loader_ep.load(require=False) + cks = loader_class() unittest.main( None, None, [unittest.__file__]+self.test_args, - testLoader = loader_class() + testLoader = cks ) diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 7ac08c22..21b9615c 100755 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -83,14 +83,16 @@ class upload(Command): dry_run=self.dry_run) # Fill in the data - content = open(filename,'rb').read() + f = open(filename,'rb') + content = f.read() + f.close() basename = os.path.basename(filename) comment = '' if command=='bdist_egg' and self.distribution.has_ext_modules(): comment = "built on %s" % platform.platform(terse=1) data = { ':action':'file_upload', - 'protcol_version':'1', + 'protocol_version':'1', 'name':self.distribution.get_name(), 'version':self.distribution.get_version(), 'content':(basename,content), @@ -107,8 +109,9 @@ class upload(Command): data['comment'] = comment if self.sign: - data['gpg_signature'] = (os.path.basename(filename) + ".asc", - open(filename+".asc").read()) + asc_file = open(filename + ".asc") + data['gpg_signature'] = (os.path.basename(filename) + ".asc", asc_file.read()) + asc_file.close() # set up the authentication auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip() @@ -179,3 +182,4 @@ class upload(Command): log.ERROR) if self.show_response: print '-'*75, r.read(), '-'*75 + diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py new file mode 100644 index 00000000..1d5a7445 --- /dev/null +++ b/setuptools/command/upload_docs.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +"""upload_docs + +Implements a Distutils 'upload_docs' subcommand (upload documentation to +PyPI's packages.python.org). +""" + +import os +import socket +import zipfile +import httplib +import urlparse +import tempfile +import sys +import shutil + +from base64 import standard_b64encode +from pkg_resources import iter_entry_points + +from distutils import log +from distutils.errors import DistutilsOptionError + +try: + from distutils.command.upload import upload +except ImportError: + from setuptools.command.upload import upload + + +# This is not just a replacement for byte literals +# but works as a general purpose encoder +def b(s, encoding='utf-8'): + if isinstance(s, unicode): + return s.encode(encoding) + return s + + +class upload_docs(upload): + + description = 'Upload documentation to PyPI' + + user_options = [ + ('repository=', 'r', + "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY), + ('show-response', None, + 'display full response text from server'), + ('upload-dir=', None, 'directory to upload'), + ] + boolean_options = upload.boolean_options + + def has_sphinx(self): + if self.upload_dir is None: + for ep in iter_entry_points('distutils.commands', 'build_sphinx'): + return True + + sub_commands = [('build_sphinx', has_sphinx)] + + def initialize_options(self): + upload.initialize_options(self) + self.upload_dir = None + self.target_dir = None + + def finalize_options(self): + upload.finalize_options(self) + if self.upload_dir is None: + if self.has_sphinx(): + build_sphinx = self.get_finalized_command('build_sphinx') + self.target_dir = build_sphinx.builder_target_dir + else: + build = self.get_finalized_command('build') + self.target_dir = os.path.join(build.build_base, 'docs') + else: + self.ensure_dirname('upload_dir') + self.target_dir = self.upload_dir + self.announce('Using upload directory %s' % self.target_dir) + + def create_zipfile(self, filename): + zip_file = zipfile.ZipFile(filename, "w") + try: + self.mkpath(self.target_dir) # just in case + for root, dirs, files in os.walk(self.target_dir): + if root == self.target_dir and not files: + raise DistutilsOptionError( + "no files found in upload directory '%s'" + % self.target_dir) + for name in files: + full = os.path.join(root, name) + relative = root[len(self.target_dir):].lstrip(os.path.sep) + dest = os.path.join(relative, name) + zip_file.write(full, dest) + finally: + zip_file.close() + + def run(self): + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + tmp_dir = tempfile.mkdtemp() + name = self.distribution.metadata.get_name() + zip_file = os.path.join(tmp_dir, "%s.zip" % name) + try: + self.create_zipfile(zip_file) + self.upload_file(zip_file) + finally: + shutil.rmtree(tmp_dir) + + def upload_file(self, filename): + f = open(filename, 'rb') + content = f.read() + f.close() + meta = self.distribution.metadata + data = { + ':action': 'doc_upload', + 'name': meta.get_name(), + 'content': (os.path.basename(filename), content), + } + # set up the authentication + credentials = b(self.username + ':' + self.password) + credentials = standard_b64encode(credentials) + if sys.version_info >= (3,): + credentials = credentials.decode('ascii') + auth = "Basic " + credentials + + # Build up the MIME payload for the POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b('\n--') + b(boundary) + end_boundary = sep_boundary + b('--') + body = [] + for key, values in data.iteritems(): + title = '\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if type(values) != type([]): + values = [values] + for value in values: + if type(value) is tuple: + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = b(value) + body.append(sep_boundary) + body.append(b(title)) + body.append(b("\n\n")) + body.append(value) + if value and value[-1:] == b('\r'): + body.append(b('\n')) # write an extra newline (lurve Macs) + body.append(end_boundary) + body.append(b("\n")) + body = b('').join(body) + + self.announce("Submitting documentation to %s" % (self.repository), + log.INFO) + + # build the Request + # We can't use urllib2 since we need to send the Basic + # auth right with the first request + schema, netloc, url, params, query, fragments = \ + urlparse.urlparse(self.repository) + assert not params and not query and not fragments + if schema == 'http': + conn = httplib.HTTPConnection(netloc) + elif schema == 'https': + conn = httplib.HTTPSConnection(netloc) + else: + raise AssertionError("unsupported schema "+schema) + + data = '' + loglevel = log.INFO + try: + conn.connect() + conn.putrequest("POST", url) + conn.putheader('Content-type', + 'multipart/form-data; boundary=%s'%boundary) + conn.putheader('Content-length', str(len(body))) + conn.putheader('Authorization', auth) + conn.endheaders() + conn.send(body) + except socket.error, e: + self.announce(str(e), log.ERROR) + return + + r = conn.getresponse() + if r.status == 200: + self.announce('Server response (%s): %s' % (r.status, r.reason), + log.INFO) + elif r.status == 301: + location = r.getheader('Location') + if location is None: + location = 'http://packages.python.org/%s/' % meta.get_name() + self.announce('Upload successful. Visit %s' % location, + log.INFO) + else: + self.announce('Upload failed (%s): %s' % (r.status, r.reason), + log.ERROR) + if self.show_response: + print '-'*75, r.read(), '-'*75 diff --git a/setuptools/dist.py b/setuptools/dist.py index 30ff35e3..998a4dbe 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -1,5 +1,6 @@ __all__ = ['Distribution'] +import re from distutils.core import Distribution as _Distribution from setuptools.depends import Require from setuptools.command.install import install @@ -210,6 +211,7 @@ class Distribution(_Distribution): self.require_features = [] self.features = {} self.dist_files = [] + self.src_root = attrs and attrs.pop("src_root", None) self.patch_missing_pkg_info(attrs) # Make sure we have any eggs needed to interpret 'attrs' if attrs is not None: @@ -254,11 +256,18 @@ class Distribution(_Distribution): if value is not None: ep.require(installer=self.fetch_build_egg) ep.load()(self, ep.name, value) + if getattr(self, 'convert_2to3_doctests', None): + # XXX may convert to set here when we can rely on set being builtin + self.convert_2to3_doctests = [os.path.abspath(p) for p in self.convert_2to3_doctests] + else: + self.convert_2to3_doctests = [] def fetch_build_egg(self, req): """Fetch an egg needed for building""" + try: cmd = self._egg_fetcher + cmd.package_index.to_scan = [] except AttributeError: from setuptools.command.easy_install import easy_install dist = self.__class__({'script_args':['easy_install']}) @@ -279,7 +288,7 @@ class Distribution(_Distribution): cmd = easy_install( dist, args=["x"], install_dir=os.curdir, exclude_scripts=True, always_copy=False, build_directory=None, editable=False, - upgrade=False, multi_version=True, no_report = True + upgrade=False, multi_version=True, no_report=True, user=False ) cmd.ensure_finalized() self._egg_fetcher = cmd @@ -415,19 +424,19 @@ class Distribution(_Distribution): if self.packages: self.packages = [ p for p in self.packages - if p<>package and not p.startswith(pfx) + if p != package and not p.startswith(pfx) ] if self.py_modules: self.py_modules = [ p for p in self.py_modules - if p<>package and not p.startswith(pfx) + if p != package and not p.startswith(pfx) ] if self.ext_modules: self.ext_modules = [ p for p in self.ext_modules - if p.name<>package and not p.name.startswith(pfx) + if p.name != package and not p.name.startswith(pfx) ] @@ -631,6 +640,43 @@ class Distribution(_Distribution): name = name[:-6] yield name + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + import sys + + if sys.version_info < (3,) or self.help_commands: + return _Distribution.handle_display_options(self, option_order) + + # Stdout may be StringIO (e.g. in tests) + import io + if not isinstance(sys.stdout, io.TextIOWrapper): + return _Distribution.handle_display_options(self, option_order) + + # Don't wrap stdout if utf-8 is already the encoding. Provides + # workaround for #334. + if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): + return _Distribution.handle_display_options(self, option_order) + + # Print metadata in UTF-8 no matter the platform + encoding = sys.stdout.encoding + errors = sys.stdout.errors + newline = sys.platform != 'win32' and '\n' or None + line_buffering = sys.stdout.line_buffering + + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) + try: + return _Distribution.handle_display_options(self, option_order) + finally: + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), encoding, errors, newline, line_buffering) + + # Install it throughout the distutils for module in distutils.dist, distutils.core, distutils.cmd: module.Distribution = Distribution @@ -799,22 +845,11 @@ 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/extension.py b/setuptools/extension.py index 2bef84e5..eb8b836c 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -1,36 +1,46 @@ -from distutils.core import Extension as _Extension -from dist import _get_unpatched -_Extension = _get_unpatched(_Extension) +import sys +import distutils.core +import distutils.extension -try: - from Pyrex.Distutils.build_ext import build_ext -except ImportError: - have_pyrex = False -else: - have_pyrex = True +from setuptools.dist import _get_unpatched + +_Extension = _get_unpatched(distutils.core.Extension) + +def have_pyrex(): + """ + Return True if Cython or Pyrex can be imported. + """ + pyrex_impls = 'Cython.Distutils.build_ext', 'Pyrex.Distutils.build_ext' + for pyrex_impl in pyrex_impls: + try: + # from (pyrex_impl) import build_ext + __import__(pyrex_impl, fromlist=['build_ext']).build_ext + return True + except Exception: + pass + return False class Extension(_Extension): """Extension that uses '.c' files in place of '.pyx' files""" - if not have_pyrex: - # convert .pyx extensions to .c - def __init__(self,*args,**kw): - _Extension.__init__(self,*args,**kw) - sources = [] - for s in self.sources: - if s.endswith('.pyx'): - sources.append(s[:-3]+'c') - else: - sources.append(s) - self.sources = sources + def __init__(self, *args, **kw): + _Extension.__init__(self, *args, **kw) + if not have_pyrex(): + self._convert_pyx_sources_to_c() + + def _convert_pyx_sources_to_c(self): + "convert .pyx extensions to .c" + def pyx_to_c(source): + if source.endswith('.pyx'): + source = source[:-4] + '.c' + return source + self.sources = map(pyx_to_c, self.sources) class Library(Extension): """Just like a regular Extension, but built as a library instead""" -import sys, distutils.core, distutils.extension distutils.core.Extension = Extension distutils.extension.Extension = Extension if 'distutils.command.build_ext' in sys.modules: sys.modules['distutils.command.build_ext'].Extension = Extension - diff --git a/setuptools/gui-32.exe b/setuptools/gui-32.exe Binary files differnew file mode 100644 index 00000000..3f64af7d --- /dev/null +++ b/setuptools/gui-32.exe diff --git a/setuptools/gui-64.exe b/setuptools/gui-64.exe Binary files differnew file mode 100644 index 00000000..3ab4378e --- /dev/null +++ b/setuptools/gui-64.exe diff --git a/setuptools/gui.exe b/setuptools/gui.exe Binary files differindex 53d4ff81..3f64af7d 100755..100644 --- a/setuptools/gui.exe +++ b/setuptools/gui.exe diff --git a/setuptools/package_index.py b/setuptools/package_index.py index da3fed12..d4d4631a 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -1,5 +1,7 @@ """PyPI and direct package downloading""" -import sys, os.path, re, urlparse, urllib2, shutil, random, socket, cStringIO +import sys, os.path, re, urlparse, urllib, urllib2, shutil, random, socket, cStringIO +import base64 +import httplib from pkg_resources import * from distutils import log from distutils.errors import DistutilsError @@ -24,20 +26,31 @@ __all__ = [ 'interpret_distro_name', ] +_SOCKET_TIMEOUT = 15 + def parse_bdist_wininst(name): """Return (base,pyversion) or (None,None) for possible .exe name""" lower = name.lower() - base, py_ver = None, None + base, py_ver, plat = None, None, None if lower.endswith('.exe'): if lower.endswith('.win32.exe'): base = name[:-10] + plat = 'win32' elif lower.startswith('.win32-py',-16): py_ver = name[-7:-4] base = name[:-16] + plat = 'win32' + elif lower.endswith('.win-amd64.exe'): + base = name[:-14] + plat = 'win-amd64' + elif lower.startswith('.win-amd64-py',-20): + py_ver = name[-7:-4] + base = name[:-20] + plat = 'win-amd64' + return base,py_ver,plat - return base,py_ver def egg_info_for_url(url): scheme, server, path, parameters, query, fragment = urlparse.urlparse(url) @@ -66,10 +79,10 @@ def distros_for_location(location, basename, metadata=None): return [Distribution.from_location(location, basename, metadata)] if basename.endswith('.exe'): - win_base, py_ver = parse_bdist_wininst(basename) + 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, "win32" + location, win_base, metadata, py_ver, BINARY_DIST, platform ) # Try source distro extensions (.zip, .tgz, etc.) @@ -142,7 +155,7 @@ def find_external_links(url, page): yield urlparse.urljoin(url, htmldecode(match.group(1))) user_agent = "Python-urllib/%s setuptools/%s" % ( - urllib2.__version__, require('setuptools')[0].version + sys.version[:3], require('setuptools')[0].version ) @@ -186,7 +199,7 @@ class PackageIndex(Environment): return self.info("Reading %s", url) - f = self.open_url(url, "Download error: %s -- Some packages may not be found!") + 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 @@ -196,12 +209,19 @@ class PackageIndex(Environment): base = f.url # handle redirects page = f.read() + if not isinstance(page, str): # We are in Python 3 and got bytes. We want str. + if isinstance(f, urllib2.HTTPError): + # Errors have no charset, assume latin1: + charset = 'latin-1' + else: + charset = f.headers.get_param('charset') or 'latin-1' + page = page.decode(charset, "ignore") f.close() - if url.startswith(self.index_url) and getattr(f,'code',None)!=404: - page = self.process_index(url, page) for match in HREF.finditer(page): link = urlparse.urljoin(base, htmldecode(match.group(1))) self.process_url(link) + if url.startswith(self.index_url) and getattr(f,'code',None)!=404: + page = self.process_index(url, page) def process_filename(self, fn, nested=False): # process filenames or directories @@ -237,7 +257,7 @@ class PackageIndex(Environment): self.scan_egg_link(item, entry) def scan_egg_link(self, path, entry): - lines = filter(None, map(str.strip, file(os.path.join(path, entry)))) + lines = filter(None, map(str.strip, open(os.path.join(path, entry)))) if len(lines)==2: for dist in find_distributions(os.path.join(path, lines[0])): dist.location = os.path.join(path, *lines) @@ -262,7 +282,10 @@ class PackageIndex(Environment): # process an index page into the package-page index for match in HREF.finditer(page): - scan( urlparse.urljoin(url, htmldecode(match.group(1))) ) + try: + scan( urlparse.urljoin(url, htmldecode(match.group(1))) ) + except ValueError: + pass pkg, ver = scan(url) # ensure this page is in the page index if pkg: @@ -409,7 +432,8 @@ class PackageIndex(Environment): def fetch_distribution(self, - requirement, tmpdir, force_scan=False, source=False, develop_ok=False + requirement, tmpdir, force_scan=False, source=False, develop_ok=False, + local_index=None ): """Obtain a distribution suitable for fulfilling `requirement` @@ -431,11 +455,14 @@ class PackageIndex(Environment): # process a Requirement self.info("Searching for %s", requirement) skipped = {} + dist = None - def find(req): + def find(req, env=None): + if env is None: + env = self # Find a matching distribution; may be called more than once - for dist in self[req.key]: + for dist in env[req.key]: if dist.precedence==DEVELOP_DIST and not develop_ok: if dist not in skipped: @@ -452,8 +479,11 @@ class PackageIndex(Environment): if force_scan: self.prescan() self.find_packages(requirement) + dist = find(requirement) + + if local_index is not None: + dist = dist or find(requirement, local_index) - dist = find(requirement) if dist is None and self.to_scan is not None: self.prescan() dist = find(requirement) @@ -550,7 +580,9 @@ class PackageIndex(Environment): bs = self.dl_blocksize size = -1 if "content-length" in headers: - size = int(headers["Content-Length"]) + # Some servers return multiple Content-Length headers :( + content_length = headers.get("Content-Length") + size = int(content_length) self.reporthook(url, filename, blocknum, bs, size) tfp = open(filename,'wb') while True: @@ -577,13 +609,33 @@ class PackageIndex(Environment): return local_open(url) try: return open_with_auth(url) + except (ValueError, httplib.InvalidURL), v: + msg = ' '.join([str(arg) for arg in v.args]) + if warning: + self.warn(warning, msg) + else: + raise DistutilsError('%s %s' % (url, msg)) except urllib2.HTTPError, v: return v except urllib2.URLError, v: - if warning: self.warn(warning, v.reason) + if warning: + self.warn(warning, v.reason) else: raise DistutilsError("Download error for %s: %s" % (url, v.reason)) + except httplib.BadStatusLine, v: + if warning: + self.warn(warning, v.line) + else: + raise DistutilsError('%s returned a bad status line. ' + 'The server might be down, %s' % \ + (url, v.line)) + except httplib.HTTPException, v: + if warning: + self.warn(warning, v) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v)) def _download_url(self, scheme, url, tmpdir): # Determine download filename @@ -605,8 +657,12 @@ class PackageIndex(Environment): # if scheme=='svn' or scheme.startswith('svn+'): return self._download_svn(url, filename) + elif scheme=='git' or scheme.startswith('git+'): + return self._download_git(url, filename) + elif scheme.startswith('hg+'): + return self._download_hg(url, filename) elif scheme=='file': - return urllib2.url2pathname(urlparse.urlparse(url)[2]) + return urllib.url2pathname(urlparse.urlparse(url)[2]) else: self.url_ok(url, True) # raises error if not allowed return self._attempt_download(url, filename) @@ -645,6 +701,55 @@ class PackageIndex(Environment): os.system("svn checkout -q %s %s" % (url, filename)) return filename + def _vcs_split_rev_from_url(self, url, pop_prefix=False): + scheme, netloc, path, query, frag = urlparse.urlsplit(url) + + scheme = scheme.split('+', 1)[-1] + + # Some fragment identification fails + path = path.split('#',1)[0] + + rev = None + if '@' in path: + path, rev = path.rsplit('@', 1) + + # Also, discard fragment + url = urlparse.urlunsplit((scheme, netloc, path, query, '')) + + return url, rev + + def _download_git(self, url, filename): + filename = filename.split('#',1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing git clone from %s to %s", url, filename) + os.system("git clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Checking out %s", rev) + os.system("(cd %s && git checkout --quiet %s)" % ( + filename, + rev, + )) + + return filename + + def _download_hg(self, url, filename): + filename = filename.split('#',1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing hg clone from %s to %s", url, filename) + os.system("hg clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Updating to %s", rev) + os.system("(cd %s && hg up -C -r %s >&-)" % ( + filename, + rev, + )) + + return filename + def debug(self, msg, *args): log.debug(msg, *args) @@ -693,20 +798,52 @@ def htmldecode(text): +def socket_timeout(timeout=15): + def _socket_timeout(func): + def _socket_timeout(*args, **kwargs): + old_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(timeout) + try: + return func(*args, **kwargs) + finally: + socket.setdefaulttimeout(old_timeout) + return _socket_timeout + return _socket_timeout +def _encode_auth(auth): + """ + A function compatible with Python 2.3-3.3 that will encode + auth from a URL suitable for an HTTP header. + >>> _encode_auth('username%3Apassword') + u'dXNlcm5hbWU6cGFzc3dvcmQ=' + """ + auth_s = urllib2.unquote(auth) + # convert to bytes + auth_bytes = auth_s.encode() + # use the legacy interface for Python 2.3 support + encoded_bytes = base64.encodestring(auth_bytes) + # convert back to a string + encoded = encoded_bytes.decode() + # strip the trailing carriage return + return encoded.rstrip() def open_with_auth(url): """Open a urllib2 request, handling HTTP authentication""" scheme, netloc, path, params, query, frag = urlparse.urlparse(url) + # Double scheme does not raise on Mac OS X as revealed by a + # failing test. We would expect "nonnumeric port". Refs #20. + if netloc.endswith(':'): + raise httplib.InvalidURL("nonnumeric port: ''") + if scheme in ('http', 'https'): auth, host = urllib2.splituser(netloc) else: auth = None if auth: - auth = "Basic " + urllib2.unquote(auth).encode('base64').strip() + auth = "Basic " + _encode_auth(auth) new_url = urlparse.urlunparse((scheme,host,path,params,query,frag)) request = urllib2.Request(new_url) request.add_header("Authorization", auth) @@ -725,6 +862,8 @@ def open_with_auth(url): return fp +# adding a timeout to avoid freezing package_index +open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) @@ -742,14 +881,16 @@ def fix_sf_url(url): def local_open(url): """Read a local path, with special support for directories""" scheme, server, path, param, query, frag = urlparse.urlparse(url) - filename = urllib2.url2pathname(path) + filename = urllib.url2pathname(path) if os.path.isfile(filename): return urllib2.urlopen(url) elif path.endswith('/') and os.path.isdir(filename): files = [] for f in os.listdir(filename): if f=='index.html': - body = open(os.path.join(filename,f),'rb').read() + fp = open(os.path.join(filename,f),'rb') + body = fp.read() + fp.close() break elif os.path.isdir(os.path.join(filename,f)): f+='/' diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 4c5e7129..e026ff13 100755 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -1,8 +1,13 @@ import os, sys, __builtin__, tempfile, operator, pkg_resources -_os = sys.modules[os.name] +if os.name == "java": + import org.python.modules.posix.PosixModule as _os +else: + _os = sys.modules[os.name] +try: + _file = file +except NameError: + _file = None _open = open -_file = file - from distutils.errors import DistutilsError from pkg_resources import working_set @@ -38,7 +43,6 @@ __all__ = [ - def run_setup(setup_script, args): """Run a distutils setup script, sandboxed in its directory""" old_dir = os.getcwd() @@ -51,7 +55,8 @@ def run_setup(setup_script, args): save_modules = sys.modules.copy() pr_state = pkg_resources.__getstate__() try: - tempfile.tempdir = temp_dir; os.chdir(setup_dir) + tempfile.tempdir = temp_dir + os.chdir(setup_dir) try: sys.argv[:] = [setup_script]+list(args) sys.path.insert(0, setup_dir) @@ -71,8 +76,14 @@ def run_setup(setup_script, args): finally: pkg_resources.__setstate__(pr_state) sys.modules.update(save_modules) - for key in list(sys.modules): - if key not in save_modules: del sys.modules[key] + # remove any modules imported within the sandbox + del_modules = [ + mod_name for mod_name in sys.modules + if mod_name not in save_modules + # exclude any encodings modules. See #285 + and not mod_name.startswith('encodings.') + ] + map(sys.modules.__delitem__, del_modules) os.chdir(old_dir) sys.path[:] = save_path sys.argv[:] = save_argv @@ -99,14 +110,16 @@ class AbstractSandbox: """Run 'func' under os sandboxing""" try: self._copy(self) - __builtin__.file = self._file + if _file: + __builtin__.file = self._file __builtin__.open = self._open self._active = True return func() finally: self._active = False + if _file: + __builtin__.file = _file __builtin__.open = _open - __builtin__.file = _file self._copy(_os) def _mk_dual_path_wrapper(name): @@ -129,8 +142,9 @@ class AbstractSandbox: return original(path,*args,**kw) return wrap + if _file: + _file = _mk_single_path_wrapper('file', _file) _open = _mk_single_path_wrapper('open', _open) - _file = _mk_single_path_wrapper('file', _file) for name in [ "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat", @@ -182,6 +196,19 @@ class AbstractSandbox: ) +if hasattr(os, 'devnull'): + _EXCEPTIONS = [os.devnull,] +else: + _EXCEPTIONS = [] + +try: + from win32com.client.gencache import GetGeneratePath + _EXCEPTIONS.append(GetGeneratePath()) + del GetGeneratePath +except ImportError: + # it appears pywin32 is not installed, so no need to exclude. + pass + class DirectorySandbox(AbstractSandbox): """Restrict operations to a single subdirectory - pseudo-chroot""" @@ -190,20 +217,28 @@ class DirectorySandbox(AbstractSandbox): "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam", ]) - def __init__(self,sandbox): + def __init__(self, sandbox, exceptions=_EXCEPTIONS): self._sandbox = os.path.normcase(os.path.realpath(sandbox)) self._prefix = os.path.join(self._sandbox,'') + self._exceptions = [os.path.normcase(os.path.realpath(path)) for path in exceptions] AbstractSandbox.__init__(self) def _violation(self, operation, *args, **kw): raise SandboxViolation(operation, args, kw) + if _file: + def _file(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("file", path, mode, *args, **kw) + return _file(path,mode,*args,**kw) + def _open(self, path, mode='r', *args, **kw): if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): self._violation("open", path, mode, *args, **kw) return _open(path,mode,*args,**kw) - def tmpnam(self): self._violation("tmpnam") + def tmpnam(self): + self._violation("tmpnam") def _ok(self,path): if hasattr(_os,'devnull') and path==_os.devnull: return True @@ -211,11 +246,16 @@ class DirectorySandbox(AbstractSandbox): try: self._active = False realpath = os.path.normcase(os.path.realpath(path)) - if realpath==self._sandbox or realpath.startswith(self._prefix): + if (self._exempted(realpath) or realpath == self._sandbox + or realpath.startswith(self._prefix)): return True finally: self._active = active + def _exempted(self, filepath): + exception_matches = map(filepath.startswith, self._exceptions) + return True in exception_matches + def _remap_input(self,operation,path,*args,**kw): """Called for path inputs""" if operation in self.write_ops and not self._ok(path): @@ -228,11 +268,6 @@ class DirectorySandbox(AbstractSandbox): self._violation(operation, src, dst, *args, **kw) return (src,dst) - def _file(self, path, mode='r', *args, **kw): - if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): - self._violation("file", path, mode, *args, **kw) - return _file(path,mode,*args,**kw) - def open(self, file, flags, mode=0777): """Called for low-level os.open()""" if flags & WRITE_FLAGS and not self._ok(file): diff --git a/setuptools/script template (dev).py b/setuptools/script template (dev).py new file mode 100644 index 00000000..6dd9dd45 --- /dev/null +++ b/setuptools/script template (dev).py @@ -0,0 +1,6 @@ +# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r +__requires__ = """%(spec)r""" +from pkg_resources import require; require("""%(spec)r""") +del require +__file__ = """%(dev_path)r""" +execfile(__file__) diff --git a/setuptools/script template.py b/setuptools/script template.py new file mode 100644 index 00000000..8dd5d510 --- /dev/null +++ b/setuptools/script template.py @@ -0,0 +1,4 @@ +# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r +__requires__ = """%(spec)r""" +import pkg_resources +pkg_resources.run_script("""%(spec)r""", """%(script_name)r""") diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 287bc240..b6988a08 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -1,22 +1,25 @@ """Tests for the 'setuptools' package""" -from unittest import TestSuite, TestCase, makeSuite, defaultTestLoader -import distutils.core, distutils.cmd +import sys +import os +import unittest +import doctest +import distutils.core +import distutils.cmd from distutils.errors import DistutilsOptionError, DistutilsPlatformError from distutils.errors import DistutilsSetupError -import setuptools, setuptools.dist -from setuptools import Feature from distutils.core import Extension -extract_constant, get_module_constant = None, None -from setuptools.depends import * -from distutils.version import StrictVersion, LooseVersion -from distutils.util import convert_path -import sys, os.path +from distutils.version import LooseVersion + +import setuptools.dist +import setuptools.depends as dep +from setuptools import Feature +from setuptools.depends import Require def additional_tests(): import doctest, unittest suite = unittest.TestSuite(( doctest.DocFileSuite( - 'api_tests.txt', + os.path.join('tests', 'api_tests.txt'), optionflags=doctest.ELLIPSIS, package='pkg_resources', ), )) @@ -35,80 +38,85 @@ def makeSetup(**args): try: return setuptools.setup(**args) finally: - distutils.core_setup_stop_after = None - - + distutils.core._setup_stop_after = None -class DependsTests(TestCase): +class DependsTests(unittest.TestCase): def testExtractConst(self): - if not extract_constant: return # skip on non-bytecode platforms + if not hasattr(dep, 'extract_constant'): + # skip on non-bytecode platforms + return def f1(): - global x,y,z + global x, y, z x = "test" y = z # unrecognized name - self.assertEqual(extract_constant(f1.func_code,'q', -1), None) + self.assertEqual(dep.extract_constant(f1.func_code,'q', -1), None) # constant assigned - self.assertEqual(extract_constant(f1.func_code,'x', -1), "test") + self.assertEqual(dep.extract_constant(f1.func_code,'x', -1), "test") # expression assigned - self.assertEqual(extract_constant(f1.func_code,'y', -1), -1) + self.assertEqual(dep.extract_constant(f1.func_code,'y', -1), -1) # recognized name, not assigned - self.assertEqual(extract_constant(f1.func_code,'z', -1), None) - + self.assertEqual(dep.extract_constant(f1.func_code,'z', -1), None) def testFindModule(self): - self.assertRaises(ImportError, find_module, 'no-such.-thing') - self.assertRaises(ImportError, find_module, 'setuptools.non-existent') - f,p,i = find_module('setuptools.tests'); f.close() + self.assertRaises(ImportError, dep.find_module, 'no-such.-thing') + self.assertRaises(ImportError, dep.find_module, 'setuptools.non-existent') + f,p,i = dep.find_module('setuptools.tests') + f.close() def testModuleExtract(self): - if not get_module_constant: return # skip on non-bytecode platforms - from distutils import __version__ + if not hasattr(dep, 'get_module_constant'): + # skip on non-bytecode platforms + return + + from email import __version__ self.assertEqual( - get_module_constant('distutils','__version__'), __version__ + dep.get_module_constant('email','__version__'), __version__ ) self.assertEqual( - get_module_constant('sys','version'), sys.version + dep.get_module_constant('sys','version'), sys.version ) self.assertEqual( - get_module_constant('setuptools.tests','__doc__'),__doc__ + dep.get_module_constant('setuptools.tests','__doc__'),__doc__ ) def testRequire(self): - if not extract_constant: return # skip on non-bytecode platforms + if not hasattr(dep, 'extract_constant'): + # skip on non-bytecode platformsh + return - req = Require('Distutils','1.0.3','distutils') + req = Require('Email','1.0.3','email') - self.assertEqual(req.name, 'Distutils') - self.assertEqual(req.module, 'distutils') + self.assertEqual(req.name, 'Email') + self.assertEqual(req.module, 'email') self.assertEqual(req.requested_version, '1.0.3') self.assertEqual(req.attribute, '__version__') - self.assertEqual(req.full_name(), 'Distutils-1.0.3') + self.assertEqual(req.full_name(), 'Email-1.0.3') - from distutils import __version__ + from email import __version__ self.assertEqual(req.get_version(), __version__) - self.failUnless(req.version_ok('1.0.9')) - self.failIf(req.version_ok('0.9.1')) - self.failIf(req.version_ok('unknown')) + self.assertTrue(req.version_ok('1.0.9')) + self.assertTrue(not req.version_ok('0.9.1')) + self.assertTrue(not req.version_ok('unknown')) - self.failUnless(req.is_present()) - self.failUnless(req.is_current()) + self.assertTrue(req.is_present()) + self.assertTrue(req.is_current()) - req = Require('Distutils 3000','03000','distutils',format=LooseVersion) - self.failUnless(req.is_present()) - self.failIf(req.is_current()) - self.failIf(req.version_ok('unknown')) + req = Require('Email 3000','03000','email',format=LooseVersion) + self.assertTrue(req.is_present()) + self.assertTrue(not req.is_current()) + self.assertTrue(not req.version_ok('unknown')) req = Require('Do-what-I-mean','1.0','d-w-i-m') - self.failIf(req.is_present()) - self.failIf(req.is_current()) + self.assertTrue(not req.is_present()) + self.assertTrue(not req.is_current()) req = Require('Tests', None, 'tests', homepage="http://example.com") self.assertEqual(req.format, None) @@ -118,11 +126,11 @@ class DependsTests(TestCase): self.assertEqual(req.homepage, 'http://example.com') paths = [os.path.dirname(p) for p in __path__] - self.failUnless(req.is_present(paths)) - self.failUnless(req.is_current(paths)) + self.assertTrue(req.is_present(paths)) + self.assertTrue(req.is_current(paths)) -class DistroTests(TestCase): +class DistroTests(unittest.TestCase): def setUp(self): self.e1 = Extension('bar.ext',['bar.c']) @@ -135,10 +143,8 @@ class DistroTests(TestCase): package_dir = {}, ) - def testDistroType(self): - self.failUnless(isinstance(self.dist,setuptools.dist.Distribution)) - + self.assertTrue(isinstance(self.dist,setuptools.dist.Distribution)) def testExcludePackage(self): self.dist.exclude_package('a') @@ -157,12 +163,6 @@ class DistroTests(TestCase): # test removals from unspecified options makeSetup().exclude_package('x') - - - - - - def testIncludeExclude(self): # remove an extension self.dist.exclude(ext_modules=[self.e1]) @@ -189,20 +189,17 @@ class DistroTests(TestCase): dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2]) def testContents(self): - self.failUnless(self.dist.has_contents_for('a')) + self.assertTrue(self.dist.has_contents_for('a')) self.dist.exclude_package('a') - self.failIf(self.dist.has_contents_for('a')) + self.assertTrue(not self.dist.has_contents_for('a')) - self.failUnless(self.dist.has_contents_for('b')) + self.assertTrue(self.dist.has_contents_for('b')) self.dist.exclude_package('b') - self.failIf(self.dist.has_contents_for('b')) + self.assertTrue(not self.dist.has_contents_for('b')) - self.failUnless(self.dist.has_contents_for('c')) + self.assertTrue(self.dist.has_contents_for('c')) self.dist.exclude_package('c') - self.failIf(self.dist.has_contents_for('c')) - - - + self.assertTrue(not self.dist.has_contents_for('c')) def testInvalidIncludeExclude(self): self.assertRaises(DistutilsSetupError, @@ -232,20 +229,7 @@ class DistroTests(TestCase): ) - - - - - - - - - - - - - -class FeatureTests(TestCase): +class FeatureTests(unittest.TestCase): def setUp(self): self.req = Require('Distutils','1.0.3','distutils') @@ -269,12 +253,12 @@ class FeatureTests(TestCase): ) def testDefaults(self): - self.failIf( + self.assertTrue(not Feature( "test",standard=True,remove='x',available=False ).include_by_default() ) - self.failUnless( + self.assertTrue( Feature("test",standard=True,remove='x').include_by_default() ) # Feature must have either kwargs, removes, or require_features @@ -288,33 +272,33 @@ class FeatureTests(TestCase): def testFeatureOptions(self): dist = self.dist - self.failUnless( + self.assertTrue( ('with-dwim',None,'include DWIM') in dist.feature_options ) - self.failUnless( + self.assertTrue( ('without-dwim',None,'exclude DWIM (default)') in dist.feature_options ) - self.failUnless( + self.assertTrue( ('with-bar',None,'include bar (default)') in dist.feature_options ) - self.failUnless( + self.assertTrue( ('without-bar',None,'exclude bar') in dist.feature_options ) self.assertEqual(dist.feature_negopt['without-foo'],'with-foo') self.assertEqual(dist.feature_negopt['without-bar'],'with-bar') self.assertEqual(dist.feature_negopt['without-dwim'],'with-dwim') - self.failIf('without-baz' in dist.feature_negopt) + self.assertTrue(not 'without-baz' in dist.feature_negopt) def testUseFeatures(self): dist = self.dist self.assertEqual(dist.with_foo,1) self.assertEqual(dist.with_bar,0) self.assertEqual(dist.with_baz,1) - self.failIf('bar_et' in dist.py_modules) - self.failIf('pkg.bar' in dist.packages) - self.failUnless('pkg.baz' in dist.packages) - self.failUnless('scripts/baz_it' in dist.scripts) - self.failUnless(('libfoo','foo/foofoo.c') in dist.libraries) + self.assertTrue(not 'bar_et' in dist.py_modules) + self.assertTrue(not 'pkg.bar' in dist.packages) + self.assertTrue('pkg.baz' in dist.packages) + self.assertTrue('scripts/baz_it' in dist.scripts) + self.assertTrue(('libfoo','foo/foofoo.c') in dist.libraries) self.assertEqual(dist.ext_modules,[]) self.assertEqual(dist.require_features, [self.req]) @@ -327,11 +311,11 @@ class FeatureTests(TestCase): SystemExit, makeSetup, features = {'x':Feature('x', remove='y')} ) -class TestCommandTests(TestCase): +class TestCommandTests(unittest.TestCase): def testTestIsCommand(self): test_cmd = makeSetup().get_command_obj('test') - self.failUnless(isinstance(test_cmd, distutils.cmd.Command)) + self.assertTrue(isinstance(test_cmd, distutils.cmd.Command)) def testLongOptSuiteWNoDefault(self): ts1 = makeSetup(script_args=['test','--test-suite=foo.tests.suite']) @@ -363,8 +347,3 @@ class TestCommandTests(TestCase): ts5 = makeSetup().get_command_obj('test') ts5.ensure_finalized() self.assertEqual(ts5.test_suite, None) - - - - - diff --git a/setuptools/tests/doctest.py b/setuptools/tests/doctest.py index bffce58f..cc1e06c3 100644 --- a/setuptools/tests/doctest.py +++ b/setuptools/tests/doctest.py @@ -1968,7 +1968,9 @@ def testfile(filename, module_relative=True, name=None, package=None, runner = DocTestRunner(verbose=verbose, optionflags=optionflags) # Read the file, convert it to a test, and run it. - s = open(filename).read() + f = open(filename) + s = f.read() + f.close() test = parser.get_doctest(s, globs, name, filename, 0) runner.run(test) @@ -2053,16 +2055,16 @@ class Tester: return (f,t) def rundict(self, d, name, module=None): - import new - m = new.module(name) + import types + m = types.ModuleType(name) m.__dict__.update(d) if module is None: module = False return self.rundoc(m, name, module) def run__test__(self, d, name): - import new - m = new.module(name) + import types + m = types.ModuleType(name) m.__test__ = d return self.rundoc(m, name) @@ -2353,7 +2355,9 @@ def DocFileTest(path, module_relative=True, package=None, # Find the file and read it. name = os.path.basename(path) - doc = open(path).read() + f = open(path) + doc = f.read() + f.close() # Convert it to a test, and wrap it in a DocFileCase. test = parser.get_doctest(doc, globs, name, path, 0) diff --git a/setuptools/tests/indexes/test_links_priority/external.html b/setuptools/tests/indexes/test_links_priority/external.html new file mode 100644 index 00000000..92e4702f --- /dev/null +++ b/setuptools/tests/indexes/test_links_priority/external.html @@ -0,0 +1,3 @@ +<html><body> +<a href="/foobar-0.1.tar.gz#md5=1__bad_md5___">bad old link</a> +</body></html> diff --git a/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html b/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html new file mode 100644 index 00000000..fefb028b --- /dev/null +++ b/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html @@ -0,0 +1,4 @@ +<html><body> +<a href="/foobar-0.1.tar.gz#md5=0_correct_md5">foobar-0.1.tar.gz</a><br/> +<a href="../../external.html" rel="homepage">external homepage</a><br/> +</body></html> diff --git a/setuptools/tests/py26compat.py b/setuptools/tests/py26compat.py new file mode 100644 index 00000000..d4fb891a --- /dev/null +++ b/setuptools/tests/py26compat.py @@ -0,0 +1,14 @@ +import unittest + +try: + # provide skipIf for Python 2.4-2.6 + skipIf = unittest.skipIf +except AttributeError: + def skipIf(condition, reason): + def skipper(func): + def skip(*args, **kwargs): + return + if condition: + return skip + return func + return skipper diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py new file mode 100644 index 00000000..b2ab7acc --- /dev/null +++ b/setuptools/tests/server.py @@ -0,0 +1,82 @@ +"""Basic http server for tests to simulate PyPI or custom indexes +""" +import urllib2 +import sys +import time +import threading +import BaseHTTPServer +from BaseHTTPServer import HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler + +class IndexServer(HTTPServer): + """Basic single-threaded http server simulating a package index + + You can use this server in unittest like this:: + s = IndexServer() + s.start() + index_url = s.base_url() + 'mytestindex' + # do some test requests to the index + # The index files should be located in setuptools/tests/indexes + s.stop() + """ + def __init__(self, server_address=('', 0), + RequestHandlerClass=SimpleHTTPRequestHandler): + HTTPServer.__init__(self, server_address, RequestHandlerClass) + self._run = True + + def serve(self): + while self._run: + self.handle_request() + + def start(self): + self.thread = threading.Thread(target=self.serve) + self.thread.start() + + def stop(self): + "Stop the server" + + # Let the server finish the last request and wait for a new one. + time.sleep(0.1) + + # self.shutdown is not supported on python < 2.6, so just + # set _run to false, and make a request, causing it to + # terminate. + self._run = False + url = 'http://127.0.0.1:%(server_port)s/' % vars(self) + try: + if sys.version_info >= (2, 6): + urllib2.urlopen(url, timeout=5) + else: + urllib2.urlopen(url) + except urllib2.URLError: + # ignore any errors; all that's important is the request + pass + self.thread.join() + + def base_url(self): + port = self.server_port + return 'http://127.0.0.1:%s/setuptools/tests/indexes/' % port + +class RequestRecorder(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + requests = vars(self.server).setdefault('requests', []) + requests.append(self) + self.send_response(200, 'OK') + +class MockServer(HTTPServer, threading.Thread): + """ + A simple HTTP Server that records the requests made to it. + """ + def __init__(self, server_address=('', 0), + RequestHandlerClass=RequestRecorder): + HTTPServer.__init__(self, server_address, RequestHandlerClass) + threading.Thread.__init__(self) + self.setDaemon(True) + self.requests = [] + + def run(self): + self.serve_forever() + + def url(self): + return 'http://localhost:%(server_port)s/' % vars(self) + url = property(url) diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py new file mode 100644 index 00000000..7da122cc --- /dev/null +++ b/setuptools/tests/test_bdist_egg.py @@ -0,0 +1,69 @@ +"""develop tests +""" +import sys +import os, re, shutil, tempfile, unittest +import tempfile +import site +from StringIO import StringIO + +from distutils.errors import DistutilsError +from setuptools.command.bdist_egg import bdist_egg +from setuptools.command import easy_install as easy_install_pkg +from setuptools.dist import Distribution + +SETUP_PY = """\ +from setuptools import setup + +setup(name='foo', py_modules=['hi']) +""" + +class TestDevelopTest(unittest.TestCase): + + def setUp(self): + self.dir = tempfile.mkdtemp() + self.old_cwd = os.getcwd() + os.chdir(self.dir) + f = open('setup.py', 'w') + f.write(SETUP_PY) + f.close() + f = open('hi.py', 'w') + f.write('1\n') + f.close() + if sys.version >= "2.6": + self.old_base = site.USER_BASE + site.USER_BASE = tempfile.mkdtemp() + self.old_site = site.USER_SITE + site.USER_SITE = tempfile.mkdtemp() + + def tearDown(self): + os.chdir(self.old_cwd) + shutil.rmtree(self.dir) + if sys.version >= "2.6": + shutil.rmtree(site.USER_BASE) + shutil.rmtree(site.USER_SITE) + site.USER_BASE = self.old_base + site.USER_SITE = self.old_site + + def test_bdist_egg(self): + dist = Distribution(dict( + script_name='setup.py', + script_args=['bdist_egg'], + name='foo', + py_modules=['hi'] + )) + os.makedirs(os.path.join('build', 'src')) + old_stdout = sys.stdout + sys.stdout = o = StringIO() + try: + dist.parse_command_line() + dist.run_commands() + finally: + sys.stdout = old_stdout + + # let's see if we got our egg link at the right place + [content] = os.listdir('dist') + self.assertTrue(re.match('foo-0.0.0-py[23].\d.egg$', content)) + +def test_suite(): + return unittest.makeSuite(TestDevelopTest) + diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py new file mode 100644 index 00000000..a520ced9 --- /dev/null +++ b/setuptools/tests/test_build_ext.py @@ -0,0 +1,20 @@ +"""build_ext tests +""" +import os, shutil, tempfile, unittest +from distutils.command.build_ext import build_ext as distutils_build_ext +from setuptools.command.build_ext import build_ext +from setuptools.dist import Distribution + +class TestBuildExtTest(unittest.TestCase): + + def test_get_ext_filename(self): + # setuptools needs to give back the same + # result than distutils, even if the fullname + # is not in ext_map + dist = Distribution() + cmd = build_ext(dist) + cmd.ext_map['foo/bar'] = '' + res = cmd.get_ext_filename('foo') + wanted = distutils_build_ext.get_ext_filename(cmd, 'foo') + assert res == wanted + diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py new file mode 100644 index 00000000..315058c5 --- /dev/null +++ b/setuptools/tests/test_develop.py @@ -0,0 +1,118 @@ +"""develop tests +""" +import sys +import os, shutil, tempfile, unittest +import tempfile +import site +from StringIO import StringIO + +from distutils.errors import DistutilsError +from setuptools.command.develop import develop +from setuptools.command import easy_install as easy_install_pkg +from setuptools.dist import Distribution + +SETUP_PY = """\ +from setuptools import setup + +setup(name='foo', + packages=['foo'], + use_2to3=True, +) +""" + +INIT_PY = """print "foo" +""" + +class TestDevelopTest(unittest.TestCase): + + def setUp(self): + if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + return + + # Directory structure + self.dir = tempfile.mkdtemp() + os.mkdir(os.path.join(self.dir, 'foo')) + # setup.py + setup = os.path.join(self.dir, 'setup.py') + f = open(setup, 'w') + f.write(SETUP_PY) + f.close() + self.old_cwd = os.getcwd() + # foo/__init__.py + init = os.path.join(self.dir, 'foo', '__init__.py') + f = open(init, 'w') + f.write(INIT_PY) + f.close() + + os.chdir(self.dir) + self.old_base = site.USER_BASE + site.USER_BASE = tempfile.mkdtemp() + self.old_site = site.USER_SITE + site.USER_SITE = tempfile.mkdtemp() + + def tearDown(self): + if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + return + + os.chdir(self.old_cwd) + shutil.rmtree(self.dir) + shutil.rmtree(site.USER_BASE) + shutil.rmtree(site.USER_SITE) + site.USER_BASE = self.old_base + site.USER_SITE = self.old_site + + def test_develop(self): + if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + return + dist = Distribution( + dict(name='foo', + packages=['foo'], + use_2to3=True, + version='0.0', + )) + dist.script_name = 'setup.py' + cmd = develop(dist) + cmd.user = 1 + cmd.ensure_finalized() + cmd.install_dir = site.USER_SITE + cmd.user = 1 + old_stdout = sys.stdout + #sys.stdout = StringIO() + try: + cmd.run() + finally: + sys.stdout = old_stdout + + # let's see if we got our egg link at the right place + content = os.listdir(site.USER_SITE) + content.sort() + self.assertEqual(content, ['easy-install.pth', 'foo.egg-link']) + + # Check that we are using the right code. + egg_link_file = open(os.path.join(site.USER_SITE, 'foo.egg-link'), 'rt') + path = egg_link_file.read().split()[0].strip() + egg_link_file.close() + init_file = open(os.path.join(path, 'foo', '__init__.py'), 'rt') + init = init_file.read().strip() + init_file.close() + if sys.version < "3": + self.assertEqual(init, 'print "foo"') + else: + self.assertEqual(init, 'print("foo")') + + def notest_develop_with_setup_requires(self): + + wanted = ("Could not find suitable distribution for " + "Requirement.parse('I-DONT-EXIST')") + old_dir = os.getcwd() + os.chdir(self.dir) + try: + try: + dist = Distribution({'setup_requires': ['I_DONT_EXIST']}) + except DistutilsError, e: + error = str(e) + if error == wanted: + pass + finally: + os.chdir(old_dir) + diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py new file mode 100644 index 00000000..fcb78c36 --- /dev/null +++ b/setuptools/tests/test_dist_info.py @@ -0,0 +1,80 @@ +"""Test .dist-info style distributions. +""" +import os +import shutil +import tempfile +import unittest +import textwrap + +try: + import ast +except: + pass + +import pkg_resources + +from setuptools.tests.py26compat import skipIf + +def DALS(s): + "dedent and left-strip" + return textwrap.dedent(s).lstrip() + +class TestDistInfo(unittest.TestCase): + + def test_distinfo(self): + dists = {} + for d in pkg_resources.find_distributions(self.tmpdir): + dists[d.project_name] = d + + assert len(dists) == 2, dists + + unversioned = dists['UnversionedDistribution'] + versioned = dists['VersionedDistribution'] + + assert versioned.version == '2.718' # from filename + assert unversioned.version == '0.3' # from METADATA + + @skipIf('ast' not in globals(), + "ast is used to test conditional dependencies (Python >= 2.6)") + def test_conditional_dependencies(self): + requires = [pkg_resources.Requirement.parse('splort==4'), + pkg_resources.Requirement.parse('quux>=1.1')] + + for d in pkg_resources.find_distributions(self.tmpdir): + self.assertEqual(d.requires(), requires[:1]) + self.assertEqual(d.requires(extras=('baz',)), requires) + self.assertEqual(d.extras, ['baz']) + + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + versioned = os.path.join(self.tmpdir, + 'VersionedDistribution-2.718.dist-info') + os.mkdir(versioned) + metadata_file = open(os.path.join(versioned, 'METADATA'), 'w+') + metadata_file.write(DALS( + """ + Metadata-Version: 1.2 + Name: VersionedDistribution + Requires-Dist: splort (4) + Provides-Extra: baz + Requires-Dist: quux (>=1.1); extra == 'baz' + """)) + metadata_file.close() + + unversioned = os.path.join(self.tmpdir, + 'UnversionedDistribution.dist-info') + os.mkdir(unversioned) + metadata_file = open(os.path.join(unversioned, 'METADATA'), 'w+') + metadata_file.write(DALS( + """ + Metadata-Version: 1.2 + Name: UnversionedDistribution + Version: 0.3 + Requires-Dist: splort (==4) + Provides-Extra: baz + Requires-Dist: quux (>=1.1); extra == 'baz' + """)) + metadata_file.close() + + def tearDown(self): + shutil.rmtree(self.tmpdir) diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py new file mode 100644 index 00000000..395056e7 --- /dev/null +++ b/setuptools/tests/test_easy_install.py @@ -0,0 +1,425 @@ +"""Easy install Tests +""" +import sys +import os +import shutil +import tempfile +import unittest +import site +import textwrap +import tarfile +import urlparse +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 +from setuptools.command.easy_install import PthDistributions +from setuptools.command import easy_install as easy_install_pkg +from setuptools.dist import Distribution +from pkg_resources import Distribution as PRDistribution +import setuptools.tests.server + +try: + # import multiprocessing solely for the purpose of testing its existence + __import__('multiprocessing') + import logging + _LOG = logging.getLogger('test_easy_install') + logging.basicConfig(level=logging.INFO, stream=sys.stderr) + _MULTIPROC = True +except ImportError: + _MULTIPROC = False + _LOG = None + +class FakeDist(object): + def get_entry_map(self, group): + if group != 'console_scripts': + return {} + return {'name': 'ep'} + + def as_requirement(self): + return 'spec' + +WANTED = """\ +#!%s +# EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name' +__requires__ = 'spec' +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.exit( + load_entry_point('spec', 'console_scripts', 'name')() + ) +""" % fix_jython_executable(sys.executable, "") + +SETUP_PY = """\ +from setuptools import setup + +setup(name='foo') +""" + +class TestEasyInstallTest(unittest.TestCase): + + def test_install_site_py(self): + dist = Distribution() + cmd = easy_install(dist) + cmd.sitepy_installed = False + cmd.install_dir = tempfile.mkdtemp() + try: + cmd.install_site_py() + sitepy = os.path.join(cmd.install_dir, 'site.py') + self.assertTrue(os.path.exists(sitepy)) + finally: + shutil.rmtree(cmd.install_dir) + + def test_get_script_args(self): + dist = FakeDist() + + old_platform = sys.platform + try: + name, script = [i for i in get_script_args(dist).next()][0:2] + finally: + sys.platform = old_platform + + self.assertEqual(script, WANTED) + + def test_no_find_links(self): + # new option '--no-find-links', that blocks find-links added at + # the project level + dist = Distribution() + cmd = easy_install(dist) + cmd.check_pth_processing = lambda: True + cmd.no_find_links = True + cmd.find_links = ['link1', 'link2'] + cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') + cmd.args = ['ok'] + cmd.ensure_finalized() + self.assertEqual(cmd.package_index.scanned_urls, {}) + + # let's try without it (default behavior) + cmd = easy_install(dist) + cmd.check_pth_processing = lambda: True + cmd.find_links = ['link1', 'link2'] + cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') + cmd.args = ['ok'] + cmd.ensure_finalized() + keys = cmd.package_index.scanned_urls.keys() + keys.sort() + self.assertEqual(keys, ['link1', 'link2']) + + +class TestPTHFileWriter(unittest.TestCase): + def test_add_from_cwd_site_sets_dirty(self): + '''a pth file manager should set dirty + if a distribution is in site but also the cwd + ''' + pth = PthDistributions('does-not_exist', [os.getcwd()]) + self.assertTrue(not pth.dirty) + pth.add(PRDistribution(os.getcwd())) + self.assertTrue(pth.dirty) + + def test_add_from_site_is_ignored(self): + if os.name != 'nt': + location = '/test/location/does-not-have-to-exist' + else: + location = 'c:\\does_not_exist' + pth = PthDistributions('does-not_exist', [location, ]) + self.assertTrue(not pth.dirty) + pth.add(PRDistribution(location)) + self.assertTrue(not pth.dirty) + + +class TestUserInstallTest(unittest.TestCase): + + def setUp(self): + self.dir = tempfile.mkdtemp() + setup = os.path.join(self.dir, 'setup.py') + f = open(setup, 'w') + f.write(SETUP_PY) + f.close() + self.old_cwd = os.getcwd() + os.chdir(self.dir) + if sys.version >= "2.6": + self.old_has_site = easy_install_pkg.HAS_USER_SITE + self.old_file = easy_install_pkg.__file__ + self.old_base = site.USER_BASE + site.USER_BASE = tempfile.mkdtemp() + self.old_site = site.USER_SITE + site.USER_SITE = tempfile.mkdtemp() + easy_install_pkg.__file__ = site.USER_SITE + + def tearDown(self): + os.chdir(self.old_cwd) + shutil.rmtree(self.dir) + if sys.version >= "2.6": + shutil.rmtree(site.USER_BASE) + shutil.rmtree(site.USER_SITE) + site.USER_BASE = self.old_base + site.USER_SITE = self.old_site + easy_install_pkg.HAS_USER_SITE = self.old_has_site + easy_install_pkg.__file__ = self.old_file + + def test_user_install_implied(self): + easy_install_pkg.HAS_USER_SITE = True # disabled sometimes + #XXX: replace with something meaningfull + if sys.version < "2.6": + return #SKIP + dist = Distribution() + dist.script_name = 'setup.py' + cmd = easy_install(dist) + cmd.args = ['py'] + cmd.ensure_finalized() + self.assertTrue(cmd.user, 'user should be implied') + + def test_multiproc_atexit(self): + if not _MULTIPROC: + return + _LOG.info('this should not break') + + def test_user_install_not_implied_without_usersite_enabled(self): + easy_install_pkg.HAS_USER_SITE = False # usually enabled + #XXX: replace with something meaningfull + if sys.version < "2.6": + return #SKIP + dist = Distribution() + dist.script_name = 'setup.py' + cmd = easy_install(dist) + cmd.args = ['py'] + cmd.initialize_options() + self.assertFalse(cmd.user, 'NOT user should be implied') + + def test_local_index(self): + # make sure the local index is used + # when easy_install looks for installed + # packages + new_location = tempfile.mkdtemp() + target = tempfile.mkdtemp() + egg_file = os.path.join(new_location, 'foo-1.0.egg-info') + f = open(egg_file, 'w') + try: + f.write('Name: foo\n') + finally: + f.close() + + sys.path.append(target) + old_ppath = os.environ.get('PYTHONPATH') + os.environ['PYTHONPATH'] = os.path.pathsep.join(sys.path) + try: + dist = Distribution() + dist.script_name = 'setup.py' + cmd = easy_install(dist) + cmd.install_dir = target + cmd.args = ['foo'] + cmd.ensure_finalized() + cmd.local_index.scan([new_location]) + res = cmd.easy_install('foo') + self.assertEqual(os.path.realpath(res.location), + os.path.realpath(new_location)) + finally: + sys.path.remove(target) + for basedir in [new_location, target, ]: + if not os.path.exists(basedir) or not os.path.isdir(basedir): + continue + try: + shutil.rmtree(basedir) + except: + pass + if old_ppath is not None: + os.environ['PYTHONPATH'] = old_ppath + else: + del os.environ['PYTHONPATH'] + + def test_setup_requires(self): + """Regression test for Distribute issue #318 + + Ensure that a package with setup_requires can be installed when + setuptools is installed in the user site-packages without causing a + SandboxViolation. + """ + + test_setup_attrs = { + 'name': 'test_pkg', 'version': '0.0', + 'setup_requires': ['foobar'], + 'dependency_links': [os.path.abspath(self.dir)] + } + + test_pkg = os.path.join(self.dir, 'test_pkg') + test_setup_py = os.path.join(test_pkg, 'setup.py') + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + os.mkdir(test_pkg) + + f = open(test_setup_py, 'w') + f.write(textwrap.dedent("""\ + import setuptools + setuptools.setup(**%r) + """ % test_setup_attrs)) + f.close() + + foobar_path = os.path.join(self.dir, 'foobar-0.1.tar.gz') + make_trivial_sdist( + foobar_path, + textwrap.dedent("""\ + import setuptools + setuptools.setup( + name='foobar', + version='0.1' + ) + """)) + + old_stdout = sys.stdout + old_stderr = sys.stderr + 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') + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + +class TestSetupRequires(unittest.TestCase): + + def test_setup_requires_honors_fetch_params(self): + """ + When easy_install installs a source distribution which specifies + setup_requires, it should honor the fetch parameters (such as + allow-hosts, index-url, and find-links). + """ + # set up a server which will simulate an alternate package index. + p_index = setuptools.tests.server.MockServer() + p_index.start() + netloc = 1 + p_index_loc = urlparse.urlparse(p_index.url)[netloc] + if p_index_loc.endswith(':0'): + # Some platforms (Jython) don't find a port to which to bind, + # so skip this test for them. + return + + # I realize this is all-but-impossible to read, because it was + # ported from some well-factored, safe code using 'with'. If you + # need to maintain this code, consider making the changes in + # the parent revision (of this comment) and then port the changes + # back for Python 2.4 (or deprecate Python 2.4). + + def install(dist_file): + def install_at(temp_install_dir): + def install_env(): + ei_params = ['--index-url', p_index.url, + '--allow-hosts', p_index_loc, + '--exclude-scripts', '--install-dir', temp_install_dir, + dist_file] + def install_clean_reset(): + def install_clean_argv(): + # attempt to install the dist. It should fail because + # it doesn't exist. + self.assertRaises(SystemExit, + easy_install_pkg.main, ei_params) + argv_context(install_clean_argv, ['easy_install']) + reset_setup_stop_context(install_clean_reset) + environment_context(install_env, PYTHONPATH=temp_install_dir) + tempdir_context(install_at) + + # create an sdist that has a build-time dependency. + self.create_sdist(install) + + # there should have been two or three requests to the server + # (three happens on Python 3.3a) + self.assertTrue(2 <= len(p_index.requests) <= 3) + self.assertEqual(p_index.requests[0].path, '/does-not-exist/') + + def create_sdist(self, installer): + """ + Create an sdist with a setup_requires dependency (of something that + doesn't exist) and invoke installer on it. + """ + def build_sdist(dir): + 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="setuptools-test-fetcher", + version="1.0", + setup_requires = ['does-not-exist'], + ) + """).lstrip()) + installer(dist_path) + tempdir_context(build_sdist) + + +def make_trivial_sdist(dist_path, setup_py): + """Create a simple sdist tarball at dist_path, containing just a + setup.py, the contents of which are provided by the setup_py string. + """ + + setup_py_file = tarfile.TarInfo(name='setup.py') + try: + # Python 3 (StringIO gets converted to io module) + MemFile = StringIO.BytesIO + except AttributeError: + MemFile = StringIO.StringIO + setup_py_bytes = MemFile(setup_py.encode('utf-8')) + setup_py_file.size = len(setup_py_bytes.getvalue()) + dist = tarfile.open(dist_path, 'w:gz') + try: + dist.addfile(setup_py_file, fileobj=setup_py_bytes) + finally: + dist.close() + + +def tempdir_context(f, cd=lambda dir:None): + """ + Invoke f in the context + """ + temp_dir = tempfile.mkdtemp() + orig_dir = os.getcwd() + try: + cd(temp_dir) + f(temp_dir) + finally: + cd(orig_dir) + shutil.rmtree(temp_dir) + +def environment_context(f, **updates): + """ + Invoke f in the context + """ + old_env = os.environ.copy() + os.environ.update(updates) + try: + f() + finally: + for key in updates: + del os.environ[key] + os.environ.update(old_env) + +def argv_context(f, repl): + """ + Invoke f in the context + """ + old_argv = sys.argv[:] + sys.argv[:] = repl + try: + f() + finally: + sys.argv[:] = old_argv + +def reset_setup_stop_context(f): + """ + 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. + """ + setup_stop_after = distutils.core._setup_stop_after + distutils.core._setup_stop_after = None + try: + f() + finally: + distutils.core._setup_stop_after = setup_stop_after diff --git a/setuptools/tests/test_markerlib.py b/setuptools/tests/test_markerlib.py new file mode 100644 index 00000000..aa461846 --- /dev/null +++ b/setuptools/tests/test_markerlib.py @@ -0,0 +1,64 @@ +import os +import unittest +from setuptools.tests.py26compat import skipIf + +try: + import ast +except ImportError: + pass + +class TestMarkerlib(unittest.TestCase): + + @skipIf('ast' not in globals(), + "ast not available (Python < 2.6?)") + def test_markers(self): + from _markerlib import interpret, default_environment, compile + + os_name = os.name + + self.assertTrue(interpret("")) + + self.assertTrue(interpret("os.name != 'buuuu'")) + self.assertTrue(interpret("python_version > '1.0'")) + self.assertTrue(interpret("python_version < '5.0'")) + self.assertTrue(interpret("python_version <= '5.0'")) + self.assertTrue(interpret("python_version >= '1.0'")) + self.assertTrue(interpret("'%s' in os.name" % os_name)) + self.assertTrue(interpret("'buuuu' not in os.name")) + + self.assertFalse(interpret("os.name == 'buuuu'")) + self.assertFalse(interpret("python_version < '1.0'")) + self.assertFalse(interpret("python_version > '5.0'")) + self.assertFalse(interpret("python_version >= '5.0'")) + self.assertFalse(interpret("python_version <= '1.0'")) + self.assertFalse(interpret("'%s' not in os.name" % os_name)) + self.assertFalse(interpret("'buuuu' in os.name and python_version >= '5.0'")) + + environment = default_environment() + environment['extra'] = 'test' + self.assertTrue(interpret("extra == 'test'", environment)) + self.assertFalse(interpret("extra == 'doc'", environment)) + + def raises_nameError(): + try: + interpret("python.version == '42'") + except NameError: + pass + else: + raise Exception("Expected NameError") + + raises_nameError() + + def raises_syntaxError(): + try: + interpret("(x for x in (4,))") + except SyntaxError: + pass + else: + raise Exception("Expected SyntaxError") + + raises_syntaxError() + + statement = "python_version == '5'" + self.assertEqual(compile(statement).__doc__, statement) + diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 0231eda8..9c4bfadb 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -1,27 +1,145 @@ """Package Index Tests """ -# More would be better! - -import os, shutil, tempfile, unittest, urllib2 +import sys +import unittest +import urllib2 import pkg_resources +import httplib +import distutils.errors import setuptools.package_index +from server import IndexServer class TestPackageIndex(unittest.TestCase): - def test_bad_urls(self): + def test_bad_url_bad_port(self): index = setuptools.package_index.PackageIndex() - url = 'http://127.0.0.1/nonesuch/test_package_index' + url = 'http://127.0.0.1:0/nonesuch/test_package_index' try: v = index.open_url(url) except Exception, v: - self.assert_(url in str(v)) + self.assertTrue(url in str(v)) else: - self.assert_(isinstance(v,urllib2.HTTPError)) + self.assertTrue(isinstance(v,urllib2.HTTPError)) + + def test_bad_url_typo(self): + # issue 16 + # easy_install inquant.contentmirror.plone breaks because of a typo + # in its home URL + index = setuptools.package_index.PackageIndex( + hosts=('www.example.com',) + ) + + url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk' + try: + v = index.open_url(url) + except Exception, v: + self.assertTrue(url in str(v)) + else: + self.assertTrue(isinstance(v, urllib2.HTTPError)) + + def test_bad_url_bad_status_line(self): + index = setuptools.package_index.PackageIndex( + hosts=('www.example.com',) + ) + + def _urlopen(*args): + import httplib + raise httplib.BadStatusLine('line') + + old_urlopen = urllib2.urlopen + urllib2.urlopen = _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 + + def test_bad_url_double_scheme(self): + """ + A bad URL with a double scheme should raise a DistutilsError. + """ + index = setuptools.package_index.PackageIndex( + hosts=('www.example.com',) + ) + + # issue 20 + url = 'http://http://svn.pythonpaste.org/Paste/wphp/trunk' + try: + index.open_url(url) + except distutils.errors.DistutilsError, error: + msg = unicode(error) + assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg + return + raise RuntimeError("Did not raise") + + def test_bad_url_screwy_href(self): + index = setuptools.package_index.PackageIndex( + hosts=('www.example.com',) + ) + + # issue #160 + if sys.version_info[0] == 2 and sys.version_info[1] == 7: + # this should not fail + url = 'http://example.com' + page = ('<a href="http://www.famfamfam.com](' + 'http://www.famfamfam.com/">') + index.process_index(url, page) def test_url_ok(self): index = setuptools.package_index.PackageIndex( hosts=('www.example.com',) ) url = 'file:///tmp/test_package_index' - self.assert_(index.url_ok(url, True)) + self.assertTrue(index.url_ok(url, True)) + + def test_links_priority(self): + """ + Download links from the pypi simple index should be used before + external download links. + http://bitbucket.org/tarek/distribute/issue/163/md5-validation-error + + Usecase : + - someone uploads a package on pypi, a md5 is generated + - someone manually copies this link (with the md5 in the url) onto an + external page accessible from the package page. + - someone reuploads the package (with a different md5) + - while easy_installing, an MD5 error occurs because the external link + is used + -> 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 + return + + # start an index server + server = IndexServer() + server.start() + index_url = server.base_url() + 'test_links_priority/simple/' + + # scan a test index + pi = setuptools.package_index.PackageIndex(index_url) + requirement = pkg_resources.Requirement.parse('foobar') + pi.find_packages(requirement) + server.stop() + + # the distribution has been found + self.assertTrue('foobar' in pi) + # we have only one link, because links are compared without md5 + self.assertTrue(len(pi['foobar'])==1) + # the link should be from the index + self.assertTrue('correct_md5' in pi['foobar'][0].location) + def test_parse_bdist_wininst(self): + self.assertEqual(setuptools.package_index.parse_bdist_wininst( + 'reportlab-2.5.win32-py2.4.exe'), ('reportlab-2.5', '2.4', 'win32')) + self.assertEqual(setuptools.package_index.parse_bdist_wininst( + 'reportlab-2.5.win32.exe'), ('reportlab-2.5', None, 'win32')) + self.assertEqual(setuptools.package_index.parse_bdist_wininst( + 'reportlab-2.5.win-amd64-py2.7.exe'), ('reportlab-2.5', '2.7', 'win-amd64')) + self.assertEqual(setuptools.package_index.parse_bdist_wininst( + 'reportlab-2.5.win-amd64.exe'), ('reportlab-2.5', None, 'win-amd64')) diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py index 03e5d0f8..9c2ed9c9 100644 --- a/setuptools/tests/test_resources.py +++ b/setuptools/tests/test_resources.py @@ -3,11 +3,21 @@ # NOTE: the shebang and encoding lines are for ScriptHeaderTests; do not remove from unittest import TestCase, makeSuite; from pkg_resources import * from setuptools.command.easy_install import get_script_header, is_sh -import os, pkg_resources, sys, StringIO +import os, pkg_resources, sys, StringIO, tempfile, shutil try: frozenset except NameError: from sets import ImmutableSet as frozenset +def safe_repr(obj, short=False): + """ copied from Python2.7""" + try: + result = repr(obj) + except Exception: + result = object.__repr__(obj) + if not short or len(result) < _MAX_LENGTH: + return result + return result[:_MAX_LENGTH] + ' [truncated]...' + class Metadata(EmptyProvider): """Mock object to return metadata as if from an on-disk distribution""" @@ -35,7 +45,7 @@ class DistroTests(TestCase): ad.add(Distribution.from_filename("FooPkg-1.2-py2.4.egg")) # Name is in there now - self.failUnless(ad['FooPkg']) + self.assertTrue(ad['FooPkg']) # But only 1 package self.assertEqual(list(ad), ['foopkg']) @@ -143,7 +153,7 @@ class DistroTests(TestCase): self.assertRaises(VersionConflict, ws.resolve, parse_requirements("Foo==0.9"), ad) ws = WorkingSet([]) # reset - + # Request an extra that causes an unresolved dependency for "Baz" self.assertRaises( DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad @@ -161,7 +171,7 @@ class DistroTests(TestCase): self.assertRaises( VersionConflict, ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad ) - + def testDistroDependsOptions(self): d = self.distRequires(""" Twisted>=1.5 @@ -188,21 +198,6 @@ class DistroTests(TestCase): self.assertRaises(UnknownExtra, d.requires, ["foo"]) - - - - - - - - - - - - - - - class EntryPointTests(TestCase): def assertfields(self, ep): @@ -210,7 +205,7 @@ class EntryPointTests(TestCase): self.assertEqual(ep.module_name,"setuptools.tests.test_resources") self.assertEqual(ep.attrs, ("EntryPointTests",)) self.assertEqual(ep.extras, ("x",)) - self.failUnless(ep.load() is EntryPointTests) + self.assertTrue(ep.load() is EntryPointTests) self.assertEqual( str(ep), "foo = setuptools.tests.test_resources:EntryPointTests [x]" @@ -310,20 +305,20 @@ class RequirementsTests(TestCase): foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg") twist11 = Distribution.from_filename("Twisted-1.1.egg") twist12 = Distribution.from_filename("Twisted-1.2.egg") - self.failUnless(parse_version('1.2') in r) - self.failUnless(parse_version('1.1') not in r) - self.failUnless('1.2' in r) - self.failUnless('1.1' not in r) - self.failUnless(foo_dist not in r) - self.failUnless(twist11 not in r) - self.failUnless(twist12 in r) + self.assertTrue(parse_version('1.2') in r) + self.assertTrue(parse_version('1.1') not in r) + self.assertTrue('1.2' in r) + self.assertTrue('1.1' not in r) + self.assertTrue(foo_dist not in r) + self.assertTrue(twist11 not in r) + self.assertTrue(twist12 in r) def testAdvancedContains(self): r, = parse_requirements("Foo>=1.2,<=1.3,==1.9,>2.0,!=2.5,<3.0,==4.5") for v in ('1.2','1.2.2','1.3','1.9','2.0.1','2.3','2.6','3.0c1','4.5'): - self.failUnless(v in r, (v,r)) + self.assertTrue(v in r, (v,r)) for v in ('1.2c1','1.3.1','1.5','1.9.1','2.0','2.5','3.0','4.0'): - self.failUnless(v not in r, (v,r)) + self.assertTrue(v not in r, (v,r)) def testOptionsAndHashing(self): @@ -341,21 +336,33 @@ class RequirementsTests(TestCase): ) def testVersionEquality(self): - r1 = Requirement.parse("setuptools==0.3a2") - r2 = Requirement.parse("setuptools!=0.3a4") + r1 = Requirement.parse("foo==0.3a2") + r2 = Requirement.parse("foo!=0.3a4") d = Distribution.from_filename - self.failIf(d("setuptools-0.3a4.egg") in r1) - self.failIf(d("setuptools-0.3a1.egg") in r1) - self.failIf(d("setuptools-0.3a4.egg") in r2) - - self.failUnless(d("setuptools-0.3a2.egg") in r1) - self.failUnless(d("setuptools-0.3a2.egg") in r2) - self.failUnless(d("setuptools-0.3a3.egg") in r2) - self.failUnless(d("setuptools-0.3a5.egg") in r2) + self.assertTrue(d("foo-0.3a4.egg") not in r1) + self.assertTrue(d("foo-0.3a1.egg") not in r1) + self.assertTrue(d("foo-0.3a4.egg") not in r2) + self.assertTrue(d("foo-0.3a2.egg") in r1) + self.assertTrue(d("foo-0.3a2.egg") in r2) + self.assertTrue(d("foo-0.3a3.egg") in r2) + self.assertTrue(d("foo-0.3a5.egg") in r2) + def testSetuptoolsProjectName(self): + """ + The setuptools project should implement the setuptools package. + """ + self.assertEqual( + Requirement.parse('setuptools').project_name, 'setuptools') + # setuptools 0.7 and higher means setuptools. + self.assertEqual( + Requirement.parse('setuptools == 0.7').project_name, 'setuptools') + self.assertEqual( + Requirement.parse('setuptools == 0.7a1').project_name, 'setuptools') + self.assertEqual( + Requirement.parse('setuptools >= 0.7').project_name, 'setuptools') @@ -452,7 +459,7 @@ class ParseTests(TestCase): def testVersionOrdering(self): def c(s1,s2): p1, p2 = parse_version(s1),parse_version(s2) - self.failUnless(p1<p2, (s1,s2,p1,p2)) + self.assertTrue(p1<p2, (s1,s2,p1,p2)) c('2.1','2.1.1') c('2a1','2b0') @@ -505,6 +512,19 @@ class ScriptHeaderTests(TestCase): '#!%s -x\n' % self.non_ascii_exe) def test_get_script_header_jython_workaround(self): + # This test doesn't work with Python 3 in some locales + if (sys.version_info >= (3,) and os.environ.get("LC_CTYPE") + in (None, "C", "POSIX")): + return + + class java: + class lang: + class System: + @staticmethod + def getProperty(property): + return "" + sys.modules["java"] = java + platform = sys.platform sys.platform = 'java1.5.0_13' stdout = sys.stdout @@ -517,17 +537,78 @@ class ScriptHeaderTests(TestCase): # Ensure we generate what is basically a broken shebang line # when there's options, with a warning emitted - sys.stdout = StringIO.StringIO() + sys.stdout = sys.stderr = StringIO.StringIO() self.assertEqual(get_script_header('#!/usr/bin/python -x', executable=exe), '#!%s -x\n' % exe) - self.assert_('Unable to adapt shebang line' in sys.stdout.getvalue()) - sys.stdout = StringIO.StringIO() + self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue()) + sys.stdout = sys.stderr = StringIO.StringIO() self.assertEqual(get_script_header('#!/usr/bin/python', executable=self.non_ascii_exe), '#!%s -x\n' % self.non_ascii_exe) - self.assert_('Unable to adapt shebang line' in sys.stdout.getvalue()) + self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue()) finally: + del sys.modules["java"] sys.platform = platform sys.stdout = stdout + + + +class NamespaceTests(TestCase): + + def setUp(self): + self._ns_pkgs = pkg_resources._namespace_packages.copy() + 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")) + + def tearDown(self): + shutil.rmtree(self._tmpdir) + pkg_resources._namespace_packages = self._ns_pkgs.copy() + sys.path = self._prev_sys_path[:] + + def _assertIn(self, member, container): + """ assertIn and assertTrue does not exist in Python2.3""" + if member not in container: + standardMsg = '%s not found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def test_two_levels_deep(self): + """ + Test nested namespace packages + Create namespace packages in the following tree : + site-packages-1/pkg1/pkg2 + site-packages-2/pkg1/pkg2 + Check both are in the _namespace_packages dict and that their __path__ + is correct + """ + sys.path.append(os.path.join(self._tmpdir, "site-pkgs2")) + os.makedirs(os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2")) + os.makedirs(os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2")) + ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n" + for site in ["site-pkgs", "site-pkgs2"]: + pkg1_init = open(os.path.join(self._tmpdir, site, + "pkg1", "__init__.py"), "w") + pkg1_init.write(ns_str) + pkg1_init.close() + pkg2_init = open(os.path.join(self._tmpdir, site, + "pkg1", "pkg2", "__init__.py"), "w") + pkg2_init.write(ns_str) + pkg2_init.close() + import pkg1 + self._assertIn("pkg1", pkg_resources._namespace_packages.keys()) + try: + import pkg1.pkg2 + except ImportError, e: + 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"]) + # check the __path__ attribute contains both paths + self.assertEqual(pkg1.pkg2.__path__, [ + os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"), + os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2") ]) + diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py new file mode 100644 index 00000000..1609ee86 --- /dev/null +++ b/setuptools/tests/test_sandbox.py @@ -0,0 +1,66 @@ +"""develop tests +""" +import sys +import os +import shutil +import unittest +import tempfile + +from setuptools.sandbox import DirectorySandbox, SandboxViolation + +def has_win32com(): + """ + Run this to determine if the local machine has win32com, and if it + does, include additional tests. + """ + if not sys.platform.startswith('win32'): + return False + try: + mod = __import__('win32com') + except ImportError: + return False + return True + +class TestSandbox(unittest.TestCase): + + def setUp(self): + self.dir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.dir) + + def test_devnull(self): + if sys.version < '2.4': + return + sandbox = DirectorySandbox(self.dir) + sandbox.run(self._file_writer(os.devnull)) + + def _file_writer(path): + def do_write(): + f = open(path, 'w') + f.write('xxx') + f.close() + return do_write + + _file_writer = staticmethod(_file_writer) + + if has_win32com(): + def test_win32com(self): + """ + win32com should not be prevented from caching COM interfaces + in gen_py. + """ + import win32com + gen_py = win32com.__gen_path__ + target = os.path.join(gen_py, 'test_write') + sandbox = DirectorySandbox(self.dir) + try: + try: + sandbox.run(self._file_writer(target)) + except SandboxViolation: + self.fail("Could not create gen_py file due to SandboxViolation") + finally: + if os.path.exists(target): os.remove(target) + +if __name__ == '__main__': + unittest.main() diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py new file mode 100644 index 00000000..a9d5d6e5 --- /dev/null +++ b/setuptools/tests/test_sdist.py @@ -0,0 +1,383 @@ +# -*- coding: utf-8 -*- +"""sdist tests""" + + +import os +import shutil +import sys +import tempfile +import unittest +import urllib +import unicodedata +from StringIO import StringIO + + +from setuptools.command.sdist import sdist +from setuptools.command.egg_info import manifest_maker +from setuptools.dist import Distribution + + +SETUP_ATTRS = { + 'name': 'sdist_test', + 'version': '0.0', + 'packages': ['sdist_test'], + 'package_data': {'sdist_test': ['*.txt']} +} + + +SETUP_PY = """\ +from setuptools import setup + +setup(**%r) +""" % SETUP_ATTRS + + +if sys.version_info >= (3,): + LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1') +else: + LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py' + + +# Cannot use context manager because of Python 2.4 +def quiet(): + global old_stdout, old_stderr + old_stdout, old_stderr = sys.stdout, sys.stderr + sys.stdout, sys.stderr = StringIO(), StringIO() + +def unquiet(): + sys.stdout, sys.stderr = old_stdout, old_stderr + + +# Fake byte literals for Python <= 2.5 +def b(s, encoding='utf-8'): + if sys.version_info >= (3,): + return s.encode(encoding) + return s + + +# Convert to POSIX path +def posix(path): + if sys.version_info >= (3,) and not isinstance(path, unicode): + return path.replace(os.sep.encode('ascii'), b('/')) + else: + return path.replace(os.sep, '/') + + +# HFS Plus uses decomposed UTF-8 +def decompose(path): + if isinstance(path, unicode): + return unicodedata.normalize('NFD', path) + try: + path = path.decode('utf-8') + path = unicodedata.normalize('NFD', path) + path = path.encode('utf-8') + except UnicodeError: + pass # Not UTF-8 + return path + + +class TestSdistTest(unittest.TestCase): + + def setUp(self): + self.temp_dir = tempfile.mkdtemp() + f = open(os.path.join(self.temp_dir, 'setup.py'), 'w') + f.write(SETUP_PY) + f.close() + # Set up the rest of the test package + test_pkg = os.path.join(self.temp_dir, 'sdist_test') + os.mkdir(test_pkg) + # *.rst was not included in package_data, so c.rst should not be + # automatically added to the manifest when not under version control + for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']: + # Just touch the files; their contents are irrelevant + open(os.path.join(test_pkg, fname), 'w').close() + + self.old_cwd = os.getcwd() + os.chdir(self.temp_dir) + + def tearDown(self): + os.chdir(self.old_cwd) + shutil.rmtree(self.temp_dir) + + def test_package_data_in_sdist(self): + """Regression test for pull request #4: ensures that files listed in + package_data are included in the manifest even if they're not added to + version control. + """ + + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # squelch output + quiet() + try: + cmd.run() + finally: + unquiet() + + manifest = cmd.filelist.files + self.assertTrue(os.path.join('sdist_test', 'a.txt') in manifest) + self.assertTrue(os.path.join('sdist_test', 'b.txt') in manifest) + self.assertTrue(os.path.join('sdist_test', 'c.rst') not in manifest) + + def test_manifest_is_written_with_utf8_encoding(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + mm = manifest_maker(dist) + mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + os.mkdir('sdist_test.egg-info') + + # UTF-8 filename + filename = os.path.join('sdist_test', 'smörbröd.py') + + # Add UTF-8 filename and write manifest + quiet() + try: + mm.run() + mm.filelist.files.append(filename) + mm.write_manifest() + finally: + unquiet() + + manifest = open(mm.manifest, 'rbU') + contents = manifest.read() + manifest.close() + + # The manifest should be UTF-8 encoded + try: + u_contents = contents.decode('UTF-8') + except UnicodeDecodeError, e: + self.fail(e) + + # The manifest should contain the UTF-8 filename + if sys.version_info >= (3,): + self.assertTrue(posix(filename) in u_contents) + else: + self.assertTrue(posix(filename) in contents) + + # Python 3 only + if sys.version_info >= (3,): + + def test_write_manifest_allows_utf8_filenames(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + mm = manifest_maker(dist) + mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + os.mkdir('sdist_test.egg-info') + + # UTF-8 filename + filename = os.path.join(b('sdist_test'), b('smörbröd.py')) + + # Add filename and write manifest + quiet() + try: + mm.run() + u_filename = filename.decode('utf-8') + mm.filelist.files.append(u_filename) + # Re-write manifest + mm.write_manifest() + finally: + unquiet() + + manifest = open(mm.manifest, 'rbU') + contents = manifest.read() + manifest.close() + + # The manifest should be UTF-8 encoded + try: + contents.decode('UTF-8') + except UnicodeDecodeError, e: + self.fail(e) + + # The manifest should contain the UTF-8 filename + self.assertTrue(posix(filename) in contents) + + # The filelist should have been updated as well + self.assertTrue(u_filename in mm.filelist.files) + + def test_write_manifest_skips_non_utf8_filenames(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + mm = manifest_maker(dist) + mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + os.mkdir('sdist_test.egg-info') + + # Latin-1 filename + filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) + + # Add filename with surrogates and write manifest + quiet() + try: + mm.run() + u_filename = filename.decode('utf-8', 'surrogateescape') + mm.filelist.files.append(u_filename) + # Re-write manifest + mm.write_manifest() + finally: + unquiet() + + manifest = open(mm.manifest, 'rbU') + contents = manifest.read() + manifest.close() + + # The manifest should be UTF-8 encoded + try: + contents.decode('UTF-8') + except UnicodeDecodeError, e: + self.fail(e) + + # The Latin-1 filename should have been skipped + self.assertFalse(posix(filename) in contents) + + # The filelist should have been updated as well + self.assertFalse(u_filename in mm.filelist.files) + + def test_manifest_is_read_with_utf8_encoding(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # Create manifest + quiet() + try: + cmd.run() + finally: + unquiet() + + # Add UTF-8 filename to manifest + filename = os.path.join(b('sdist_test'), b('smörbröd.py')) + cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + manifest = open(cmd.manifest, 'ab') + manifest.write(b('\n')+filename) + manifest.close() + + # The file must exist to be included in the filelist + open(filename, 'w').close() + + # Re-read manifest + cmd.filelist.files = [] + quiet() + try: + cmd.read_manifest() + finally: + unquiet() + + # The filelist should contain the UTF-8 filename + if sys.version_info >= (3,): + filename = filename.decode('utf-8') + self.assertTrue(filename in cmd.filelist.files) + + # Python 3 only + if sys.version_info >= (3,): + + def test_read_manifest_skips_non_utf8_filenames(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # Create manifest + quiet() + try: + cmd.run() + finally: + unquiet() + + # Add Latin-1 filename to manifest + filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) + cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') + manifest = open(cmd.manifest, 'ab') + manifest.write(b('\n')+filename) + manifest.close() + + # The file must exist to be included in the filelist + open(filename, 'w').close() + + # Re-read manifest + cmd.filelist.files = [] + quiet() + try: + try: + cmd.read_manifest() + except UnicodeDecodeError, e: + self.fail(e) + finally: + unquiet() + + # The Latin-1 filename should have been skipped + filename = filename.decode('latin-1') + self.assertFalse(filename in cmd.filelist.files) + + def test_sdist_with_utf8_encoded_filename(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # UTF-8 filename + filename = os.path.join(b('sdist_test'), b('smörbröd.py')) + open(filename, 'w').close() + + quiet() + try: + cmd.run() + finally: + unquiet() + + if sys.platform == 'darwin': + filename = decompose(filename) + + if sys.version_info >= (3,): + if sys.platform == 'win32': + # Python 3 mangles the UTF-8 filename + filename = filename.decode('cp1252') + self.assertTrue(filename in cmd.filelist.files) + else: + filename = filename.decode('utf-8') + self.assertTrue(filename in cmd.filelist.files) + else: + self.assertTrue(filename in cmd.filelist.files) + + def test_sdist_with_latin1_encoded_filename(self): + # Test for #303. + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + # Latin-1 filename + filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) + open(filename, 'w').close() + + quiet() + try: + cmd.run() + finally: + unquiet() + + if sys.version_info >= (3,): + filename = filename.decode('latin-1') + if sys.platform == 'win32': + # Latin-1 is similar to Windows-1252 + self.assertTrue(filename in cmd.filelist.files) + else: + # The Latin-1 filename should have been skipped + self.assertFalse(filename in cmd.filelist.files) + else: + # No conversion takes place under Python 2 and the file + # is included. We shall keep it that way for BBB. + self.assertTrue(filename in cmd.filelist.files) + + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) + diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py new file mode 100644 index 00000000..ad7cbd0f --- /dev/null +++ b/setuptools/tests/test_test.py @@ -0,0 +1,124 @@ +# -*- coding: UTF-8 -*- + +"""develop tests +""" +import sys +import os, shutil, tempfile, unittest +import tempfile +import site +from StringIO import StringIO + +from distutils.errors import DistutilsError +from setuptools.command.test import test +from setuptools.command import easy_install as easy_install_pkg +from setuptools.dist import Distribution + +SETUP_PY = """\ +from setuptools import setup + +setup(name='foo', + packages=['name', 'name.space', 'name.space.tests'], + namespace_packages=['name'], + test_suite='name.space.tests.test_suite', +) +""" + +NS_INIT = """# -*- coding: Latin-1 -*- +# Söme Arbiträry Ünicode to test Issüé 310 +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) +""" +# Make sure this is Latin-1 binary, before writing: +if sys.version_info < (3,): + NS_INIT = NS_INIT.decode('UTF-8') +NS_INIT = NS_INIT.encode('Latin-1') + +TEST_PY = """import unittest + +class TestTest(unittest.TestCase): + def test_test(self): + print "Foo" # Should fail under Python 3 unless 2to3 is used + +test_suite = unittest.makeSuite(TestTest) +""" + +class TestTestTest(unittest.TestCase): + + def setUp(self): + if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + return + + # Directory structure + self.dir = tempfile.mkdtemp() + os.mkdir(os.path.join(self.dir, 'name')) + os.mkdir(os.path.join(self.dir, 'name', 'space')) + os.mkdir(os.path.join(self.dir, 'name', 'space', 'tests')) + # setup.py + setup = os.path.join(self.dir, 'setup.py') + f = open(setup, 'wt') + f.write(SETUP_PY) + f.close() + self.old_cwd = os.getcwd() + # name/__init__.py + init = os.path.join(self.dir, 'name', '__init__.py') + f = open(init, 'wb') + f.write(NS_INIT) + f.close() + # name/space/__init__.py + init = os.path.join(self.dir, 'name', 'space', '__init__.py') + f = open(init, 'wt') + f.write('#empty\n') + f.close() + # name/space/tests/__init__.py + init = os.path.join(self.dir, 'name', 'space', 'tests', '__init__.py') + f = open(init, 'wt') + f.write(TEST_PY) + f.close() + + os.chdir(self.dir) + self.old_base = site.USER_BASE + site.USER_BASE = tempfile.mkdtemp() + self.old_site = site.USER_SITE + site.USER_SITE = tempfile.mkdtemp() + + def tearDown(self): + if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + return + + os.chdir(self.old_cwd) + shutil.rmtree(self.dir) + shutil.rmtree(site.USER_BASE) + shutil.rmtree(site.USER_SITE) + site.USER_BASE = self.old_base + site.USER_SITE = self.old_site + + def test_test(self): + if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + return + + dist = Distribution(dict( + name='foo', + packages=['name', 'name.space', 'name.space.tests'], + namespace_packages=['name'], + test_suite='name.space.tests.test_suite', + use_2to3=True, + )) + dist.script_name = 'setup.py' + cmd = test(dist) + cmd.user = 1 + cmd.ensure_finalized() + cmd.install_dir = site.USER_SITE + cmd.user = 1 + old_stdout = sys.stdout + sys.stdout = StringIO() + try: + try: # try/except/finally doesn't work in Python 2.4, so we need nested try-statements. + cmd.run() + except SystemExit: # The test runner calls sys.exit, stop that making an error. + pass + finally: + sys.stdout = old_stdout +
\ No newline at end of file diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py new file mode 100644 index 00000000..769f16cc --- /dev/null +++ b/setuptools/tests/test_upload_docs.py @@ -0,0 +1,72 @@ +"""build_ext tests +""" +import sys, os, shutil, tempfile, unittest, site, zipfile +from setuptools.command.upload_docs import upload_docs +from setuptools.dist import Distribution + +SETUP_PY = """\ +from setuptools import setup + +setup(name='foo') +""" + +class TestUploadDocsTest(unittest.TestCase): + def setUp(self): + self.dir = tempfile.mkdtemp() + setup = os.path.join(self.dir, 'setup.py') + f = open(setup, 'w') + f.write(SETUP_PY) + f.close() + self.old_cwd = os.getcwd() + os.chdir(self.dir) + + self.upload_dir = os.path.join(self.dir, 'build') + os.mkdir(self.upload_dir) + + # A test document. + f = open(os.path.join(self.upload_dir, 'index.html'), 'w') + f.write("Hello world.") + f.close() + + # An empty folder. + os.mkdir(os.path.join(self.upload_dir, 'empty')) + + if sys.version >= "2.6": + self.old_base = site.USER_BASE + site.USER_BASE = upload_docs.USER_BASE = tempfile.mkdtemp() + self.old_site = site.USER_SITE + site.USER_SITE = upload_docs.USER_SITE = tempfile.mkdtemp() + + def tearDown(self): + os.chdir(self.old_cwd) + shutil.rmtree(self.dir) + if sys.version >= "2.6": + shutil.rmtree(site.USER_BASE) + shutil.rmtree(site.USER_SITE) + site.USER_BASE = self.old_base + site.USER_SITE = self.old_site + + def test_create_zipfile(self): + # Test to make sure zipfile creation handles common cases. + # This explicitly includes a folder containing an empty folder. + + dist = Distribution() + + cmd = upload_docs(dist) + cmd.upload_dir = self.upload_dir + cmd.target_dir = self.upload_dir + tmp_dir = tempfile.mkdtemp() + tmp_file = os.path.join(tmp_dir, 'foo.zip') + try: + zip_file = cmd.create_zipfile(tmp_file) + + assert zipfile.is_zipfile(tmp_file) + + zip_file = zipfile.ZipFile(tmp_file) # woh... + + assert zip_file.namelist() == ['index.html'] + + zip_file.close() + finally: + shutil.rmtree(tmp_dir) + diff --git a/setuptools/tests/win_script_wrapper.txt b/setuptools/tests/win_script_wrapper.txt index 2d95502e..9f7c81d6 100644 --- a/setuptools/tests/win_script_wrapper.txt +++ b/setuptools/tests/win_script_wrapper.txt @@ -1,137 +1,151 @@ -Python Script Wrapper for Windows
-=================================
-
-setuptools includes wrappers for Python scripts that allows them to be
-executed like regular windows programs. There are 2 wrappers, once
-for command-line programs, cli.exe, and one for graphica programs,
-gui.exe. These programs are almost identical, function pretty much
-the same way, and are generated from the same source file. The
-wrapper programs are used by copying them to the directory containing
-the script they are to wrap and with the same name as the script they
-are to wrap. In the rest of this document, we'll give an example that
-will illustrate this.
-
-Let's create a simple script, foo-script.py:
-
- >>> import os, sys, tempfile
- >>> from setuptools.command.easy_install import nt_quote_arg
- >>> sample_directory = tempfile.mkdtemp()
- >>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').write(
- ... """#!%(python_exe)s
- ... import sys
- ... input = repr(sys.stdin.read())
- ... print sys.argv[0][-14:]
- ... print sys.argv[1:]
- ... print input
- ... if __debug__:
- ... print 'non-optimized'
- ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
-
-Note that the script starts with a Unix-style '#!' line saying which
-Python executable to run. The wrapper will use this to find the
-correct Python executable.
-
-We'll also copy cli.exe to the sample-directory with the name foo.exe:
-
- >>> import pkg_resources
- >>> open(os.path.join(sample_directory, 'foo.exe'), 'wb').write(
- ... pkg_resources.resource_string('setuptools', 'cli.exe')
- ... )
-
-When the copy of cli.exe, foo.exe in this example, runs, it examines
-the path name it was run with and computes a Python script path name
-by removing the '.exe' suffic and adding the '-script.py' suffix. (For
-GUI programs, the suffix '-script-pyw' is added.) This is why we
-named out script the way we did. Now we can run out script by running
-the wrapper:
-
- >>> import os
- >>> input, output = os.popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'foo.exe'))
- ... + r' arg1 "arg 2" "arg \"2\\\"" "arg 4\\" "arg5 a\\b"')
- >>> input.write('hello\nworld\n')
- >>> input.close()
- >>> print output.read(),
- \foo-script.py
- ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
- 'hello\nworld\n'
- non-optimized
-
-This example was a little pathological in that it exercised windows
-(MS C runtime) quoting rules:
-
-- Strings containing spaces are surrounded by double quotes.
-
-- Double quotes in strings need to be escaped by preceding them with
- back slashes.
-
-- One or more backslashes preceding double quotes quotes need to be
- escaped by preceding each of them them with back slashes.
-
-
-Specifying Python Command-line Options
---------------------------------------
-
-You can specify a single argument on the '#!' line. This can be used
-to specify Python options like -O, to run in optimized mode or -i
-to start the interactive interpreter. You can combine multiple
-options as usual. For example, to run in optimized mode and
-enter the interpreter after running the script, you could use -Oi:
-
- >>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').write(
- ... """#!%(python_exe)s -Oi
- ... import sys
- ... input = repr(sys.stdin.read())
- ... print sys.argv[0][-14:]
- ... print sys.argv[1:]
- ... print input
- ... if __debug__:
- ... print 'non-optimized'
- ... sys.ps1 = '---'
- ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
-
- >>> input, output = os.popen4(nt_quote_arg(os.path.join(sample_directory, 'foo.exe')))
- >>> input.close()
- >>> print output.read(),
- \foo-script.py
- []
- ''
- ---
-
-Testing the GUI Version
------------------------
-
-Now let's test the GUI version with the simple scipt, bar-script.py:
-
- >>> import os, sys, tempfile
- >>> from setuptools.command.easy_install import nt_quote_arg
- >>> sample_directory = tempfile.mkdtemp()
- >>> open(os.path.join(sample_directory, 'bar-script.pyw'), 'w').write(
- ... """#!%(python_exe)s
- ... import sys
- ... open(sys.argv[1], 'wb').write(repr(sys.argv[2]))
- ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
-
-We'll also copy gui.exe to the sample-directory with the name bar.exe:
-
- >>> import pkg_resources
- >>> open(os.path.join(sample_directory, 'bar.exe'), 'wb').write(
- ... pkg_resources.resource_string('setuptools', 'gui.exe')
- ... )
-
-Finally, we'll run the script and check the result:
-
- >>> import os
- >>> input, output = os.popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'bar.exe'))
- ... + r' "%s" "Test Argument"' % os.path.join(sample_directory, 'test_output.txt'))
- >>> input.close()
- >>> print output.read()
- <BLANKLINE>
- >>> print open(os.path.join(sample_directory, 'test_output.txt'), 'rb').read()
- 'Test Argument'
-
-
-We're done with the sample_directory:
-
- >>> import shutil
- >>> shutil.rmtree(sample_directory)
-
+Python Script Wrapper for Windows +================================= + +setuptools includes wrappers for Python scripts that allows them to be +executed like regular windows programs. There are 2 wrappers, once +for command-line programs, cli.exe, and one for graphica programs, +gui.exe. These programs are almost identical, function pretty much +the same way, and are generated from the same source file. The +wrapper programs are used by copying them to the directory containing +the script they are to wrap and with the same name as the script they +are to wrap. In the rest of this document, we'll give an example that +will illustrate this. + +Let's create a simple script, foo-script.py: + + >>> import os, sys, tempfile + >>> from setuptools.command.easy_install import nt_quote_arg + >>> sample_directory = tempfile.mkdtemp() + >>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w') + >>> f.write( + ... """#!%(python_exe)s + ... import sys + ... input = repr(sys.stdin.read()) + ... print sys.argv[0][-14:] + ... print sys.argv[1:] + ... print input + ... if __debug__: + ... print 'non-optimized' + ... """ % dict(python_exe=nt_quote_arg(sys.executable))) + >>> f.close() + +Note that the script starts with a Unix-style '#!' line saying which +Python executable to run. The wrapper will use this to find the +correct Python executable. + +We'll also copy cli.exe to the sample-directory with the name foo.exe: + + >>> import pkg_resources + >>> f = open(os.path.join(sample_directory, 'foo.exe'), 'wb') + >>> f.write( + ... pkg_resources.resource_string('setuptools', 'cli.exe') + ... ) + >>> f.close() + +When the copy of cli.exe, foo.exe in this example, runs, it examines +the path name it was run with and computes a Python script path name +by removing the '.exe' suffic and adding the '-script.py' suffix. (For +GUI programs, the suffix '-script-pyw' is added.) This is why we +named out script the way we did. Now we can run out script by running +the wrapper: + + >>> import os + >>> input, output = os.popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'foo.exe')) + ... + r' arg1 "arg 2" "arg \"2\\\"" "arg 4\\" "arg5 a\\b"') + >>> input.write('hello\nworld\n') + >>> input.close() + >>> print output.read(), + \foo-script.py + ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b'] + 'hello\nworld\n' + non-optimized + +This example was a little pathological in that it exercised windows +(MS C runtime) quoting rules: + +- Strings containing spaces are surrounded by double quotes. + +- Double quotes in strings need to be escaped by preceding them with + back slashes. + +- One or more backslashes preceding double quotes quotes need to be + escaped by preceding each of them them with back slashes. + + +Specifying Python Command-line Options +-------------------------------------- + +You can specify a single argument on the '#!' line. This can be used +to specify Python options like -O, to run in optimized mode or -i +to start the interactive interpreter. You can combine multiple +options as usual. For example, to run in optimized mode and +enter the interpreter after running the script, you could use -Oi: + + >>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w') + >>> f.write( + ... """#!%(python_exe)s -Oi + ... import sys + ... input = repr(sys.stdin.read()) + ... print sys.argv[0][-14:] + ... print sys.argv[1:] + ... print input + ... if __debug__: + ... print 'non-optimized' + ... sys.ps1 = '---' + ... """ % dict(python_exe=nt_quote_arg(sys.executable))) + >>> f.close() + + >>> input, output = os.popen4(nt_quote_arg(os.path.join(sample_directory, 'foo.exe'))) + >>> input.close() + >>> print output.read(), + \foo-script.py + [] + '' + --- + +Testing the GUI Version +----------------------- + +Now let's test the GUI version with the simple scipt, bar-script.py: + + >>> import os, sys, tempfile + >>> from setuptools.command.easy_install import nt_quote_arg + >>> sample_directory = tempfile.mkdtemp() + >>> f = open(os.path.join(sample_directory, 'bar-script.pyw'), 'w') + >>> f.write( + ... """#!%(python_exe)s + ... import sys + ... f = open(sys.argv[1], 'wb') + ... f.write(repr(sys.argv[2])) + ... f.close() + ... """ % dict(python_exe=nt_quote_arg(sys.executable))) + >>> f.close() + +We'll also copy gui.exe to the sample-directory with the name bar.exe: + + >>> import pkg_resources + >>> f = open(os.path.join(sample_directory, 'bar.exe'), 'wb') + >>> f.write( + ... pkg_resources.resource_string('setuptools', 'gui.exe') + ... ) + >>> f.close() + +Finally, we'll run the script and check the result: + + >>> import os + >>> input, output = os.popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'bar.exe')) + ... + r' "%s" "Test Argument"' % os.path.join(sample_directory, 'test_output.txt')) + >>> input.close() + >>> print output.read() + <BLANKLINE> + >>> f = open(os.path.join(sample_directory, 'test_output.txt'), 'rb') + >>> print f.read() + 'Test Argument' + >>> f.close() + + +We're done with the sample_directory: + + >>> import shutil + >>> shutil.rmtree(sample_directory) + @@ -1,5 +1,5 @@ def __boot(): - import sys, imp, os, os.path + import sys, os, os.path PYTHONPATH = os.environ.get('PYTHONPATH') if PYTHONPATH is None or (sys.platform=='win32' and not PYTHONPATH): PYTHONPATH = [] @@ -23,6 +23,7 @@ def __boot(): break else: try: + import imp # Avoid import loop in Python >= 3.3 stream, path, descr = imp.find_module('site',[item]) except ImportError: continue diff --git a/test.sh b/test.sh new file mode 100644 index 00000000..ed6d4efc --- /dev/null +++ b/test.sh @@ -0,0 +1,67 @@ +#!/bin/sh +echo -n "Running tests for Python 2.4..." +python2.4 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + +echo -n "Running tests for Python 2.5..." +python2.5 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + +echo -n "Running tests for Python 2.6..." +python2.6 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + +echo -n "Running tests for Python 2.7..." +python2.7 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + +rm -rf build +echo -n "Running tests for Python 3.1..." +python3.1 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + +rm -rf build +echo -n "Running tests for Python 3.2..." +python3.2 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + +rm -rf build +echo -n "Running tests for Python 3.3..." +python3.3 setup.py -q test > /dev/null 2> /dev/null +if [ $? -ne 0 ];then + echo "Failed" + exit $1 +else + echo "Success" +fi + diff --git a/api_tests.txt b/tests/api_tests.txt index 735ad8dd..6cf6e66f 100755..100644 --- a/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 + >>> from pkg_resources import WorkingSet, VersionConflict 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.py...'] + >>> ws.entries == ['http://example.com/something', pkg_resources.__file__] + True 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 @@ -208,11 +208,11 @@ You can ask a WorkingSet to ``find()`` a distribution matching a requirement:: Note that asking for a conflicting version of a distribution already in a working set triggers a ``pkg_resources.VersionConflict`` error: - >>> ws.find(Requirement.parse("Bar==1.0")) # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - VersionConflict: (Bar 0.9 (http://example.com/something), - Requirement.parse('Bar==1.0')) + >>> try: + ... ws.find(Requirement.parse("Bar==1.0")) + ... except VersionConflict: + ... print 'ok' + ok You can subscribe a callback function to receive notifications whenever a new distribution is added to a working set. The callback is immediately invoked diff --git a/tests/manual_test.py b/tests/manual_test.py new file mode 100644 index 00000000..44cf2d06 --- /dev/null +++ b/tests/manual_test.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +import sys + +if sys.version_info[0] >= 3: + raise NotImplementedError('Py3 not supported in this test yet') + +import os +import shutil +import tempfile +from distutils.command.install import INSTALL_SCHEMES +from string import Template +from urllib2 import urlopen + +try: + import subprocess + def _system_call(*args): + assert subprocess.call(args) == 0 +except ImportError: + # Python 2.3 + def _system_call(*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] + assert os.system(' '.join(args)) == 0 + +def tempdir(func): + def _tempdir(*args, **kwargs): + test_dir = tempfile.mkdtemp() + old_dir = os.getcwd() + os.chdir(test_dir) + try: + return func(*args, **kwargs) + finally: + os.chdir(old_dir) + shutil.rmtree(test_dir) + return _tempdir + +SIMPLE_BUILDOUT = """\ +[buildout] + +parts = eggs + +[eggs] +recipe = zc.recipe.egg + +eggs = + extensions +""" + +BOOTSTRAP = 'http://downloads.buildout.org/1/bootstrap.py' +PYVER = sys.version.split()[0][:3] + +_VARS = {'base': '.', + 'py_version_short': PYVER} + +if sys.platform == 'win32': + PURELIB = INSTALL_SCHEMES['nt']['purelib'] +else: + PURELIB = INSTALL_SCHEMES['unix_prefix']['purelib'] + + +@tempdir +def test_virtualenv(): + """virtualenv with setuptools""" + purelib = os.path.abspath(Template(PURELIB).substitute(**_VARS)) + _system_call('virtualenv', '--no-site-packages', '.') + _system_call('bin/easy_install', 'setuptools==dev') + # linux specific + site_pkg = os.listdir(purelib) + site_pkg.sort() + 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 'setuptools' in res + +@tempdir +def test_full(): + """virtualenv + pip + buildout""" + _system_call('virtualenv', '--no-site-packages', '.') + _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') + + with open('buildout.cfg', 'w') as f: + f.write(SIMPLE_BUILDOUT) + + with open('bootstrap.py', 'w') as f: + f.write(urlopen(BOOTSTRAP).read()) + + _system_call('bin/python', 'bootstrap.py') + _system_call('bin/buildout', '-q') + eggs = os.listdir('eggs') + eggs.sort() + assert len(eggs) == 3 + 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_ez_setup.py b/tests/test_ez_setup.py new file mode 100644 index 00000000..922bd884 --- /dev/null +++ b/tests/test_ez_setup.py @@ -0,0 +1,63 @@ +import sys +import os +import tempfile +import unittest +import shutil +import copy + +CURDIR = os.path.abspath(os.path.dirname(__file__)) +TOPDIR = os.path.split(CURDIR)[0] +sys.path.insert(0, TOPDIR) + +from ez_setup import (use_setuptools, _build_egg, _python_cmd, _do_download, + _install, DEFAULT_URL, DEFAULT_VERSION) +import ez_setup + +class TestSetup(unittest.TestCase): + + def urlopen(self, url): + return open(self.tarball) + + def setUp(self): + self.old_sys_path = copy.copy(sys.path) + self.cwd = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + os.chdir(TOPDIR) + _python_cmd("setup.py", "-q", "egg_info", "-RDb", "''", "sdist", + "--dist-dir", "%s" % self.tmpdir) + tarball = os.listdir(self.tmpdir)[0] + self.tarball = os.path.join(self.tmpdir, tarball) + import urllib2 + urllib2.urlopen = self.urlopen + + def tearDown(self): + shutil.rmtree(self.tmpdir) + os.chdir(self.cwd) + sys.path = copy.copy(self.old_sys_path) + + def test_build_egg(self): + # making it an egg + egg = _build_egg(self.tarball, self.tmpdir) + + # now trying to import it + sys.path[0] = egg + import setuptools + self.assertTrue(setuptools.__file__.startswith(egg)) + + def test_do_download(self): + tmpdir = tempfile.mkdtemp() + _do_download(DEFAULT_VERSION, DEFAULT_URL, tmpdir, 1) + import setuptools + self.assertTrue(setuptools.bootstrap_install_from.startswith(tmpdir)) + + def test_install(self): + def _faked(*args): + return True + ez_setup.python_cmd = _faked + _install(self.tarball) + + def test_use_setuptools(self): + self.assertEqual(use_setuptools(), None) + +if __name__ == '__main__': + unittest.main() diff --git a/version b/version deleted file mode 100755 index d7318d71..00000000 --- a/version +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/local/bin/invoke /usr/bin/peak version-config - -# This is a PEAK 'version' tool configuration file, that's -# also executable. PJE uses it to bump version numbers in -# the various parts of the project without having to edit them -# by hand. The current version is stored in the version.dat -# file. - -# These are not the droids you're looking for. You can go on -# about your business... - -<Scheme default> - DefaultFormat full - part major - part minor - part status choice alpha beta "release candidate" final - part build - part date timestamp - - <Formats> - trailer remap status "a%(build)s" "b%(build)s" "c%(build)s" "%(dot-maint)s" - dot-maint optional build ".%(build)s" - full "%(major)s.%(minor)s %(status)s %(build)s" - short "%(major)s.%(minor)s%(trailer)s" - </Formats> -</Scheme> - -<Module> - Name setuptools - - <Edit> - File setup.py - File ez_setup.py - Match 'VERSION = "%(short)s"' - </Edit> - - <Edit> - File release.sh - Match 'VERSION="%(short)s"' - </Edit> - - <Edit> - File setuptools/__init__.py - Match "__version__ = '%(short)s'" - </Edit> - -</Module> - diff --git a/version.dat b/version.dat deleted file mode 100755 index 32144c8e..00000000 --- a/version.dat +++ /dev/null @@ -1,6 +0,0 @@ -[setuptools]
-status = 'release candidate'
-major = 0
-build = 9
-minor = 6
-
diff --git a/virtual-python.py b/virtual-python.py deleted file mode 100755 index 8624f37d..00000000 --- a/virtual-python.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Create a "virtual" Python installation - -Based on a script created by Ian Bicking.""" - -import sys, os, optparse, shutil -join = os.path.join -py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) - -def mkdir(path): - if not os.path.exists(path): - print 'Creating %s' % path - os.makedirs(path) - else: - if verbose: - print 'Directory %s already exists' - -def symlink(src, dest): - if not os.path.exists(dest): - if verbose: - print 'Creating symlink %s' % dest - os.symlink(src, dest) - else: - print 'Symlink %s already exists' % dest - - -def rmtree(dir): - if os.path.exists(dir): - print 'Deleting tree %s' % dir - shutil.rmtree(dir) - else: - if verbose: - print 'Do not need to delete %s; already gone' % dir - -def make_exe(fn): - if os.name == 'posix': - oldmode = os.stat(fn).st_mode & 07777 - newmode = (oldmode | 0555) & 07777 - os.chmod(fn, newmode) - if verbose: - print 'Changed mode of %s to %s' % (fn, oct(newmode)) - -def main(): - if os.name != 'posix': - print "This script only works on Unix-like platforms, sorry." - return - - parser = optparse.OptionParser() - - parser.add_option('-v', '--verbose', action='count', dest='verbose', - default=0, help="Increase verbosity") - - parser.add_option('--prefix', dest="prefix", default='~', - help="The base directory to install to (default ~)") - - parser.add_option('--clear', dest='clear', action='store_true', - help="Clear out the non-root install and start from scratch") - - parser.add_option('--no-site-packages', dest='no_site_packages', - action='store_true', - help="Don't copy the contents of the global site-packages dir to the " - "non-root site-packages") - - options, args = parser.parse_args() - global verbose - - home_dir = os.path.expanduser(options.prefix) - lib_dir = join(home_dir, 'lib', py_version) - inc_dir = join(home_dir, 'include', py_version) - bin_dir = join(home_dir, 'bin') - - if sys.executable.startswith(bin_dir): - print 'Please use the *system* python to run this script' - return - - verbose = options.verbose - assert not args, "No arguments allowed" - - if options.clear: - rmtree(lib_dir) - rmtree(inc_dir) - print 'Not deleting', bin_dir - - prefix = sys.prefix - mkdir(lib_dir) - stdlib_dir = join(prefix, 'lib', py_version) - for fn in os.listdir(stdlib_dir): - if fn != 'site-packages': - symlink(join(stdlib_dir, fn), join(lib_dir, fn)) - - mkdir(join(lib_dir, 'site-packages')) - if not options.no_site_packages: - for fn in os.listdir(join(stdlib_dir, 'site-packages')): - symlink(join(stdlib_dir, 'site-packages', fn), - join(lib_dir, 'site-packages', fn)) - - mkdir(inc_dir) - stdinc_dir = join(prefix, 'include', py_version) - for fn in os.listdir(stdinc_dir): - symlink(join(stdinc_dir, fn), join(inc_dir, fn)) - - if sys.exec_prefix != sys.prefix: - exec_dir = join(sys.exec_prefix, 'lib', py_version) - for fn in os.listdir(exec_dir): - symlink(join(exec_dir, fn), join(lib_dir, fn)) - - mkdir(bin_dir) - print 'Copying %s to %s' % (sys.executable, bin_dir) - py_executable = join(bin_dir, 'python') - if sys.executable != py_executable: - shutil.copyfile(sys.executable, py_executable) - make_exe(py_executable) - - pydistutils = os.path.expanduser('~/.pydistutils.cfg') - if os.path.exists(pydistutils): - print 'Please make sure you remove any previous custom paths from' - print "your", pydistutils, "file." - - print "You're now ready to download ez_setup.py, and run" - print py_executable, "ez_setup.py" - -if __name__ == '__main__': - main() - diff --git a/wikiup.cfg b/wikiup.cfg deleted file mode 100755 index 58619158..00000000 --- a/wikiup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[PEAK] -EasyInstall = EasyInstall.txt -setuptools = setuptools.txt -PkgResources = pkg_resources.txt -EggFormats = doc/formats.txt |