mirror of
https://github.com/kennethreitz/heroku-buildpack-python.git
synced 2026-06-05 23:10:16 +00:00
Compare commits
296 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8be04ea656 | |||
| de7c16d942 | |||
| 5ea843458a | |||
| 2c16539190 | |||
| ed79e61a2f | |||
| b7bcc69722 | |||
| 3dde375d0b | |||
| 483e30a5ba | |||
| e783556e6b | |||
| 5645a433dc | |||
| 715ab8b96a | |||
| 5f96190eb5 | |||
| c579162ef9 | |||
| a5cca6de75 | |||
| b7f1157693 | |||
| ae7b6fc715 | |||
| e0c3b72950 | |||
| 566f7f4555 | |||
| 4ff62b2b41 | |||
| d47970fd81 | |||
| 69bdca063f | |||
| 63cea99415 | |||
| 5ecd27e3b8 | |||
| 206a2dbc04 | |||
| 2a083791b6 | |||
| d5b2b0b464 | |||
| 2eab1ad845 | |||
| 2b16420d41 | |||
| 9fa0889499 | |||
| 99b17fabeb | |||
| 933d3014d7 | |||
| 018e0f31de | |||
| cf1647e937 | |||
| b9f154bf38 | |||
| 4cc18ce0af | |||
| f5ea1c24a3 | |||
| 952b0bb735 | |||
| 8b7edad8a2 | |||
| 02787ac910 | |||
| bcc3ba09ca | |||
| 31e65dc58b | |||
| 9a227e6c73 | |||
| ddc9c24d34 | |||
| 42488a2be7 | |||
| 839486c25b | |||
| 5d56200179 | |||
| fef1d4f7c0 | |||
| 43defcc17e | |||
| f78460e2a7 | |||
| 3448923b69 | |||
| 58a13bc268 | |||
| c18038f986 | |||
| d4356a1f4c | |||
| 3c8f478b65 | |||
| 23493302e6 | |||
| 35ceaf728d | |||
| 5c9412fc33 | |||
| 825c9329eb | |||
| 9ebb990716 | |||
| f964209589 | |||
| 2e699d392e | |||
| 431ecee509 | |||
| 90696215ae | |||
| 5b55261700 | |||
| 727ff90acf | |||
| 0060fdacd8 | |||
| 85ad1fa988 | |||
| 56c022ca41 | |||
| f68e0989ea | |||
| 7381117bb8 | |||
| c9209ebb3d | |||
| 35345d11f5 | |||
| 6ee17b4c26 | |||
| 86267f93a1 | |||
| 50642e7069 | |||
| 8349f291d2 | |||
| 1aad21382e | |||
| 686ee087a5 | |||
| f137ba213a | |||
| 5ae51010cc | |||
| 29d8e7f670 | |||
| 5be3e0ce52 | |||
| ddde30a12e | |||
| 2db5578998 | |||
| 290250836f | |||
| 0ff4df4dc3 | |||
| 9177aa2c08 | |||
| ad791dc7f8 | |||
| 6b298ff7f3 | |||
| 01e30e938c | |||
| e81632e05e | |||
| 4ab3358851 | |||
| 4ded988907 | |||
| 8637dc05c0 | |||
| 6f65ad0b97 | |||
| c5ec378ea7 | |||
| 37013c2eca | |||
| 54a8a6407e | |||
| 69360fb804 | |||
| 92d296ec24 | |||
| 99557332a0 | |||
| a3d9c5a1b2 | |||
| 235ff64a38 | |||
| 0f7fca9e03 | |||
| 161bf49a3a | |||
| 7ac90f19dd | |||
| 90cc426c82 | |||
| 550a239553 | |||
| 57fea1bd6f | |||
| d758545164 | |||
| 5935632b8b | |||
| 8f3a7b7947 | |||
| 2f47bd12fd | |||
| 86fb607dd4 | |||
| 80823b1e16 | |||
| ff6f56b8c5 | |||
| 5393ba3cb4 | |||
| 4ade6c0457 | |||
| e5c389e677 | |||
| ea27b5a77c | |||
| 18c19e4466 | |||
| 0ea950ee52 | |||
| 2d3033fc71 | |||
| 1687f1cffe | |||
| 2f0263a5b8 | |||
| dfbf20acd2 | |||
| 0b9019ab71 | |||
| b0fc2245f4 | |||
| 113c669041 | |||
| b8b5e12a4a | |||
| bece3de495 | |||
| 89120f7797 | |||
| 99e035495b | |||
| 0c56b0c35c | |||
| 111f92e368 | |||
| 03e666ffea | |||
| 40598be9d4 | |||
| 8f057f3c9b | |||
| 972e6f8054 | |||
| aeb7ef392a | |||
| 930a63d55c | |||
| 544de727a0 | |||
| 466e6b5fe3 | |||
| 1abaaf6cf1 | |||
| 48bd910207 | |||
| 87c580f819 | |||
| db1df1ac67 | |||
| 4005d7ea50 | |||
| d29d4404c3 | |||
| 33779576bd | |||
| 3bddd66637 | |||
| e00a407c91 | |||
| 117dcff4e1 | |||
| 7243afd5b5 | |||
| 0a75bc9744 | |||
| ca3b3808b9 | |||
| 287b398826 | |||
| 76d2eda80e | |||
| 880c498c3f | |||
| 12f053ef7f | |||
| 35ee2f60bd | |||
| 3e480e9aea | |||
| 46ba98a4ee | |||
| 958de9441a | |||
| 74312f5487 | |||
| ecf5278679 | |||
| 863abfb00e | |||
| f782693537 | |||
| a2c2411109 | |||
| 712f02e359 | |||
| 61477e515a | |||
| be9343534a | |||
| 2b4e9be4ad | |||
| 382dff7e3b | |||
| 6f2012c1c9 | |||
| c71fe81f8d | |||
| 91586d6690 | |||
| 046726b864 | |||
| ddf353cff5 | |||
| 160497cb64 | |||
| 6194f94aa7 | |||
| e288332f0e | |||
| e4979f33e3 | |||
| 03c3aebca8 | |||
| 487a5ffb95 | |||
| 01d15d4d0e | |||
| 6655794b4d | |||
| 4beb57a5a0 | |||
| bac517e755 | |||
| 62b68cfcd9 | |||
| 481e800a7c | |||
| 9a7c6c339d | |||
| 23340bcfee | |||
| 102979e985 | |||
| ab9a4fbe5f | |||
| aad72f8e81 | |||
| 72295bf186 | |||
| c45a2c14b2 | |||
| ee177b28ff | |||
| 2e0f2f59a0 | |||
| 9856efa7c4 | |||
| 34878b9638 | |||
| 27abdfe7d7 | |||
| 3b0bdba799 | |||
| b547d8c30c | |||
| 1bd317cd50 | |||
| 60b57fd368 | |||
| 27a22e8ccb | |||
| 0378bbb491 | |||
| bccec5cee9 | |||
| d6fbb29977 | |||
| 9d0bf98a52 | |||
| 795272d625 | |||
| c3866f80b6 | |||
| 580a662f36 | |||
| 5767e0df04 | |||
| eeb63f215a | |||
| 5a6e4e49b4 | |||
| b6eada4e67 | |||
| bbe0acf001 | |||
| 05e65801fa | |||
| 08338a4a26 | |||
| bf913e0016 | |||
| 774ceb3367 | |||
| 335a14c714 | |||
| 4dea56d4a0 | |||
| c9916dd2e3 | |||
| 662a311f95 | |||
| bfd6ea22ca | |||
| c5d7991468 | |||
| c4404694e1 | |||
| e914736956 | |||
| 23833dda9b | |||
| 15573b9d3f | |||
| 86661c1cda | |||
| 9cbace7f76 | |||
| 74cb870488 | |||
| ae9b83f07c | |||
| d70c681c32 | |||
| c312d917a1 | |||
| 5d2ecd2f1f | |||
| a1e1dbd865 | |||
| 12f28894ce | |||
| 538ef6e378 | |||
| 1588ea44b2 | |||
| 3cc9098e25 | |||
| 436e945a91 | |||
| f9b83a4e95 | |||
| b1f8bf9f5e | |||
| 7af5175942 | |||
| c24a9096de | |||
| 36d27c46ed | |||
| 713fa36bca | |||
| 20249f9672 | |||
| 8b55e73f77 | |||
| 2058b3c5bf | |||
| 4eb17eb9a9 | |||
| 7138b7afb2 | |||
| fe9b9401d3 | |||
| 8c870cabfb | |||
| b8182c077a | |||
| fbbdadd347 | |||
| b54faf36f5 | |||
| a0dc2a6f33 | |||
| f775fca31c | |||
| efebff4ce8 | |||
| 94b2ab43a5 | |||
| a21dbc2da1 | |||
| cf5a9d26d1 | |||
| ef4772b87a | |||
| ffda318476 | |||
| 6aff0d0197 | |||
| b9ebd33703 | |||
| 61e17df22d | |||
| b88d0ad800 | |||
| 7e24f94164 | |||
| 3a239aa71a | |||
| 0e1ae7a798 | |||
| 133c308796 | |||
| 7ea8c861a2 | |||
| 20e0af2bf4 | |||
| 5d0ed4e7cd | |||
| 144629d7dc | |||
| cf36e8c7cb | |||
| 233df5d59d | |||
| 375796063b | |||
| d8a70de8a8 | |||
| f1ee7fc04f | |||
| e8585fb4a7 | |||
| e10cc01f7e | |||
| 79d6db74b1 | |||
| 69d607a76e | |||
| 2c03d89213 | |||
| 11bf9833da | |||
| d2d37255d5 | |||
| bea5b4919e |
@@ -0,0 +1,6 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.7
|
||||
script: make tests
|
||||
notifications:
|
||||
email: false
|
||||
@@ -1,3 +1,71 @@
|
||||
## v14
|
||||
|
||||
Features:
|
||||
|
||||
* Full removal of Django settings injection for new apps.
|
||||
* Support for profile.d
|
||||
* Fresh app detection.
|
||||
* Update to Virtualenv v1.7.2
|
||||
* Updated to Pip v1.1 (patched)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Default pip path exists action.
|
||||
|
||||
## v13
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Fix pip quoting error.
|
||||
* Only talk about collectstatic in buildpack output when it's configured.
|
||||
|
||||
## v12
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Catch database setting corner case.
|
||||
|
||||
## v11
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Cleanup collectstatic output.
|
||||
|
||||
|
||||
## v10
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Check for collectstatic validity with --dry-run instead of --help for Django 1.4.
|
||||
|
||||
## v9
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Unset PYTHONHOME in buildpack for [user_env_compile](http://devcenter.heroku.com/articles/labs-user-env-compile).
|
||||
|
||||
## v8
|
||||
|
||||
Features:
|
||||
|
||||
* Disable Django collectstatic with `$DISABLE_COLLECTSTATIC` + [user_env_compile](http://devcenter.heroku.com/articles/labs-user-env-compile).
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Don't disbable injection for new Django apps.
|
||||
* Inform user of July 1, 2012 deprecation of Django injection.
|
||||
|
||||
## v7
|
||||
|
||||
Features:
|
||||
|
||||
* Full removal of Django setting injection for new apps.
|
||||
* Automatic execution of collectstatic.
|
||||
* Suppress collectstatic errors via env SILENCE_COLLECTSTATIC.
|
||||
* Increase settings.py search depth to 3.
|
||||
* Search recursively from included requirements.txt files.
|
||||
|
||||
|
||||
## v6 (03/23/2012)
|
||||
|
||||
Features:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License:
|
||||
|
||||
Copyright (C) 2012 Heroku, Inc.
|
||||
Copyright (C) 2013 Heroku, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
tests:
|
||||
./bin/test
|
||||
|
||||
docs:
|
||||
mkdir -p site
|
||||
shocco -t 'Python Buildpack Compiler' ./bin/compile > site/index.html
|
||||
shocco -t 'Django Buildpack Compiler' ./bin/steps/django > site/django.html
|
||||
shocco -t 'Pylibmc Buildpack Compiler' ./bin/steps/pylibmc > site/pylibmc.html
|
||||
|
||||
site: docs
|
||||
cd site && git add -A && git commit -m 'update' && git push heroku master
|
||||
|
||||
pip:
|
||||
git clone git@github.com:kennethreitz/pip.git --branch heroku --depth 1
|
||||
rm -fr vendor/virtualenv-1.7/virtualenv_support/pip-1.0.2.tar.gz
|
||||
rm -fr pip/.git
|
||||
tar -pczf vendor/virtualenv-1.7/virtualenv_support/pip-1.0.2.tar.gz pip
|
||||
rm -fr pip
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Heroku buildpack: Python
|
||||
========================
|
||||
|
||||
This is a [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) for Python apps.
|
||||
It uses [virtualenv](http://www.virtualenv.org/) and [pip](http://www.pip-installer.org/).
|
||||
This is a [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) for Python apps, powered by [pip](http://www.pip-installer.org/).
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -12,18 +12,17 @@ Example usage:
|
||||
$ ls
|
||||
Procfile requirements.txt web.py
|
||||
|
||||
$ heroku create --stack cedar --buildpack git@github.com:heroku/heroku-buildpack-python.git
|
||||
$ heroku create --stack cedar --buildpack git://github.com/heroku/heroku-buildpack-python.git
|
||||
|
||||
$ git push heroku master
|
||||
...
|
||||
-----> Heroku receiving push
|
||||
-----> Fetching custom build pack... done
|
||||
-----> Fetching custom git buildpack... done
|
||||
-----> Python app detected
|
||||
-----> Preparing virtualenv version 1.6.4
|
||||
New python executable in ./bin/python
|
||||
Installing setuptools............done.
|
||||
Installing pip...............done.
|
||||
-----> Installing dependencies using pip version 1.0.2
|
||||
-----> No runtime.txt provided; assuming python-2.7.6.
|
||||
-----> Preparing Python runtime (python-2.7.6)
|
||||
-----> Installing Setuptools (2.1)
|
||||
-----> Installing Pip (1.5.2)
|
||||
-----> Installing dependencies using Pip (1.5.2)
|
||||
Downloading/unpacking Flask==0.7.2 (from -r requirements.txt (line 1))
|
||||
Downloading/unpacking Werkzeug>=0.6.1 (from Flask==0.7.2->-r requirements.txt (line 1))
|
||||
Downloading/unpacking Jinja2>=2.4 (from Flask==0.7.2->-r requirements.txt (line 1))
|
||||
@@ -31,13 +30,26 @@ Example usage:
|
||||
Successfully installed Flask Werkzeug Jinja2
|
||||
Cleaning up...
|
||||
|
||||
The buildpack will detect your app as Python if it has the file `requirements.txt` in the root. It will detect your app as Python/Django if there is an additional `settings.py` in a project subdirectory.
|
||||
You can also add it to upcoming builds of an existing application:
|
||||
|
||||
It will use virtualenv and pip to install your dependencies, vendoring a copy of the Python runtime into your slug. The `bin/`, `include/` and `lib/` directories will be cached between builds to allow for faster pip install time.
|
||||
$ heroku config:add BUILDPACK_URL=git://github.com/heroku/heroku-buildpack-python.git
|
||||
|
||||
Hacking
|
||||
-------
|
||||
The buildpack will detect your app as Python if it has the file `requirements.txt` in the root.
|
||||
|
||||
To use this buildpack, fork it on Github. Push up changes to your fork, then create a test app with `--buildpack <your-github-url>` and push to it.
|
||||
It will use Pip to install your dependencies, vendoring a copy of the Python runtime into your slug.
|
||||
|
||||
To change the vendored virtualenv, unpack the desired version to the `src/` folder, and update the virtualenv() function in `bin/compile` to prepend the virtualenv module directory to the path. The virtualenv release vendors its own versions of pip and setuptools.
|
||||
Specify a Runtime
|
||||
-----------------
|
||||
|
||||
You can also provide arbitrary releases Python with a `runtime.txt` file.
|
||||
|
||||
$ cat runtime.txt
|
||||
python-3.3.3
|
||||
|
||||
Runtime options include:
|
||||
|
||||
- python-2.7.6
|
||||
- python-3.3.3
|
||||
- pypy-1.9 (experimental)
|
||||
|
||||
Other [unsupported runtimes](https://github.com/kennethreitz/python-versions/tree/master/formula) are available as well.
|
||||
|
||||
+194
-127
@@ -1,29 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script serves as the
|
||||
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||
# compiler.
|
||||
#
|
||||
# A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an
|
||||
# adapter between a Python application and Heroku's runtime.
|
||||
|
||||
# ## Usage
|
||||
# Compiling an app into a slug is simple:
|
||||
#
|
||||
# $ bin/compile <build-dir> <cache-dir>
|
||||
|
||||
|
||||
# ## Assumptions
|
||||
#
|
||||
# This buildpack makes the following assumptions:
|
||||
#
|
||||
# - The desired Python VM is available on the base system.
|
||||
# - Library dependencies are available on the base system.
|
||||
# - Django applications should not require any platform-specific configuration.
|
||||
|
||||
# <hr />
|
||||
|
||||
# ## Context
|
||||
# Usage:
|
||||
#
|
||||
# $ bin/compile <build-dir> <cache-dir> <env-path>
|
||||
|
||||
# Fail fast and fail hard.
|
||||
set -eo pipefail
|
||||
@@ -36,59 +15,86 @@ BIN_DIR=$(cd $(dirname $0); pwd) # absolute path
|
||||
ROOT_DIR=$(dirname $BIN_DIR)
|
||||
BUILD_DIR=$1
|
||||
CACHE_DIR=$2
|
||||
ENV_DIR=$3
|
||||
|
||||
# The detected application type (`Python`|`Python/Django`).
|
||||
NAME=$($BIN_DIR/detect $BUILD_DIR)
|
||||
|
||||
# Where to store the Pip download cache.
|
||||
CACHED_DIRS=".heroku"
|
||||
PIP_DOWNLOAD_CACHE=${PIP_DOWNLOAD_CACHE:-$CACHE_DIR/pip_downloads}
|
||||
|
||||
# Static configurations for virtualenv caches.
|
||||
LEGACY_VIRTUALENV_LOC="."
|
||||
MODERN_VIRTUALENV_LOC=".heroku/venv"
|
||||
LEGACY_VIRTUALENV_DIRS="bin include lib"
|
||||
LEGACY_VIRTUALENV_TRIGGER="lib/python2.7"
|
||||
VIRTUALENV_LOC=".heroku/venv"
|
||||
LEGACY_TRIGGER="lib/python2.7"
|
||||
PROFILE_PATH="$BUILD_DIR/.profile.d/python.sh"
|
||||
|
||||
DEFAULT_PYTHON_VERSION="python-2.7.6"
|
||||
PYTHON_EXE="/app/.heroku/python/bin/python"
|
||||
PIP_VERSION="1.5.4"
|
||||
SETUPTOOLS_VERSION="2.1"
|
||||
|
||||
# Python version. This will be used in the future to specify custom Pythons.
|
||||
PYTHON_VERSION="2.7.2"
|
||||
PYTHON_EXE="python2.7"
|
||||
# Setup bpwatch
|
||||
export PATH=$PATH:$ROOT_DIR/vendor/bpwatch
|
||||
LOGPLEX_KEY="t.b396af7f-ad75-4643-8b9e-ebb288acc624"
|
||||
export BPWATCH_STORE_PATH=$CACHE_DIR/bpwatch.json
|
||||
BUILDPACK_VERSION=v28
|
||||
|
||||
# Support Anvil Build_IDs
|
||||
[ ! "$REQUEST_ID" ] && REQUEST_ID=$SLUG_ID
|
||||
|
||||
# Sanitizing environment variables.
|
||||
unset GIT_DIR PYTHONHOME PYTHONPATH LD_LIBRARY_PATH LIBRARY_PATH
|
||||
|
||||
bpwatch init $LOGPLEX_KEY
|
||||
bpwatch build python $BUILDPACK_VERSION $REQUEST_ID
|
||||
TMP_APP_DIR=$CACHE_DIR/tmp_app_dir
|
||||
|
||||
bpwatch start compile
|
||||
|
||||
# The slug compiler doesn't do a very good job of sanitizing environment variables.
|
||||
unset GIT_DIR
|
||||
|
||||
# We'll need to send these statics to other scripts we `source`.
|
||||
export PIP_DOWNLOAD_CACHE
|
||||
export BUILD_DIR
|
||||
export BUILD_DIR CACHE_DIR BIN_DIR PROFILE_PATH
|
||||
|
||||
# Syntax sugar.
|
||||
indent() {
|
||||
RE="s/^/ /"
|
||||
[ $(uname) == "Darwin" ] && sed -l "$RE" || sed -u "$RE"
|
||||
}
|
||||
source $BIN_DIR/utils
|
||||
|
||||
function virtualenv (){
|
||||
python "$ROOT_DIR/vendor/virtualenv-1.7/virtualenv.py" "$@"
|
||||
}
|
||||
# Directory Hacks for path consistiency.
|
||||
APP_DIR='/app'
|
||||
TMP_APP_DIR=$CACHE_DIR/tmp_app_dir
|
||||
|
||||
function puts-step (){
|
||||
echo "-----> $@"
|
||||
}
|
||||
# Copy Anvil app dir to temporary storage...
|
||||
bpwatch start anvil_appdir_stage
|
||||
if [ "$SLUG_ID" ]; then
|
||||
mkdir -p $TMP_APP_DIR
|
||||
deep-mv $APP_DIR $TMP_APP_DIR
|
||||
else
|
||||
deep-rm $APP_DIR
|
||||
fi
|
||||
bpwatch stop anvil_appdir_stage
|
||||
|
||||
function puts-warn (){
|
||||
echo " ! $@"
|
||||
}
|
||||
# Copy Application code in.
|
||||
bpwatch start appdir_stage
|
||||
deep-mv $BUILD_DIR $APP_DIR
|
||||
bpwatch stop appdir_stage
|
||||
|
||||
# ## Build Time
|
||||
#
|
||||
# Set new context.
|
||||
ORIG_BUILD_DIR=$BUILD_DIR
|
||||
BUILD_DIR=$APP_DIR
|
||||
|
||||
# Prepend proper path buildpack use.
|
||||
export PATH=$BUILD_DIR/.heroku/python/bin:$BUILD_DIR/.heroku/vendor/bin:$PATH
|
||||
export PYTHONUNBUFFERED=1
|
||||
export LANG=en_US.UTF-8
|
||||
export C_INCLUDE_PATH=/app/.heroku/vendor/include:$BUILD_DIR/.heroku/vendor/include
|
||||
export CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:$BUILD_DIR/.heroku/vendor/include
|
||||
export LIBRARY_PATH=/app/.heroku/vendor/lib:$BUILD_DIR/.heroku/vendor/lib
|
||||
export LD_LIBRARY_PATH=/app/.heroku/vendor/lib:$BUILD_DIR/.heroku/vendor/lib
|
||||
export PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:$BUILD_DIR/.heroku/vendor/lib/pkg-config
|
||||
|
||||
# Switch to the repo's context.
|
||||
cd $BUILD_DIR
|
||||
|
||||
# ### Sanity Checks
|
||||
#
|
||||
# Just a little peace of mind.
|
||||
# Experimental pre_compile hook.
|
||||
bpwatch start pre_compile
|
||||
source $BIN_DIR/steps/hooks/pre_compile
|
||||
bpwatch stop pre_compile
|
||||
|
||||
# If no requirements given, assume `setup.py develop`.
|
||||
if [ ! -f requirements.txt ]; then
|
||||
@@ -96,109 +102,170 @@ if [ ! -f requirements.txt ]; then
|
||||
echo "-e ." > requirements.txt
|
||||
fi
|
||||
|
||||
# Sticky runtimes.
|
||||
if [ -f $CACHE_DIR/.heroku/python-version ]; then
|
||||
DEFAULT_PYTHON_VERSION=$(cat $CACHE_DIR/.heroku/python-version)
|
||||
fi
|
||||
|
||||
# If no runtime given, assume default version.
|
||||
if [ ! -f runtime.txt ]; then
|
||||
puts-step "No runtime.txt provided; assuming $DEFAULT_PYTHON_VERSION."
|
||||
echo $DEFAULT_PYTHON_VERSION > runtime.txt
|
||||
fi
|
||||
|
||||
# ### The Cache
|
||||
mkdir -p $CACHE_DIR
|
||||
|
||||
# Purge "old-style" virtualenvs.
|
||||
bpwatch start clear_old_venvs
|
||||
[ -d $CACHE_DIR/$LEGACY_TRIGGER ] && rm -fr $CACHE_DIR/.heroku/bin $CACHE_DIR/.heroku/lib $CACHE_DIR/.heroku/include
|
||||
[ -d $CACHE_DIR/$VIRTUALENV_LOC ] && rm -fr $CACHE_DIR/.heroku/venv $CACHE_DIR/.heroku/src
|
||||
bpwatch stop clear_old_venvs
|
||||
|
||||
# Restore old artifacts from the cache.
|
||||
bpwatch start restore_cache
|
||||
for dir in $CACHED_DIRS; do
|
||||
cp -R $CACHE_DIR/$dir . &> /dev/null || true
|
||||
done
|
||||
bpwatch stop restore_cache
|
||||
|
||||
set +e
|
||||
# Create set-aside `.heroku` folder.
|
||||
mkdir -p .heroku
|
||||
mkdir .heroku &> /dev/null
|
||||
set -e
|
||||
|
||||
# Nice defaults.
|
||||
LEGACY_VIRTUALENV=false
|
||||
VIRTUALENV_LOC=$MODERN_VIRTUALENV_LOC
|
||||
mkdir -p $(dirname $PROFILE_PATH)
|
||||
|
||||
# Support "old-style" virtualenvs.
|
||||
if [ -d $CACHE_DIR/$LEGACY_VIRTUALENV_TRIGGER ]; then
|
||||
LEGACY_VIRTUALENV=true
|
||||
VIRTUALENV_LOC=$LEGACY_VIRTUALENV_LOC
|
||||
CACHED_DIRS=$LEGACY_VIRTUALENV_DIRS
|
||||
set +e
|
||||
PYTHON_VERSION=$(cat runtime.txt)
|
||||
|
||||
# Warn for a checked-in virtualenv.
|
||||
if [ -d "lib" ] || [ -d "bin" ]; then
|
||||
puts-warn "You have a virtualenv checked in. You should ignore the appropriate paths in your repo. See http://devcenter.heroku.com/articles/gitignore for more info.";
|
||||
fi
|
||||
|
||||
# Reject a conflicting checked-in virtualenv.
|
||||
if [ -f "lib/python2.7" ]; then
|
||||
puts-warn "Checked-in virtualenv conflict."
|
||||
exit 1;
|
||||
# Install Python.
|
||||
if [ -f .heroku/python-version ]; then
|
||||
if [ ! $(cat .heroku/python-version) = $PYTHON_VERSION ]; then
|
||||
bpwatch start uninstall_python
|
||||
puts-step "Found $(cat .heroku/python-version), removing."
|
||||
rm -fr .heroku/python
|
||||
bpwatch stop uninstall_python
|
||||
else
|
||||
SKIP_INSTALL=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restore old artifacts from the cache.
|
||||
for dir in $CACHED_DIRS; do
|
||||
cp -R $CACHE_DIR/$dir . &> /dev/null || true
|
||||
done
|
||||
|
||||
if [ ! "$SKIP_INSTALL" ]; then
|
||||
bpwatch start install_python
|
||||
puts-step "Preparing Python runtime ($PYTHON_VERSION)"
|
||||
curl http://envy-versions.s3.amazonaws.com/$PYTHON_VERSION.tar.bz2 -s | tar jx &> /dev/null
|
||||
if [[ $? != 0 ]] ; then
|
||||
puts-warn "Requested runtime ($PYTHON_VERSION) was not found."
|
||||
puts-warn "Aborting. More info: https://devcenter.heroku.com/articles/python-support"
|
||||
exit 1
|
||||
fi
|
||||
mv python .heroku/python
|
||||
bpwatch stop install_python
|
||||
|
||||
# ### Virtualenv Setup
|
||||
#
|
||||
# Record for future reference.
|
||||
echo $PYTHON_VERSION > .heroku/python-version
|
||||
FRESH_PYTHON=true
|
||||
|
||||
# Create the virtualenv. Rebuild if corrupt.
|
||||
# TODO: Bootstrap a bottled Python VM...
|
||||
hash -r
|
||||
else
|
||||
puts-step "Using Python runtime ($PYTHON_VERSION)"
|
||||
fi
|
||||
|
||||
set +e
|
||||
puts-step "Preparing Python interpreter ($PYTHON_VERSION)"
|
||||
puts-step "Creating Virtualenv version $(virtualenv --version)"
|
||||
# If Pip isn't up to date:
|
||||
if [ "$FRESH_PYTHON" ] || [[ ! $(pip --version) == *$PIP_VERSION* ]]; then
|
||||
WORKING_DIR=$(pwd)
|
||||
|
||||
# Try to create the virtualenv.
|
||||
OUT=$(virtualenv --python $PYTHON_EXE --distribute --never-download --prompt='(venv) ' $VIRTUALENV_LOC 2>&1)
|
||||
bpwatch start prepare_environment
|
||||
|
||||
# If there's an error, purge and recreate.
|
||||
[ $? -ne 0 ] && {
|
||||
puts-warn "Virtualenv corrupt, rebuilding."
|
||||
for dir in $VIRTUALENV_DIRS; do
|
||||
rm -fr $dir &> /dev/null || true
|
||||
done
|
||||
OUT=$(virtualenv --python $PYTHON_EXE --distribute --never-download --prompt='(venv) ' $VIRTUALENV_LOC )
|
||||
}
|
||||
echo "$OUT" | indent
|
||||
bpwatch start install_setuptools
|
||||
# Prepare it for the real world
|
||||
puts-step "Installing Setuptools ($SETUPTOOLS_VERSION)"
|
||||
cd $ROOT_DIR/vendor/setuptools-$SETUPTOOLS_VERSION/
|
||||
python setup.py install &> /dev/null
|
||||
cd $WORKING_DIR
|
||||
bpwatch stop install_setuptoools
|
||||
|
||||
bpwatch start install_pip
|
||||
puts-step "Installing Pip ($PIP_VERSION)"
|
||||
cd $ROOT_DIR/vendor/pip-$PIP_VERSION/
|
||||
python setup.py install &> /dev/null
|
||||
cd $WORKING_DIR
|
||||
|
||||
bpwatch stop install_pip
|
||||
bpwatch stop prepare_environment
|
||||
fi
|
||||
|
||||
set -e
|
||||
hash -r
|
||||
|
||||
# Pylibmc support.
|
||||
# Pylibmc support.
|
||||
# See [`bin/steps/pylibmc`](pylibmc.html).
|
||||
source $BIN_DIR/steps/pylibmc
|
||||
|
||||
# Activate the Virtualenv.
|
||||
puts-step "Activating virtualenv"
|
||||
source $VIRTUALENV_LOC/bin/activate
|
||||
bpwatch start pylibmc_install
|
||||
source $BIN_DIR/steps/pylibmc
|
||||
bpwatch stop pylibmc_install
|
||||
|
||||
# Install Mercurial if it appears to be required.
|
||||
if (grep -Fiq "hg+" requirements.txt) then
|
||||
pip install --use-mirrors mercurial | indent
|
||||
bpwatch start mercurial_install
|
||||
/app/.heroku/python/bin/pip install mercurial | cleanup | indent
|
||||
bpwatch stop mercurial_install
|
||||
fi
|
||||
|
||||
# Install dependencies with Pip.
|
||||
puts-step "Installing dependencies using Pip version $(pip --version | awk '{print $2}')"
|
||||
pip install --use-mirrors -r requirements.txt --src ./.heroku/src | indent
|
||||
puts-step "Installing dependencies using Pip ($PIP_VERSION)"
|
||||
|
||||
# Do additional application hackery if applications appears to be a Django app.
|
||||
# Optionally, disable all Django-specific changes with `DISABLE_INJECTION` env.
|
||||
#
|
||||
# See [`bin/steps/django`](django.html).
|
||||
|
||||
if [ "$NAME" = "Python/Django" ] && ! [ "$DISABLE_INJECTION" ]; then
|
||||
source $BIN_DIR/steps/django
|
||||
fi
|
||||
[ ! "$FRESH_PYTHON" ] && bpwatch start pip_install
|
||||
[ "$FRESH_PYTHON" ] && bpwatch start pip_install_first
|
||||
|
||||
# Make Virtualenv's paths relative for portability.
|
||||
set +e
|
||||
OUT=$(virtualenv --python $PYTHON_EXE --relocatable $VIRTUALENV_LOC)
|
||||
[ $? -ne 0 ] && {
|
||||
puts-warn "Error making virtualenv relocatable"
|
||||
echo "$OUT" | indent
|
||||
exit 1
|
||||
}
|
||||
set -e
|
||||
/app/.heroku/python/bin/pip install -r requirements.txt --exists-action=w --src=./.heroku/src --allow-all-external | cleanup | indent
|
||||
|
||||
[ ! "$FRESH_PYTHON" ] && bpwatch stop pip_install
|
||||
[ "$FRESH_PYTHON" ] && bpwatch stop pip_install_first
|
||||
|
||||
# Django collectstatic support.
|
||||
bpwatch start collectstatic
|
||||
sub-env $BIN_DIR/steps/collectstatic
|
||||
bpwatch stop collectstatic
|
||||
|
||||
# ### Finalize
|
||||
#
|
||||
|
||||
# Set context environment variables.
|
||||
set-env PATH '$HOME/.heroku/python/bin:$PATH'
|
||||
set-env PYTHONUNBUFFERED true
|
||||
set-env PYTHONHOME /app/.heroku/python
|
||||
set-default-env LIBRARY_PATH /app/.heroku/vendor/lib
|
||||
set-default-env LD_LIBRARY_PATH /app/.heroku/vendor/lib
|
||||
set-default-env LANG en_US.UTF-8
|
||||
set-default-env PYTHONHASHSEED random
|
||||
set-default-env PYTHONPATH /app/
|
||||
|
||||
|
||||
# Experimental post_compile hook.
|
||||
bpwatch start post_compile
|
||||
source $BIN_DIR/steps/hooks/post_compile
|
||||
bpwatch stop post_compile
|
||||
|
||||
# Store new artifacts in cache.
|
||||
for dir in $CACHED_DIRS; do
|
||||
bpwatch start dump_cache
|
||||
for dir in $CACHED_DIRS; do
|
||||
rm -rf $CACHE_DIR/$dir
|
||||
cp -R $dir $CACHE_DIR/
|
||||
done
|
||||
bpwatch stop dump_cache
|
||||
|
||||
rm -rf $CACHE_DIR/$dir
|
||||
cp -R $dir $CACHE_DIR/
|
||||
done
|
||||
# ### Fin.
|
||||
bpwatch start appdir_commit
|
||||
deep-mv $BUILD_DIR $ORIG_BUILD_DIR
|
||||
bpwatch stop appdir_commit
|
||||
|
||||
# ### Fin.
|
||||
bpwatch start anvil_appdir_commit
|
||||
if [ "$SLUG_ID" ]; then
|
||||
deep-mv $TMP_APP_DIR $APP_DIR
|
||||
fi
|
||||
|
||||
bpwatch stop anvil_appdir_commit
|
||||
bpwatch stop compile
|
||||
|
||||
+7
-18
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script serves as the
|
||||
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||
# detector.
|
||||
#
|
||||
# A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an
|
||||
# This script serves as the
|
||||
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||
# detector.
|
||||
#
|
||||
# A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an
|
||||
# adapter between a Python application and Heroku's runtime.
|
||||
|
||||
# ## Usage
|
||||
# Compiling an app into a slug is simple:
|
||||
#
|
||||
#
|
||||
# $ bin/detect <build-dir> <cache-dir>
|
||||
|
||||
BUILD_DIR=$1
|
||||
@@ -19,15 +19,4 @@ if [ ! -f $BUILD_DIR/requirements.txt ] && [ ! -f $BUILD_DIR/setup.py ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If only `setup.py`, assume that the app is not Django.
|
||||
if [ ! -f $BUILD_DIR/requirements.txt ]; then
|
||||
echo Python
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# `Python/Django` if `**/settings.py` is present and `django` exists in
|
||||
# `requirements.txt`.
|
||||
#
|
||||
# Otherwise, `Python`.
|
||||
|
||||
ls $BUILD_DIR/**/settings.py &> /dev/null && (grep -Fiq "django" $BUILD_DIR/requirements.txt) && echo Python/Django || echo Python
|
||||
echo Python
|
||||
|
||||
+7
-19
@@ -3,33 +3,21 @@
|
||||
|
||||
BIN_DIR=$(cd $(dirname $0); pwd) # absolute path
|
||||
BUILD_DIR=$1
|
||||
NAME=$($BIN_DIR/detect $BUILD_DIR) || exit 1
|
||||
|
||||
MANAGE_FILE=$(cd $BUILD_DIR && find . -maxdepth 3 -type f -name 'manage.py' | head -1)
|
||||
MANAGE_FILE=${MANAGE_FILE:2}
|
||||
|
||||
cat <<EOF
|
||||
---
|
||||
config_vars:
|
||||
PATH: /app/.heroku/venv/bin:/bin:/usr/local/bin:/usr/bin
|
||||
PYTHONUNBUFFERED: true
|
||||
LIBRARY_PATH: /app/.heroku/vendor/lib
|
||||
LD_LIBRARY_PATH: /app/.heroku/vendor/lib
|
||||
LANG: en_US.UTF-8
|
||||
PYTHONHASHSEED: random
|
||||
PYTHONHOME: /app/.heroku/venv/
|
||||
PYTHONPATH: /app/
|
||||
|
||||
EOF
|
||||
|
||||
|
||||
[ "$NAME" = "Python/Django" ] || exit 0
|
||||
|
||||
MANAGE_FILE=$(cd $BUILD_DIR && find . -maxdepth 2 -type f -name 'manage.py' | head -1)
|
||||
MANAGE_FILE=${MANAGE_FILE:2}
|
||||
PROJECT=$(dirname $MANAGE_FILE)
|
||||
|
||||
if [[ $MANAGE_FILE ]]; then
|
||||
cat <<EOF
|
||||
|
||||
addons:
|
||||
shared-database:5mb
|
||||
|
||||
default_process_types:
|
||||
web: python $PROJECT/manage.py runserver 0.0.0.0:\$PORT --noreload
|
||||
heroku-postgresql:hobby-dev
|
||||
EOF
|
||||
fi
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source $BIN_DIR/utils
|
||||
|
||||
MANAGE_FILE=$(find . -maxdepth 3 -type f -name 'manage.py' | head -1)
|
||||
MANAGE_FILE=${MANAGE_FILE:-fakepath}
|
||||
|
||||
[ -f .heroku/collectstatic_disabled ] && DISABLE_COLLECTSTATIC=1
|
||||
|
||||
if [ ! "$DISABLE_COLLECTSTATIC" ] && [ -f "$MANAGE_FILE" ]; then
|
||||
set +e
|
||||
|
||||
echo "-----> Preparing static assets"
|
||||
# Check if collectstatic is configured properly.
|
||||
python $MANAGE_FILE collectstatic --dry-run --noinput &> /dev/null && RUN_COLLECTSTATIC=true
|
||||
|
||||
# Compile assets if collectstatic appears to be kosher.
|
||||
if [ "$RUN_COLLECTSTATIC" ]; then
|
||||
|
||||
echo " Running collectstatic..."
|
||||
python $MANAGE_FILE collectstatic --noinput 2>&1 | sed '/^Copying/d;/^$/d;/^ /d' | indent
|
||||
|
||||
[ $? -ne 0 ] && {
|
||||
echo " ! Error running 'manage.py collectstatic'. More info:"
|
||||
echo " http://devcenter.heroku.com/articles/django-assets"
|
||||
}
|
||||
else
|
||||
echo " Collectstatic configuration error. To debug, run:"
|
||||
echo " $ heroku run python $MANAGE_FILE collectstatic --noinput"
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
|
||||
fi
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script serves as the Django build step of the
|
||||
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||
# compiler.
|
||||
#
|
||||
# A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an
|
||||
# adapter between a Python application and Heroku's runtime.
|
||||
#
|
||||
# This script is invoked by [`bin/compile`](/).
|
||||
|
||||
# ## Sanity Checks
|
||||
#
|
||||
|
||||
# Reject a Django app that appears to be packaged incorrectly.
|
||||
if [ -f settings.py ]; then
|
||||
echo " ! Django app must be in a package subdirectory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "-----> Injecting Django settings..."
|
||||
|
||||
|
||||
SETTINGS_FILE=$(find . -maxdepth 2 -type f -name 'settings.py' | head -1)
|
||||
PROJECT=$(dirname $SETTINGS_FILE)
|
||||
|
||||
echo "Injecting code into $SETTINGS_FILE to read from DATABASE_URL" | indent
|
||||
|
||||
cat >>$SETTINGS_FILE <<EOF
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
# Register database schemes in URLs.
|
||||
urlparse.uses_netloc.append('postgres')
|
||||
urlparse.uses_netloc.append('mysql')
|
||||
|
||||
try:
|
||||
|
||||
# Check to make sure DATABASES is set in settings.py file.
|
||||
# If not default to {}
|
||||
|
||||
if 'DATABASES' not in locals():
|
||||
DATABASES = {}
|
||||
|
||||
if 'DATABASE_URL' in os.environ:
|
||||
url = urlparse.urlparse(os.environ['DATABASE_URL'])
|
||||
|
||||
# Ensure default database exists.
|
||||
DATABASES['default'] = DATABASES.get('default', {})
|
||||
|
||||
# Update with environment configuration.
|
||||
DATABASES['default'].update({
|
||||
'NAME': url.path[1:],
|
||||
'USER': url.username,
|
||||
'PASSWORD': url.password,
|
||||
'HOST': url.hostname,
|
||||
'PORT': url.port,
|
||||
})
|
||||
if url.scheme == 'postgres':
|
||||
DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
|
||||
|
||||
if url.scheme == 'mysql':
|
||||
DATABASES['default']['ENGINE'] = 'django.db.backends.mysql'
|
||||
except Exception:
|
||||
print 'Unexpected error:', sys.exc_info()
|
||||
|
||||
EOF
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -f bin/post_compile ]; then
|
||||
echo "-----> Running post-compile hook"
|
||||
chmod +x bin/post_compile
|
||||
sub-env bin/post_compile
|
||||
fi
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -f bin/pre_compile ]; then
|
||||
echo "-----> Running pre-compile hook"
|
||||
chmod +x bin/pre_compile
|
||||
sub-env bin/pre_compile
|
||||
fi
|
||||
Regular → Executable
+8
-5
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script serves as the Pylibmc build step of the
|
||||
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||
# compiler.
|
||||
#
|
||||
# A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an
|
||||
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||
# compiler.
|
||||
#
|
||||
# A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an
|
||||
# adapter between a Python application and Heroku's runtime.
|
||||
#
|
||||
# This script is invoked by [`bin/compile`](/).
|
||||
@@ -12,8 +12,11 @@
|
||||
# The location of the pre-compiled libmemcached binary.
|
||||
VENDORED_MEMCACHED="http://cl.ly/0a191R3K160t1w1P0N25/vendor-libmemcached.tar.gz"
|
||||
|
||||
# Syntax sugar.
|
||||
source $BIN_DIR/utils
|
||||
|
||||
# If pylibmc exists within requirements, use vendored libmemcached.
|
||||
if (grep -Fiq "pylibmc" requirements.txt) then
|
||||
if (grep -Eiq "^\s*pylibmc" requirements.txt) then
|
||||
echo "-----> Noticed pylibmc. Bootstrapping libmemcached."
|
||||
cd .heroku
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#
|
||||
# Create a Heroku app with the following buildpack:
|
||||
# https://github.com/ddollar/buildpack-tet
|
||||
# https://github.com/ddollar/buildpack-test
|
||||
#
|
||||
# Push this Python buildpack to that Heroku app to
|
||||
# run the tests.
|
||||
@@ -20,15 +20,21 @@ testDetectWithEmptyReqs() {
|
||||
assertCapturedSuccess
|
||||
}
|
||||
|
||||
testDetectDjango15() {
|
||||
detect "django-1.5-skeleton"
|
||||
assertCapturedEquals "Python"
|
||||
assertCapturedSuccess
|
||||
}
|
||||
|
||||
testDetectDjango14() {
|
||||
detect "django-1.4-skeleton"
|
||||
assertCapturedEquals "Python/Django"
|
||||
assertCapturedEquals "Python"
|
||||
assertCapturedSuccess
|
||||
}
|
||||
|
||||
testDetectDjango13() {
|
||||
detect "django-1.3-skeleton"
|
||||
assertCapturedEquals "Python/Django"
|
||||
assertCapturedEquals "Python"
|
||||
assertCapturedSuccess
|
||||
}
|
||||
|
||||
@@ -74,4 +80,4 @@ compile() {
|
||||
}
|
||||
|
||||
source ${BASE}/vendor/shunit2
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
shopt -s extglob
|
||||
|
||||
if [ $(uname) == Darwin ]; then
|
||||
sed() { command sed -l "$@"; }
|
||||
else
|
||||
sed() { command sed -u "$@"; }
|
||||
fi
|
||||
|
||||
# Syntax sugar.
|
||||
indent() {
|
||||
sed "s/^/ /"
|
||||
}
|
||||
|
||||
# Clean up pip output
|
||||
cleanup() {
|
||||
sed -e 's/\.\.\.\+/.../g' | sed -e '/already satisfied/Id' | sed -e '/Overwriting/Id' | sed -e '/python executable/Id' | sed -e '/no previously-included files/Id'
|
||||
}
|
||||
|
||||
# Buildpack Steps.
|
||||
puts-step() {
|
||||
echo "-----> $@"
|
||||
}
|
||||
|
||||
# Buildpack Warnings.
|
||||
puts-warn() {
|
||||
echo " ! $@"
|
||||
}
|
||||
|
||||
# Usage: $ set-env key value
|
||||
set-env() {
|
||||
echo "export $1=$2" >> $PROFILE_PATH
|
||||
}
|
||||
|
||||
# Usage: $ set-default-env key value
|
||||
set-default-env() {
|
||||
echo "export $1=\${$1:-$2}" >> $PROFILE_PATH
|
||||
}
|
||||
|
||||
# Usage: $ set-default-env key value
|
||||
un-set-env() {
|
||||
echo "unset $1" >> $PROFILE_PATH
|
||||
}
|
||||
|
||||
# Does some serious copying.
|
||||
deep-cp() {
|
||||
declare source="$1" target="$2"
|
||||
|
||||
mkdir -p "$target"
|
||||
|
||||
# cp doesn't like being called without source params,
|
||||
# so make sure they expand to something first.
|
||||
# subshell to avoid surprising caller with shopts.
|
||||
(
|
||||
shopt -s nullglob dotglob
|
||||
set -- "$source"/!(tmp|.|..)
|
||||
[[ $# == 0 ]] || cp -a "$@" "$target"
|
||||
)
|
||||
}
|
||||
|
||||
# Does some serious moving.
|
||||
deep-mv() {
|
||||
deep-cp "$1" "$2"
|
||||
deep-rm "$1"
|
||||
}
|
||||
|
||||
# Does some serious deleting.
|
||||
deep-rm() {
|
||||
# subshell to avoid surprising caller with shopts.
|
||||
(
|
||||
shopt -s dotglob
|
||||
rm -rf "$1"/!(tmp|.|..)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
sub-env() {
|
||||
|
||||
WHITELIST=${2:-''}
|
||||
BLACKLIST=${3:-'^(GIT_DIR|PYTHONHOME|PYTHONPATH|LD_LIBRARY_PATH|LIBRARY_PATH|PATH)$'}
|
||||
|
||||
(
|
||||
if [ -d "$ENV_DIR" ]; then
|
||||
for e in $(ls $ENV_DIR); do
|
||||
echo "$e" | grep -E "$WHITELIST" | grep -qvE "$BLACKLIST" &&
|
||||
export "$e=$(cat $ENV_DIR/$e)"
|
||||
:
|
||||
done
|
||||
fi
|
||||
|
||||
$1
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
requests
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,156 @@
|
||||
# Django settings for haystack project.
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
|
||||
'NAME': '', # Or path to database file if using sqlite3.
|
||||
# The following settings are not used with sqlite3:
|
||||
'USER': '',
|
||||
'PASSWORD': '',
|
||||
'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
|
||||
'PORT': '', # Set to empty string for default.
|
||||
}
|
||||
}
|
||||
|
||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# In a Windows environment this must be set to your system time zone.
|
||||
TIME_ZONE = 'America/Chicago'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# If you set this to False, Django will not format dates, numbers and
|
||||
# calendars according to the current locale.
|
||||
USE_L10N = True
|
||||
|
||||
# If you set this to False, Django will not use timezone-aware datetimes.
|
||||
USE_TZ = True
|
||||
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
# Example: "/var/www/example.com/media/"
|
||||
MEDIA_ROOT = ''
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://example.com/media/", "http://media.example.com/"
|
||||
MEDIA_URL = ''
|
||||
|
||||
# Absolute path to the directory static files should be collected to.
|
||||
# Don't put anything in this directory yourself; store your static files
|
||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||
# Example: "/var/www/example.com/static/"
|
||||
STATIC_ROOT = ''
|
||||
|
||||
# URL prefix for static files.
|
||||
# Example: "http://example.com/static/", "http://static.example.com/"
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# Additional locations of static files
|
||||
STATICFILES_DIRS = (
|
||||
# Put strings here, like "/home/html/static" or "C:/www/django/static".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# Don't forget to use absolute paths, not relative paths.
|
||||
)
|
||||
|
||||
# List of finder classes that know how to find static files in
|
||||
# various locations.
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||
)
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = '@w-1$9#jh05!qvbh#1k)c4=w9llcq116f$5(4&s_c)n4@%n=pc'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
# 'django.template.loaders.eggs.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
# Uncomment the next line for simple clickjacking protection:
|
||||
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'haystack.urls'
|
||||
|
||||
# Python dotted path to the WSGI application used by Django's runserver.
|
||||
WSGI_APPLICATION = 'haystack.wsgi.application'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# Don't forget to use absolute paths, not relative paths.
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
# Uncomment the next line to enable the admin:
|
||||
# 'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
# performed by this configuration is to send an email to
|
||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Examples:
|
||||
# url(r'^$', 'haystack.views.home', name='home'),
|
||||
# url(r'^haystack/', include('haystack.foo.urls')),
|
||||
|
||||
# Uncomment the admin/doc line below to enable admin documentation:
|
||||
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
||||
# Uncomment the next line to enable the admin:
|
||||
# url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
WSGI config for haystack project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "haystack.settings"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "haystack.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "haystack.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
@@ -0,0 +1 @@
|
||||
django==1.5
|
||||
@@ -0,0 +1,2 @@
|
||||
pylibmc
|
||||
psycopg2
|
||||
@@ -1 +1,2 @@
|
||||
httpbin
|
||||
requests
|
||||
distribute==0.6.49
|
||||
@@ -0,0 +1 @@
|
||||
requests==1.0.3
|
||||
@@ -0,0 +1 @@
|
||||
python-3.3.2
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
DEFAULT_PATH = '{0}.zip'.format(os.path.abspath(__file__))
|
||||
BPWATCH_DISTRO_PATH = os.environ.get('BPWATCH_DISTRO_PATH', DEFAULT_PATH)
|
||||
|
||||
sys.path.insert(0, BPWATCH_DISTRO_PATH)
|
||||
|
||||
import bp_cli
|
||||
bp_cli.main()
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
Alex Gaynor
|
||||
Alex Grönholm
|
||||
Alex Morega
|
||||
Alexandre Conrad
|
||||
Andrey Bulgakov
|
||||
Anrs Hu
|
||||
Anton Patrushev
|
||||
Antti Kaihola
|
||||
Armin Ronacher
|
||||
Aziz Köksal
|
||||
Ben Rosser
|
||||
Bernardo B. Marques
|
||||
Bradley Ayers
|
||||
Brian Rosner
|
||||
Carl Meyer
|
||||
Chris McDonough
|
||||
Christian Oudard
|
||||
Clay McClure
|
||||
Cody Soyland
|
||||
Craig Kerstiens
|
||||
Cristian Sorinel
|
||||
Dan Sully
|
||||
Daniel Holth
|
||||
Dave Abrahams
|
||||
David (d1b)
|
||||
David Aguilar
|
||||
David Evans
|
||||
David Pursehouse
|
||||
dengzhp
|
||||
Dmitry Gladkov
|
||||
Donald Stufft
|
||||
Endoh Takanao
|
||||
enoch
|
||||
Erik M. Bray
|
||||
Francesco
|
||||
Gabriel de Perthuis
|
||||
Garry Polley
|
||||
Geoffrey Lehée
|
||||
George Song
|
||||
Georgi Valkov
|
||||
Herbert Pfennig
|
||||
hetmankp
|
||||
Hugo Lopes Tavares
|
||||
Hynek Schlawack
|
||||
Ian Bicking
|
||||
Igor Sobreira
|
||||
Ionel Maries Cristian
|
||||
Jakub Vysoky
|
||||
James Cleveland
|
||||
Jannis Leidel
|
||||
Jakub Stasiak
|
||||
Jay Graves
|
||||
Jeff Dairiki
|
||||
John-Scott Atlakson
|
||||
Jon Parise
|
||||
Jonas Nockert
|
||||
Jon Parise
|
||||
Jorge Niedbalski
|
||||
Josh Bronson
|
||||
Josh Hansen
|
||||
Kamal Bin Mustafa
|
||||
Kelsey Hightower
|
||||
Kenneth Belitzky
|
||||
Kenneth Reitz
|
||||
Kevin Frommelt
|
||||
Kumar McMillan
|
||||
Lev Givon
|
||||
Lincoln de Sousa
|
||||
Luke Macken
|
||||
Masklinn
|
||||
Marc Abramowitz
|
||||
Marc Tamlyn
|
||||
Marcus Smith
|
||||
Markus Hametner
|
||||
Matt Maker
|
||||
Maxime Rouyrre
|
||||
Michael Williamson
|
||||
Miguel Araujo Perez
|
||||
Monty Taylor
|
||||
Nick Stenning
|
||||
Nowell Strite
|
||||
Oliver Tonnhofer
|
||||
Olivier Girardot
|
||||
Ollie Rutherfurd
|
||||
Oren Held
|
||||
Patrick Jenkins
|
||||
Patrick Dubroy
|
||||
Paul Moore
|
||||
Paul Nasrat
|
||||
Paul Oswald
|
||||
Paul van der Linden
|
||||
Peter Waller
|
||||
Phil Freo
|
||||
Phil Whelan
|
||||
Piet Delport
|
||||
Preston Holmes
|
||||
Przemek Wrzos
|
||||
Qiangning Hong
|
||||
Rafael Caricio
|
||||
Rene Dudfield
|
||||
Roey Berman
|
||||
Ronny Pfannschmidt
|
||||
Rory McCann
|
||||
Ross Brattain
|
||||
Sergey Vasilyev
|
||||
Seth Woodworth
|
||||
Simon Cross
|
||||
Stavros Korokithakis
|
||||
Stéphane Klein
|
||||
Steven Myint
|
||||
Takayuki SHIMIZUKAWA
|
||||
Thomas Fenzl
|
||||
Thomas Johansson
|
||||
Toshio Kuratomi
|
||||
Travis Swicegood
|
||||
Vinay Sajip
|
||||
Vitaly Babiy
|
||||
W. Trevor King
|
||||
Wil Tan
|
||||
Hsiaoming Yang
|
||||
Vendored
+915
@@ -0,0 +1,915 @@
|
||||
**1.5.4 (2014-02-21)**
|
||||
|
||||
|
||||
* Correct deprecation warning for ``pip install --build`` to only notify when
|
||||
the `--build` value is different than the default.
|
||||
|
||||
|
||||
**1.5.3 (2014-02-20)**
|
||||
|
||||
|
||||
* **DEPRECATION** ``pip install --build`` and ``pip install --no-clean`` are now
|
||||
deprecated. See Issue #906 for discussion.
|
||||
|
||||
* Fixed #1112. Couldn't download directly from wheel paths/urls, and when wheel
|
||||
downloads did occur using requirement specifiers, dependencies weren't
|
||||
downloaded (PR #1527)
|
||||
|
||||
* Fixed #1320. ``pip wheel`` was not downloading wheels that already existed (PR
|
||||
#1524)
|
||||
|
||||
* Fixed #1111. ``pip install --download`` was failing using local
|
||||
``--find-links`` (PR #1524)
|
||||
|
||||
* Workaround for Python bug http://bugs.python.org/issue20053 (PR #1544)
|
||||
|
||||
* Don't pass a unicode __file__ to setup.py on Python 2.x (PR #1583)
|
||||
|
||||
* Verify that the Wheel version is compatible with this pip (PR #1569)
|
||||
|
||||
|
||||
**1.5.2 (2014-01-26)**
|
||||
|
||||
|
||||
* Upgraded the vendored ``pkg_resources`` and ``_markerlib`` to setuptools 2.1.
|
||||
|
||||
* Fixed an error that prevented accessing PyPI when pyopenssl, ndg-httpsclient,
|
||||
and pyasn1 are installed
|
||||
|
||||
* Fixed an issue that caused trailing comments to be incorrectly included as
|
||||
part of the URL in a requirements file
|
||||
|
||||
|
||||
**1.5.1 (2014-01-20)**
|
||||
|
||||
|
||||
* pip now only requires setuptools (any setuptools, not a certain version) when
|
||||
installing distributions from src (i.e. not from wheel). (Pull #1434).
|
||||
|
||||
* `get-pip.py` now installs setuptools, when it's not already installed (Pull
|
||||
#1475)
|
||||
|
||||
* Don't decode downloaded files that have a ``Content-Encoding`` header. (Pull
|
||||
#1435)
|
||||
|
||||
* Fix to correctly parse wheel filenames with single digit versions. (Pull
|
||||
#1445)
|
||||
|
||||
* If `--allow-unverified` is used assume it also means `--allow-external`. (Pull
|
||||
#1457)
|
||||
|
||||
|
||||
**1.5 (2014-01-01)**
|
||||
|
||||
|
||||
* **BACKWARD INCOMPATIBLE** pip no longer supports the ``--use-mirrors``,
|
||||
``-M``, and ``--mirrors`` flags. The mirroring support has been removed. In
|
||||
order to use a mirror specify it as the primary index with ``-i`` or
|
||||
``--index-url``, or as an additional index with ``--extra-index-url``. (Pull #1098, CVE-2013-5123)
|
||||
|
||||
* **BACKWARD INCOMPATIBLE** pip no longer will scrape insecure external urls by
|
||||
default nor will it install externally hosted files by default. Users may opt
|
||||
into installing externally hosted or insecure files or urls using
|
||||
``--allow-external PROJECT`` and ``--allow-unverified PROJECT``. (Pull #1055)
|
||||
|
||||
* **BACKWARD INCOMPATIBLE** pip no longer respects dependency links by default.
|
||||
Users may opt into respecting them again using ``--process-dependency-links``.
|
||||
|
||||
* **DEPRECATION** ``pip install --no-install`` and ``pip install
|
||||
--no-download`` are now formally deprecated. See Issue #906 for discussion on
|
||||
possible alternatives, or lack thereof, in future releases.
|
||||
|
||||
* **DEPRECATION** ``pip zip`` and ``pip unzip`` are now formally deprecated.
|
||||
|
||||
* pip will now install Mac OSX platform wheels from PyPI. (Pull #1278)
|
||||
|
||||
* pip now generates the appropriate platform-specific console scripts when
|
||||
installing wheels. (Pull #1251)
|
||||
|
||||
* Pip now confirms a wheel is supported when installing directly from a path or
|
||||
url. (Pull #1315)
|
||||
|
||||
* Fixed #1097, ``--ignore-installed`` now behaves again as designed, after it was
|
||||
unintentionally broke in v0.8.3 when fixing Issue #14 (Pull #1352).
|
||||
|
||||
* Fixed a bug where global scripts were being removed when uninstalling --user
|
||||
installed packages (Pull #1353).
|
||||
|
||||
* Fixed #1163, --user wasn't being respected when installing scripts from wheels (Pull #1176).
|
||||
|
||||
* Fixed #1150, we now assume '_' means '-' in versions from wheel filenames (Pull #1158).
|
||||
|
||||
* Fixed #219, error when using --log with a failed install (Pull #1205).
|
||||
|
||||
* Fixed #1131, logging was buffered and choppy in Python 3.
|
||||
|
||||
* Fixed #70, --timeout was being ignored (Pull #1202).
|
||||
|
||||
* Fixed #772, error when setting PIP_EXISTS_ACTION (Pull #1201).
|
||||
|
||||
* Added colors to the logging output in order to draw attention to important
|
||||
warnings and errors. (Pull #1109)
|
||||
|
||||
* Added warnings when using an insecure index, find-link, or dependency link. (Pull #1121)
|
||||
|
||||
* Added support for installing packages from a subdirectory using the ``subdirectory``
|
||||
editable option. ( Pull #1082 )
|
||||
|
||||
* Fixed #1192. "TypeError: bad operand type for unary" in some cases when
|
||||
installing wheels using --find-links (Pull #1218).
|
||||
|
||||
* Fixed #1133 and #317. Archive contents are now written based on system
|
||||
defaults and umask (i.e. permissions are not preserved), except that regular
|
||||
files with any execute permissions have the equivalent of "chmod +x" applied
|
||||
after being written (Pull #1146).
|
||||
|
||||
* PreviousBuildDirError now returns a non-zero exit code and prevents the
|
||||
previous build dir from being cleaned in all cases (Pull #1162).
|
||||
|
||||
* Renamed --allow-insecure to --allow-unverified, however the old name will
|
||||
continue to work for a period of time (Pull #1257).
|
||||
|
||||
* Fixed #1006, error when installing local projects with symlinks in
|
||||
Python 3. (Pull #1311)
|
||||
|
||||
* The previously hidden ``--log-file`` otion, is now shown as a general option.
|
||||
(Pull #1316)
|
||||
|
||||
|
||||
**1.4.1 (2013-08-07)**
|
||||
|
||||
|
||||
* **New Signing Key** Release 1.4.1 is using a different key than normal with
|
||||
fingerprint: 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
|
||||
* Fixed issues with installing from pybundle files (Pull #1116).
|
||||
* Fixed error when sysconfig module throws an exception (Pull #1095).
|
||||
* Don't ignore already installed pre-releases (Pull #1076).
|
||||
* Fixes related to upgrading setuptools (Pull #1092).
|
||||
* Fixes so that --download works with wheel archives (Pull #1113).
|
||||
* Fixes related to recognizing and cleaning global build dirs (Pull #1080).
|
||||
|
||||
|
||||
**1.4 (2013-07-23)**
|
||||
|
||||
|
||||
* **BACKWARD INCOMPATIBLE** pip now only installs stable versions by default,
|
||||
and offers a new ``--pre`` option to also find pre-release and development
|
||||
versions. (Pull #834)
|
||||
|
||||
* **BACKWARD INCOMPATIBLE** Dropped support for Python 2.5. The minimum
|
||||
supported Python version for pip 1.4 is Python 2.6.
|
||||
|
||||
* Added support for installing and building wheel archives.
|
||||
Thanks Daniel Holth, Marcus Smith, Paul Moore, and Michele Lacchia
|
||||
(Pull #845)
|
||||
|
||||
* Applied security patch to pip's ssl support related to certificate DNS
|
||||
wildcard matching (http://bugs.python.org/issue17980).
|
||||
|
||||
* To satisfy pip's setuptools requirement, pip now recommends setuptools>=0.8,
|
||||
not distribute. setuptools and distribute are now merged into one project
|
||||
called 'setuptools'. (Pull #1003)
|
||||
|
||||
* pip will now warn when installing a file that is either hosted externally to
|
||||
the index or cannot be verified with a hash. In the future pip will default
|
||||
to not installing them and will require the flags --allow-external NAME, and
|
||||
--allow-insecure NAME respectively. (Pull #985)
|
||||
|
||||
* If an already-downloaded or cached file has a bad hash, re-download it rather
|
||||
than erroring out. (Issue #963).
|
||||
|
||||
* ``pip bundle`` and support for installing from pybundle files is now
|
||||
considered deprecated and will be removed in pip v1.5.
|
||||
|
||||
* Fixed a number of issues (#413, #709, #634, #602, and #939) related to
|
||||
cleaning up and not reusing build directories. (Pull #865, #948)
|
||||
|
||||
* Added a User Agent so that pip is identifiable in logs. (Pull #901)
|
||||
|
||||
* Added ssl and --user support to get-pip.py. Thanks Gabriel de Perthuis.
|
||||
(Pull #895)
|
||||
|
||||
* Fixed the proxy support, which was broken in pip 1.3.x (Pull #840)
|
||||
|
||||
* Fixed issue #32 - pip fails when server does not send content-type header.
|
||||
Thanks Hugo Lopes Tavares and Kelsey Hightower (Pull #872).
|
||||
|
||||
* "Vendorized" distlib as pip.vendor.distlib (https://distlib.readthedocs.org/).
|
||||
|
||||
* Fixed git VCS backend with git 1.8.3. (Pull #967)
|
||||
|
||||
|
||||
**1.3.1 (2013-03-08)**
|
||||
|
||||
|
||||
* Fixed a major backward incompatible change of parsing URLs to externally
|
||||
hosted packages that got accidentily included in 1.3.
|
||||
|
||||
|
||||
**1.3 (2013-03-07)**
|
||||
|
||||
|
||||
* SSL Cert Verification; Make https the default for PyPI access.
|
||||
Thanks James Cleveland, Giovanni Bajo, Marcus Smith and many others (Pull #791, CVE-2013-1629).
|
||||
|
||||
* Added "pip list" for listing installed packages and the latest version
|
||||
available. Thanks Rafael Caricio, Miguel Araujo, Dmitry Gladkov (Pull #752)
|
||||
|
||||
* Fixed security issues with pip's use of temp build directories.
|
||||
Thanks David (d1b) and Thomas Guttler. (Pull #780, CVE-2013-1888)
|
||||
|
||||
* Improvements to sphinx docs and cli help. (Pull #773)
|
||||
|
||||
* Fixed issue #707, dealing with OS X temp dir handling, which was causing
|
||||
global NumPy installs to fail. (Pull #768)
|
||||
|
||||
* Split help output into general vs command-specific option groups.
|
||||
Thanks Georgi Valkov. (Pull #744; Pull #721 contains preceding refactor)
|
||||
|
||||
* Fixed dependency resolution when installing from archives with uppercase
|
||||
project names. (Pull #724)
|
||||
|
||||
* Fixed problem where re-installs always occurred when using file:// find-links.
|
||||
(Pulls #683/#702)
|
||||
|
||||
* "pip install -v" now shows the full download url, not just the archive name.
|
||||
Thanks Marc Abramowitz (Pull #687)
|
||||
|
||||
* Fix to prevent unnecessary PyPI redirects. Thanks Alex Gronholm (Pull #695)
|
||||
|
||||
* Fixed issue #670 - install failure under Python 3 when the same version
|
||||
of a package is found under 2 different URLs. Thanks Paul Moore (Pull #671)
|
||||
|
||||
* Fix git submodule recursive updates. Thanks Roey Berman. (Pulls #674)
|
||||
|
||||
* Explicitly ignore rel='download' links while looking for html pages.
|
||||
Thanks Maxime R. (Pull #677)
|
||||
|
||||
* --user/--upgrade install options now work together. Thanks 'eevee' for
|
||||
discovering the problem. (Pull #705)
|
||||
|
||||
* Added check in ``install --download`` to prevent re-downloading if the target
|
||||
file already exists. Thanks Andrey Bulgakov. (Pull #669)
|
||||
|
||||
* Added support for bare paths (including relative paths) as argument to
|
||||
`--find-links`. Thanks Paul Moore for draft patch.
|
||||
|
||||
* Added support for --no-index in requirements files.
|
||||
|
||||
* Added "pip show" command to get information about an installed package.
|
||||
Fixes #131. Thanks Kelsey Hightower and Rafael Caricio.
|
||||
|
||||
* Added `--root` option for "pip install" to specify root directory. Behaves
|
||||
like the same option in distutils but also plays nice with pip's egg-info.
|
||||
Thanks Przemek Wrzos. (Issue #253 / Pull #693)
|
||||
|
||||
|
||||
**1.2.1 (2012-09-06)**
|
||||
|
||||
|
||||
* Fixed a regression introduced in 1.2 about raising an exception when
|
||||
not finding any files to uninstall in the current environment. Thanks for
|
||||
the fix, Marcus Smith.
|
||||
|
||||
|
||||
**1.2 (2012-09-01)**
|
||||
|
||||
|
||||
* **Dropped support for Python 2.4** The minimum supported Python version is
|
||||
now Python 2.5.
|
||||
|
||||
* Fixed issue #605 - pypi mirror support broken on some DNS responses. Thanks
|
||||
philwhin.
|
||||
|
||||
* Fixed issue #355 - pip uninstall removes files it didn't install. Thanks
|
||||
pjdelport.
|
||||
|
||||
* Fixed issues #493, #494, #440, and #573 related to improving support for the
|
||||
user installation scheme. Thanks Marcus Smith.
|
||||
|
||||
* Write failure log to temp file if default location is not writable. Thanks
|
||||
andreigc.
|
||||
|
||||
* Pull in submodules for git editable checkouts. Fixes #289 and #421. Thanks
|
||||
Hsiaoming Yang and Markus Hametner.
|
||||
|
||||
* Use a temporary directory as the default build location outside of a
|
||||
virtualenv. Fixes issues #339 and #381. Thanks Ben Rosser.
|
||||
|
||||
* Added support for specifying extras with local editables. Thanks Nick
|
||||
Stenning.
|
||||
|
||||
* Added ``--egg`` flag to request egg-style rather than flat installation. Refs
|
||||
issue #3. Thanks Kamal Bin Mustafa.
|
||||
|
||||
* Fixed issue #510 - prevent e.g. ``gmpy2-2.0.tar.gz`` from matching a request
|
||||
to ``pip install gmpy``; sdist filename must begin with full project name
|
||||
followed by a dash. Thanks casevh for the report.
|
||||
|
||||
* Fixed issue #504 - allow package URLS to have querystrings. Thanks W.
|
||||
Trevor King.
|
||||
|
||||
* Fixed issue #58 - pip freeze now falls back to non-editable format rather
|
||||
than blowing up if it can't determine the origin repository of an editable.
|
||||
Thanks Rory McCann.
|
||||
|
||||
* Added a `__main__.py` file to enable `python -m pip` on Python versions
|
||||
that support it. Thanks Alexey Luchko.
|
||||
|
||||
* Fixed issue #487 - upgrade from VCS url of project that does exist on
|
||||
index. Thanks Andrew Knapp for the report.
|
||||
|
||||
* Fixed issue #486 - fix upgrade from VCS url of project with no distribution
|
||||
on index. Thanks Andrew Knapp for the report.
|
||||
|
||||
* Fixed issue #427 - clearer error message on a malformed VCS url. Thanks
|
||||
Thomas Fenzl.
|
||||
|
||||
* Added support for using any of the built in guaranteed algorithms in
|
||||
``hashlib`` as a checksum hash.
|
||||
|
||||
* Fixed issue #321 - Raise an exception if current working directory can't be
|
||||
found or accessed.
|
||||
|
||||
* Fixed issue #82 - Removed special casing of the user directory and use the
|
||||
Python default instead.
|
||||
|
||||
* Fixed #436 - Only warn about version conflicts if there is actually one.
|
||||
This re-enables using ``==dev`` in requirements files.
|
||||
|
||||
* Moved tests to be run on Travis CI: http://travis-ci.org/pypa/pip
|
||||
|
||||
* Added a better help formatter.
|
||||
|
||||
|
||||
**1.1 (2012-02-16)**
|
||||
|
||||
|
||||
* Fixed issue #326 - don't crash when a package's setup.py emits UTF-8 and
|
||||
then fails. Thanks Marc Abramowitz.
|
||||
|
||||
* Added ``--target`` option for installing directly to arbitrary directory.
|
||||
Thanks Stavros Korokithakis.
|
||||
|
||||
* Added support for authentication with Subversion repositories. Thanks
|
||||
Qiangning Hong.
|
||||
|
||||
* Fixed issue #315 - ``--download`` now downloads dependencies as well.
|
||||
Thanks Qiangning Hong.
|
||||
|
||||
* Errors from subprocesses will display the current working directory.
|
||||
Thanks Antti Kaihola.
|
||||
|
||||
* Fixed issue #369 - compatibility with Subversion 1.7. Thanks Qiangning
|
||||
Hong. Note that setuptools remains incompatible with Subversion 1.7; to
|
||||
get the benefits of pip's support you must use Distribute rather than
|
||||
setuptools.
|
||||
|
||||
* Fixed issue #57 - ignore py2app-generated OS X mpkg zip files in finder.
|
||||
Thanks Rene Dudfield.
|
||||
|
||||
* Fixed issue #182 - log to ~/Library/Logs/ by default on OS X framework
|
||||
installs. Thanks Dan Callahan for report and patch.
|
||||
|
||||
* Fixed issue #310 - understand version tags without minor version ("py3")
|
||||
in sdist filenames. Thanks Stuart Andrews for report and Olivier Girardot for
|
||||
patch.
|
||||
|
||||
* Fixed issue #7 - Pip now supports optionally installing setuptools
|
||||
"extras" dependencies; e.g. "pip install Paste[openid]". Thanks Matt Maker
|
||||
and Olivier Girardot.
|
||||
|
||||
* Fixed issue #391 - freeze no longer borks on requirements files with
|
||||
--index-url or --find-links. Thanks Herbert Pfennig.
|
||||
|
||||
* Fixed issue #288 - handle symlinks properly. Thanks lebedov for the patch.
|
||||
|
||||
* Fixed issue #49 - pip install -U no longer reinstalls the same versions of
|
||||
packages. Thanks iguananaut for the pull request.
|
||||
|
||||
* Removed ``-E``/``--environment`` option and ``PIP_RESPECT_VIRTUALENV``;
|
||||
both use a restart-in-venv mechanism that's broken, and neither one is
|
||||
useful since every virtualenv now has pip inside it. Replace ``pip -E
|
||||
path/to/venv install Foo`` with ``virtualenv path/to/venv &&
|
||||
path/to/venv/pip install Foo``.
|
||||
|
||||
* Fixed issue #366 - pip throws IndexError when it calls `scraped_rel_links`
|
||||
|
||||
* Fixed issue #22 - pip search should set and return a userful shell status code
|
||||
|
||||
* Fixed issue #351 and #365 - added global ``--exists-action`` command line
|
||||
option to easier script file exists conflicts, e.g. from editable
|
||||
requirements from VCS that have a changed repo URL.
|
||||
|
||||
|
||||
**1.0.2 (2011-07-16)**
|
||||
|
||||
|
||||
* Fixed docs issues.
|
||||
* Fixed issue #295 - Reinstall a package when using the ``install -I`` option
|
||||
* Fixed issue #283 - Finds a Git tag pointing to same commit as origin/master
|
||||
* Fixed issue #279 - Use absolute path for path to docs in setup.py
|
||||
* Fixed issue #314 - Correctly handle exceptions on Python3.
|
||||
* Fixed issue #320 - Correctly parse ``--editable`` lines in requirements files
|
||||
|
||||
|
||||
**1.0.1 (2011-04-30)**
|
||||
|
||||
|
||||
* Start to use git-flow.
|
||||
* Fixed issue #274 - `find_command` should not raise AttributeError
|
||||
* Fixed issue #273 - respect Content-Disposition header. Thanks Bradley Ayers.
|
||||
* Fixed issue #233 - pathext handling on Windows.
|
||||
* Fixed issue #252 - svn+svn protocol.
|
||||
* Fixed issue #44 - multiple CLI searches.
|
||||
* Fixed issue #266 - current working directory when running setup.py clean.
|
||||
|
||||
|
||||
**1.0 (2011-04-04)**
|
||||
|
||||
|
||||
* Added Python 3 support! Huge thanks to Vinay Sajip, Vitaly Babiy, Kelsey
|
||||
Hightower, and Alex Gronholm, among others.
|
||||
|
||||
* Download progress only shown on a real TTY. Thanks Alex Morega.
|
||||
|
||||
* Fixed finding of VCS binaries to not be fooled by same-named directories.
|
||||
Thanks Alex Morega.
|
||||
|
||||
* Fixed uninstall of packages from system Python for users of Debian/Ubuntu
|
||||
python-setuptools package (workaround until fixed in Debian and Ubuntu).
|
||||
|
||||
* Added `get-pip.py <https://raw.github.com/pypa/pip/master/contrib/get-pip.py>`_
|
||||
installer. Simply download and execute it, using the Python interpreter of
|
||||
your choice::
|
||||
|
||||
$ curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py
|
||||
$ python get-pip.py
|
||||
|
||||
This may have to be run as root.
|
||||
|
||||
.. note::
|
||||
|
||||
Make sure you have `distribute <http://pypi.python.org/pypi/distribute>`_
|
||||
installed before using the installer!
|
||||
|
||||
|
||||
**0.8.3**
|
||||
|
||||
|
||||
* Moved main repository to Github: https://github.com/pypa/pip
|
||||
|
||||
* Transferred primary maintenance from Ian to Jannis Leidel, Carl Meyer, Brian Rosner
|
||||
|
||||
* Fixed issue #14 - No uninstall-on-upgrade with URL package. Thanks Oliver Tonnhofer
|
||||
|
||||
* Fixed issue #163 - Egg name not properly resolved. Thanks Igor Sobreira
|
||||
|
||||
* Fixed issue #178 - Non-alphabetical installation of requirements. Thanks Igor Sobreira
|
||||
|
||||
* Fixed issue #199 - Documentation mentions --index instead of --index-url. Thanks Kelsey Hightower
|
||||
|
||||
* Fixed issue #204 - rmtree undefined in mercurial.py. Thanks Kelsey Hightower
|
||||
|
||||
* Fixed bug in Git vcs backend that would break during reinstallation.
|
||||
|
||||
* Fixed bug in Mercurial vcs backend related to pip freeze and branch/tag resolution.
|
||||
|
||||
* Fixed bug in version string parsing related to the suffix "-dev".
|
||||
|
||||
|
||||
**0.8.2**
|
||||
|
||||
|
||||
* Avoid redundant unpacking of bundles (from pwaller)
|
||||
|
||||
* Fixed issue #32, #150, #161 - Fixed checking out the correct
|
||||
tag/branch/commit when updating an editable Git requirement.
|
||||
|
||||
* Fixed issue #49 - Added ability to install version control requirements
|
||||
without making them editable, e.g.::
|
||||
|
||||
pip install git+https://github.com/pypa/pip/
|
||||
|
||||
* Fixed issue #175 - Correctly locate build and source directory on Mac OS X.
|
||||
|
||||
* Added ``git+https://`` scheme to Git VCS backend.
|
||||
|
||||
|
||||
**0.8.1**
|
||||
|
||||
|
||||
* Added global --user flag as shortcut for --install-option="--user". From
|
||||
Ronny Pfannschmidt.
|
||||
|
||||
* Added support for `PyPI mirrors <http://pypi.python.org/mirrors>`_ as
|
||||
defined in `PEP 381 <http://www.python.org/dev/peps/pep-0381/>`_, from
|
||||
Jannis Leidel.
|
||||
|
||||
* Fixed issue #138 - Git revisions ignored. Thanks John-Scott Atlakson.
|
||||
|
||||
* Fixed issue #95 - Initial editable install of github package from a tag fails. Thanks John-Scott Atlakson.
|
||||
|
||||
* Fixed issue #107 - Can't install if a directory in cwd has the same name as the package you're installing.
|
||||
|
||||
* Fixed issue #39 - --install-option="--prefix=~/.local" ignored with -e.
|
||||
Thanks Ronny Pfannschmidt and Wil Tan.
|
||||
|
||||
|
||||
**0.8**
|
||||
|
||||
|
||||
* Track which ``build/`` directories pip creates, never remove directories
|
||||
it doesn't create. From Hugo Lopes Tavares.
|
||||
|
||||
* Pip now accepts file:// index URLs. Thanks Dave Abrahams.
|
||||
|
||||
* Various cleanup to make test-running more consistent and less fragile.
|
||||
Thanks Dave Abrahams.
|
||||
|
||||
* Real Windows support (with passing tests). Thanks Dave Abrahams.
|
||||
|
||||
* ``pip-2.7`` etc. scripts are created (Python-version specific scripts)
|
||||
|
||||
* ``contrib/build-standalone`` script creates a runnable ``.zip`` form of
|
||||
pip, from Jannis Leidel
|
||||
|
||||
* Editable git repos are updated when reinstalled
|
||||
|
||||
* Fix problem with ``--editable`` when multiple ``.egg-info/`` directories
|
||||
are found.
|
||||
|
||||
* A number of VCS-related fixes for ``pip freeze``, from Hugo Lopes Tavares.
|
||||
|
||||
* Significant test framework changes, from Hugo Lopes Tavares.
|
||||
|
||||
|
||||
**0.7.2**
|
||||
|
||||
|
||||
* Set zip_safe=False to avoid problems some people are encountering where
|
||||
pip is installed as a zip file.
|
||||
|
||||
|
||||
**0.7.1**
|
||||
|
||||
|
||||
* Fixed opening of logfile with no directory name. Thanks Alexandre Conrad.
|
||||
|
||||
* Temporary files are consistently cleaned up, especially after
|
||||
installing bundles, also from Alex Conrad.
|
||||
|
||||
* Tests now require at least ScriptTest 1.0.3.
|
||||
|
||||
|
||||
**0.7**
|
||||
|
||||
|
||||
* Fixed uninstallation on Windows
|
||||
* Added ``pip search`` command.
|
||||
* Tab-complete names of installed distributions for ``pip uninstall``.
|
||||
* Support tab-completion when there is a global-option before the
|
||||
subcommand.
|
||||
* Install header files in standard (scheme-default) location when installing
|
||||
outside a virtualenv. Install them to a slightly more consistent
|
||||
non-standard location inside a virtualenv (since the standard location is
|
||||
a non-writable symlink to the global location).
|
||||
* pip now logs to a central location by default (instead of creating
|
||||
``pip-log.txt`` all over the place) and constantly overwrites the
|
||||
file in question. On Unix and Mac OS X this is ``'$HOME/.pip/pip.log'``
|
||||
and on Windows it's ``'%HOME%\\pip\\pip.log'``. You are still able to
|
||||
override this location with the ``$PIP_LOG_FILE`` environment variable.
|
||||
For a complete (appended) logfile use the separate ``'--log'`` command line
|
||||
option.
|
||||
* Fixed an issue with Git that left an editable packge as a checkout of a
|
||||
remote branch, even if the default behaviour would have been fine, too.
|
||||
* Fixed installing from a Git tag with older versions of Git.
|
||||
* Expand "~" in logfile and download cache paths.
|
||||
* Speed up installing from Mercurial repositories by cloning without
|
||||
updating the working copy multiple times.
|
||||
* Fixed installing directly from directories (e.g.
|
||||
``pip install path/to/dir/``).
|
||||
* Fixed installing editable packages with ``svn+ssh`` URLs.
|
||||
* Don't print unwanted debug information when running the freeze command.
|
||||
* Create log file directory automatically. Thanks Alexandre Conrad.
|
||||
* Make test suite easier to run successfully. Thanks Dave Abrahams.
|
||||
* Fixed "pip install ." and "pip install .."; better error for directory
|
||||
without setup.py. Thanks Alexandre Conrad.
|
||||
* Support Debian/Ubuntu "dist-packages" in zip command. Thanks duckx.
|
||||
* Fix relative --src folder. Thanks Simon Cross.
|
||||
* Handle missing VCS with an error message. Thanks Alexandre Conrad.
|
||||
* Added --no-download option to install; pairs with --no-install to separate
|
||||
download and installation into two steps. Thanks Simon Cross.
|
||||
* Fix uninstalling from requirements file containing -f, -i, or
|
||||
--extra-index-url.
|
||||
* Leftover build directories are now removed. Thanks Alexandre Conrad.
|
||||
|
||||
|
||||
**0.6.3**
|
||||
|
||||
|
||||
* Fixed import error on Windows with regard to the backwards compatibility
|
||||
package
|
||||
|
||||
|
||||
**0.6.2**
|
||||
|
||||
|
||||
* Fixed uninstall when /tmp is on a different filesystem.
|
||||
|
||||
* Fixed uninstallation of distributions with namespace packages.
|
||||
|
||||
|
||||
**0.6.1**
|
||||
|
||||
|
||||
* Added support for the ``https`` and ``http-static`` schemes to the
|
||||
Mercurial and ``ftp`` scheme to the Bazaar backend.
|
||||
|
||||
* Fixed uninstallation of scripts installed with easy_install.
|
||||
|
||||
* Fixed an issue in the package finder that could result in an
|
||||
infinite loop while looking for links.
|
||||
|
||||
* Fixed issue with ``pip bundle`` and local files (which weren't being
|
||||
copied into the bundle), from Whit Morriss.
|
||||
|
||||
|
||||
**0.6**
|
||||
|
||||
|
||||
* Add ``pip uninstall`` and uninstall-before upgrade (from Carl
|
||||
Meyer).
|
||||
|
||||
* Extended configurability with config files and environment variables.
|
||||
|
||||
* Allow packages to be upgraded, e.g., ``pip install Package==0.1``
|
||||
then ``pip install Package==0.2``.
|
||||
|
||||
* Allow installing/upgrading to Package==dev (fix "Source version does not
|
||||
match target version" errors).
|
||||
|
||||
* Added command and option completion for bash and zsh.
|
||||
|
||||
* Extended integration with virtualenv by providing an option to
|
||||
automatically use an active virtualenv and an option to warn if no active
|
||||
virtualenv is found.
|
||||
|
||||
* Fixed a bug with pip install --download and editable packages, where
|
||||
directories were being set with 0000 permissions, now defaults to 755.
|
||||
|
||||
* Fixed uninstallation of easy_installed console_scripts.
|
||||
|
||||
* Fixed uninstallation on Mac OS X Framework layout installs
|
||||
|
||||
* Fixed bug preventing uninstall of editables with source outside venv.
|
||||
|
||||
* Creates download cache directory if not existing.
|
||||
|
||||
|
||||
**0.5.1**
|
||||
|
||||
|
||||
* Fixed a couple little bugs, with git and with extensions.
|
||||
|
||||
|
||||
**0.5**
|
||||
|
||||
|
||||
* Added ability to override the default log file name (``pip-log.txt``)
|
||||
with the environmental variable ``$PIP_LOG_FILE``.
|
||||
|
||||
* Made the freeze command print installed packages to stdout instead of
|
||||
writing them to a file. Use simple redirection (e.g.
|
||||
``pip freeze > stable-req.txt``) to get a file with requirements.
|
||||
|
||||
* Fixed problem with freezing editable packages from a Git repository.
|
||||
|
||||
* Added support for base URLs using ``<base href='...'>`` when parsing
|
||||
HTML pages.
|
||||
|
||||
* Fixed installing of non-editable packages from version control systems.
|
||||
|
||||
* Fixed issue with Bazaar's bzr+ssh scheme.
|
||||
|
||||
* Added --download-dir option to the install command to retrieve package
|
||||
archives. If given an editable package it will create an archive of it.
|
||||
|
||||
* Added ability to pass local file and directory paths to ``--find-links``,
|
||||
e.g. ``--find-links=file:///path/to/my/private/archive``
|
||||
|
||||
* Reduced the amount of console log messages when fetching a page to find a
|
||||
distribution was problematic. The full messages can be found in pip-log.txt.
|
||||
|
||||
* Added ``--no-deps`` option to install ignore package dependencies
|
||||
|
||||
* Added ``--no-index`` option to ignore the package index (PyPI) temporarily
|
||||
|
||||
* Fixed installing editable packages from Git branches.
|
||||
|
||||
* Fixes freezing of editable packages from Mercurial repositories.
|
||||
|
||||
* Fixed handling read-only attributes of build files, e.g. of Subversion and
|
||||
Bazaar on Windows.
|
||||
|
||||
* When downloading a file from a redirect, use the redirected
|
||||
location's extension to guess the compression (happens specifically
|
||||
when redirecting to a bitbucket.org tip.gz file).
|
||||
|
||||
* Editable freeze URLs now always use revision hash/id rather than tip or
|
||||
branch names which could move.
|
||||
|
||||
* Fixed comparison of repo URLs so incidental differences such as
|
||||
presence/absence of final slashes or quoted/unquoted special
|
||||
characters don't trigger "ignore/switch/wipe/backup" choice.
|
||||
|
||||
* Fixed handling of attempt to checkout editable install to a
|
||||
non-empty, non-repo directory.
|
||||
|
||||
|
||||
**0.4**
|
||||
|
||||
|
||||
* Make ``-e`` work better with local hg repositories
|
||||
|
||||
* Construct PyPI URLs the exact way easy_install constructs URLs (you
|
||||
might notice this if you use a custom index that is
|
||||
slash-sensitive).
|
||||
|
||||
* Improvements on Windows (from `Ionel Maries Cristian
|
||||
<http://ionelmc.wordpress.com/>`_).
|
||||
|
||||
* Fixed problem with not being able to install private git repositories.
|
||||
|
||||
* Make ``pip zip`` zip all its arguments, not just the first.
|
||||
|
||||
* Fix some filename issues on Windows.
|
||||
|
||||
* Allow the ``-i`` and ``--extra-index-url`` options in requirements
|
||||
files.
|
||||
|
||||
* Fix the way bundle components are unpacked and moved around, to make
|
||||
bundles work.
|
||||
|
||||
* Adds ``-s`` option to allow the access to the global site-packages if a
|
||||
virtualenv is to be created.
|
||||
|
||||
* Fixed support for Subversion 1.6.
|
||||
|
||||
|
||||
**0.3.1**
|
||||
|
||||
|
||||
* Improved virtualenv restart and various path/cleanup problems on win32.
|
||||
|
||||
* Fixed a regression with installing from svn repositories (when not
|
||||
using ``-e``).
|
||||
|
||||
* Fixes when installing editable packages that put their source in a
|
||||
subdirectory (like ``src/``).
|
||||
|
||||
* Improve ``pip -h``
|
||||
|
||||
|
||||
**0.3**
|
||||
|
||||
|
||||
* Added support for editable packages created from Git, Mercurial and Bazaar
|
||||
repositories and ability to freeze them. Refactored support for version
|
||||
control systems.
|
||||
|
||||
* Do not use ``sys.exit()`` from inside the code, instead use a
|
||||
return. This will make it easier to invoke programmatically.
|
||||
|
||||
* Put the install record in ``Package.egg-info/installed-files.txt``
|
||||
(previously they went in
|
||||
``site-packages/install-record-Package.txt``).
|
||||
|
||||
* Fix a problem with ``pip freeze`` not including ``-e svn+`` when an
|
||||
svn structure is peculiar.
|
||||
|
||||
* Allow ``pip -E`` to work with a virtualenv that uses a different
|
||||
version of Python than the parent environment.
|
||||
|
||||
* Fixed Win32 virtualenv (``-E``) option.
|
||||
|
||||
* Search the links passed in with ``-f`` for packages.
|
||||
|
||||
* Detect zip files, even when the file doesn't have a ``.zip``
|
||||
extension and it is served with the wrong Content-Type.
|
||||
|
||||
* Installing editable from existing source now works, like ``pip
|
||||
install -e some/path/`` will install the package in ``some/path/``.
|
||||
Most importantly, anything that package requires will also be
|
||||
installed by pip.
|
||||
|
||||
* Add a ``--path`` option to ``pip un/zip``, so you can avoid zipping
|
||||
files that are outside of where you expect.
|
||||
|
||||
* Add ``--simulate`` option to ``pip zip``.
|
||||
|
||||
|
||||
**0.2.1**
|
||||
|
||||
|
||||
* Fixed small problem that prevented using ``pip.py`` without actually
|
||||
installing pip.
|
||||
|
||||
* Fixed ``--upgrade``, which would download and appear to install
|
||||
upgraded packages, but actually just reinstall the existing package.
|
||||
|
||||
* Fixed Windows problem with putting the install record in the right
|
||||
place, and generating the ``pip`` script with Setuptools.
|
||||
|
||||
* Download links that include embedded spaces or other unsafe
|
||||
characters (those characters get %-encoded).
|
||||
|
||||
* Fixed use of URLs in requirement files, and problems with some blank
|
||||
lines.
|
||||
|
||||
* Turn some tar file errors into warnings.
|
||||
|
||||
|
||||
**0.2**
|
||||
|
||||
|
||||
* Renamed to ``pip``, and to install you now do ``pip install
|
||||
PACKAGE``
|
||||
|
||||
* Added command ``pip zip PACKAGE`` and ``pip unzip PACKAGE``. This
|
||||
is particularly intended for Google App Engine to manage libraries
|
||||
to stay under the 1000-file limit.
|
||||
|
||||
* Some fixes to bundles, especially editable packages and when
|
||||
creating a bundle using unnamed packages (like just an svn
|
||||
repository without ``#egg=Package``).
|
||||
|
||||
|
||||
**0.1.4**
|
||||
|
||||
|
||||
* Added an option ``--install-option`` to pass options to pass
|
||||
arguments to ``setup.py install``
|
||||
|
||||
* ``.svn/`` directories are no longer included in bundles, as these
|
||||
directories are specific to a version of svn -- if you build a
|
||||
bundle on a system with svn 1.5, you can't use the checkout on a
|
||||
system with svn 1.4. Instead a file ``svn-checkout.txt`` is
|
||||
included that notes the original location and revision, and the
|
||||
command you can use to turn it back into an svn checkout. (Probably
|
||||
unpacking the bundle should, maybe optionally, recreate this
|
||||
information -- but that is not currently implemented, and it would
|
||||
require network access.)
|
||||
|
||||
* Avoid ambiguities over project name case, where for instance
|
||||
MyPackage and mypackage would be considered different packages.
|
||||
This in particular caused problems on Macs, where ``MyPackage/`` and
|
||||
``mypackage/`` are the same directory.
|
||||
|
||||
* Added support for an environmental variable
|
||||
``$PIP_DOWNLOAD_CACHE`` which will cache package downloads, so
|
||||
future installations won't require large downloads. Network access
|
||||
is still required, but just some downloads will be avoided when
|
||||
using this.
|
||||
|
||||
|
||||
**0.1.3**
|
||||
|
||||
|
||||
* Always use ``svn checkout`` (not ``export``) so that
|
||||
``tag_svn_revision`` settings give the revision of the package.
|
||||
|
||||
* Don't update checkouts that came from ``.pybundle`` files.
|
||||
|
||||
|
||||
**0.1.2**
|
||||
|
||||
|
||||
* Improve error text when there are errors fetching HTML pages when
|
||||
seeking packages.
|
||||
|
||||
* Improve bundles: include empty directories, make them work with
|
||||
editable packages.
|
||||
|
||||
* If you use ``-E env`` and the environment ``env/`` doesn't exist, a
|
||||
new virtual environment will be created.
|
||||
|
||||
* Fix ``dependency_links`` for finding packages.
|
||||
|
||||
|
||||
**0.1.1**
|
||||
|
||||
|
||||
* Fixed a NameError exception when running pip outside of a
|
||||
virtualenv environment.
|
||||
|
||||
* Added HTTP proxy support (from Prabhu Ramachandran)
|
||||
|
||||
* Fixed use of ``hashlib.md5`` on python2.5+ (also from Prabhu
|
||||
Ramachandran)
|
||||
|
||||
|
||||
**0.1**
|
||||
|
||||
|
||||
* Initial release
|
||||
@@ -1,6 +1,4 @@
|
||||
Copyright (c) 2007 Ian Bicking and Contributors
|
||||
Copyright (c) 2009 Ian Bicking, The Open Planning Project
|
||||
Copyright (c) 2011 The virtualenv developers
|
||||
Copyright (c) 2008-2014 The pip developers (see AUTHORS.txt file)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
include AUTHORS.txt
|
||||
include LICENSE.txt
|
||||
include CHANGES.txt
|
||||
include PROJECT.txt
|
||||
include pip/cacert.pem
|
||||
recursive-include docs *.rst
|
||||
recursive-include docs *.html
|
||||
recursive-exclude docs/_build *.rst
|
||||
prune docs/_build/_sources
|
||||
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: pip
|
||||
Version: 1.5.4
|
||||
Summary: A tool for installing and managing Python packages.
|
||||
Home-page: http://www.pip-installer.org
|
||||
Author: The pip developers
|
||||
Author-email: python-virtualenv@groups.google.com
|
||||
License: MIT
|
||||
Description:
|
||||
Project Info
|
||||
============
|
||||
|
||||
* Project Page: https://github.com/pypa/pip
|
||||
* Install howto: http://www.pip-installer.org/en/latest/installing.html
|
||||
* Changelog: http://www.pip-installer.org/en/latest/news.html
|
||||
* Bug Tracking: https://github.com/pypa/pip/issues
|
||||
* Mailing list: http://groups.google.com/group/python-virtualenv
|
||||
* Docs: http://www.pip-installer.org/
|
||||
* User IRC: #pip on Freenode.
|
||||
* Dev IRC: #pypa on Freenode.
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
First, :doc:`Install pip <installing>`.
|
||||
|
||||
Install a package from `PyPI`_:
|
||||
|
||||
::
|
||||
|
||||
$ pip install SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
Show what files were installed:
|
||||
|
||||
::
|
||||
|
||||
$ pip show --files SomePackage
|
||||
Name: SomePackage
|
||||
Version: 1.0
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Files:
|
||||
../somepackage/__init__.py
|
||||
[...]
|
||||
|
||||
List what packages are outdated:
|
||||
|
||||
::
|
||||
|
||||
$ pip list --outdated
|
||||
SomePackage (Current: 1.0 Latest: 2.0)
|
||||
|
||||
Upgrade a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip install --upgrade SomePackage
|
||||
[...]
|
||||
Found existing installation: SomePackage 1.0
|
||||
Uninstalling SomePackage:
|
||||
Successfully uninstalled SomePackage
|
||||
Running setup.py install for SomePackage
|
||||
Successfully installed SomePackage
|
||||
|
||||
Uninstall a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip uninstall SomePackage
|
||||
Uninstalling SomePackage:
|
||||
/my/env/lib/pythonx.x/site-packages/somepackage
|
||||
Proceed (y/n)? y
|
||||
Successfully uninstalled SomePackage
|
||||
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/
|
||||
|
||||
Keywords: easy_install distutils setuptools egg virtualenv
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Topic :: Software Development :: Build Tools
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.1
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
Project Info
|
||||
============
|
||||
|
||||
* Project Page: https://github.com/pypa/pip
|
||||
* Install howto: http://www.pip-installer.org/en/latest/installing.html
|
||||
* Changelog: http://www.pip-installer.org/en/latest/news.html
|
||||
* Bug Tracking: https://github.com/pypa/pip/issues
|
||||
* Mailing list: http://groups.google.com/group/python-virtualenv
|
||||
* Docs: http://www.pip-installer.org/
|
||||
* User IRC: #pip on Freenode.
|
||||
* Dev IRC: #pypa on Freenode.
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
pip
|
||||
===
|
||||
|
||||
.. image:: https://pypip.in/v/pip/badge.png
|
||||
:target: https://crate.io/packages/pip
|
||||
|
||||
.. image:: https://secure.travis-ci.org/pypa/pip.png?branch=develop
|
||||
:target: http://travis-ci.org/pypa/pip
|
||||
|
||||
For documentation, see http://www.pip-installer.org
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
:orphan:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
This content is now covered in the :doc:`User Guide <user_guide>`
|
||||
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
:orphan:
|
||||
|
||||
============
|
||||
Cookbook
|
||||
============
|
||||
|
||||
This content is now covered in the :doc:`User Guide <user_guide>`
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
===========
|
||||
Development
|
||||
===========
|
||||
|
||||
Pull Requests
|
||||
=============
|
||||
|
||||
Submit Pull Requests against the `develop` branch.
|
||||
|
||||
Provide a good description of what you're doing and why.
|
||||
|
||||
Provide tests that cover your changes and try to run the tests locally first.
|
||||
|
||||
Automated Testing
|
||||
=================
|
||||
|
||||
All pull requests and merges to 'develop' branch are tested in `Travis <https://travis-ci.org/>`_
|
||||
based on our `.travis.yml file <https://github.com/pypa/pip/blob/develop/.travis.yml>`_.
|
||||
|
||||
Usually, a link to your specific travis build appears in pull requests, but if not,
|
||||
you can find it on our `travis pull requests page <https://travis-ci.org/pypa/pip/pull_requests>`_
|
||||
|
||||
The only way to trigger Travis to run again for a pull request, is to submit another change to the pull branch.
|
||||
|
||||
We also have Jenkins CI that runs regularly for certain python versions on windows and centos.
|
||||
|
||||
Running tests
|
||||
=============
|
||||
|
||||
OS Requirements: subversion, bazaar, git, and mercurial.
|
||||
|
||||
Python Requirements: tox or pytest, virtualenv, scripttest, and mock
|
||||
|
||||
Ways to run the tests locally:
|
||||
|
||||
::
|
||||
|
||||
$ tox -e py33 # The preferred way to run the tests, can use pyNN to
|
||||
# run for a particular version or leave off the -e to
|
||||
# run for all versions.
|
||||
$ python setup.py test # Using the setuptools test plugin
|
||||
$ py.test # Using py.test directly
|
||||
$ tox # Using tox against pip's tox.ini
|
||||
|
||||
|
||||
Getting Involved
|
||||
================
|
||||
|
||||
The pip project welcomes help in the following ways:
|
||||
|
||||
- Making Pull Requests for code, tests, or docs.
|
||||
- Commenting on open issues and pull requests.
|
||||
- Helping to answer questions on the mailing list.
|
||||
|
||||
If you want to become an official maintainer, start by helping out.
|
||||
|
||||
Later, when you think you're ready, get in touch with one of the maintainers,
|
||||
and they will initiate a vote.
|
||||
|
||||
Release Process
|
||||
===============
|
||||
|
||||
This process includes virtualenv, since pip releases necessitate a virtualenv release.
|
||||
|
||||
As an example, the instructions assume we're releasing pip-1.4, and virtualenv-1.10.
|
||||
|
||||
1. Upgrade setuptools, if needed:
|
||||
|
||||
#. Upgrade setuptools in ``virtualenv/develop`` using the :ref:`Refresh virtualenv` process.
|
||||
#. Create a pull request against ``pip/develop`` with a modified ``.travis.yml`` file that installs virtualenv from ``virtualenv/develop``, to confirm the travis builds are still passing.
|
||||
|
||||
2. Create Release branches:
|
||||
|
||||
#. Create ``pip/release-1.4`` branch.
|
||||
#. In ``pip/develop``, change ``pip.version`` to '1.5.dev1'.
|
||||
#. Create ``virtualenv/release-1.10`` branch.
|
||||
#. In ``virtualenv/develop``, change ``virtualenv.version`` to '1.11.dev1'.
|
||||
|
||||
3. Prepare "rcX":
|
||||
|
||||
#. In ``pip/release-1.4``, change ``pip.version`` to '1.4rcX', and tag with '1.4rcX'.
|
||||
#. Build a pip sdist from ``pip/release-1.4``, and build it into ``virtualenv/release-1.10`` using the :ref:`Refresh virtualenv` process.
|
||||
#. In ``virtualenv/release-1.10``, change ``virtualenv.version`` to '1.10rcX', and tag with '1.10rcX'.
|
||||
|
||||
4. Announce ``pip-1.4rcX`` and ``virtualenv-1.10rcX`` with the :ref:`RC Install Instructions` and elicit feedback.
|
||||
|
||||
5. Apply fixes to 'rcX':
|
||||
|
||||
#. Apply fixes to ``pip/release-1.4`` and ``virtualenv/release-1.10``
|
||||
#. Periodically merge fixes to ``pip/develop`` and ``virtualenv/develop``
|
||||
|
||||
6. Repeat #4 thru #6 if needed.
|
||||
|
||||
7. Final Release:
|
||||
|
||||
#. In ``pip/release-1.4``, change ``pip.version`` to '1.4', and tag with '1.4'.
|
||||
#. Merge ``pip/release-1.4`` to ``pip/master``.
|
||||
#. Build a pip sdist from ``pip/release-1.4``, and load it into ``virtualenv/release-1.10`` using the :ref:`Refresh virtualenv` process.
|
||||
#. Merge ``vitualenv/release-1.10`` to ``virtualenv/develop``.
|
||||
#. In ``virtualenv/release-1.10``, change ``virtualenv.version`` to '1.10', and tag with '1.10'.
|
||||
#. Merge ``virtualenv/release-1.10`` to ``virtualenv/master``
|
||||
#. Build and upload pip and virtualenv sdists to PyPI.
|
||||
|
||||
.. _`Refresh virtualenv`:
|
||||
|
||||
Refresh virtualenv
|
||||
++++++++++++++++++
|
||||
|
||||
#. Update the embedded versions of pip and setuptools in ``virtualenv_support``.
|
||||
#. Run ``bin/rebuild-script.py`` to rebuild virtualenv based on the latest versions.
|
||||
|
||||
|
||||
.. _`RC Install Instructions`:
|
||||
|
||||
RC Install Instructions
|
||||
+++++++++++++++++++++++
|
||||
|
||||
::
|
||||
|
||||
$ curl -L -O https://github.com/pypa/virtualenv/archive/1.10rc1.tar.gz
|
||||
$ echo "<md5sum value> 1.10rc1.tar.gz" | md5sum -c
|
||||
1.10rc1.tar.gz: OK
|
||||
$ tar zxf 1.10rc1.tar.gz
|
||||
$ python virtualenv-1.10rc1/virtualenv.py myVE
|
||||
$ myVE/bin/pip install SomePackage
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
:orphan:
|
||||
|
||||
"ImportError: No module named setuptools"
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Although using ``pip install --upgrade setuptools`` to upgrade from distribute
|
||||
to setuptools works in isolation, it's possible to get "ImportError: No module
|
||||
named setuptools" when using pip<1.4 to upgrade a package that depends on
|
||||
setuptools or distribute.
|
||||
|
||||
e.g. when running a command like this: `pip install --upgrade pyramid`
|
||||
|
||||
Solution
|
||||
~~~~~~~~
|
||||
|
||||
To prevent the problem in *new* environments (that aren't broken yet):
|
||||
|
||||
* Option 1:
|
||||
|
||||
* *First* run `pip install -U setuptools`,
|
||||
* *Then* run the command to upgrade your package (e.g. `pip install --upgrade pyramid`)
|
||||
|
||||
* Option 2:
|
||||
|
||||
* Upgrade pip using :ref:`get-pip <get-pip>`
|
||||
* *Then* run the command to upgrade your package (e.g. `pip install --upgrade pyramid`)
|
||||
|
||||
To fix the problem once it's occurred, you'll need to manually install the new
|
||||
setuptools, then rerun the upgrade that failed.
|
||||
|
||||
1. Download `ez_setup.py` (https://bitbucket.org/pypa/setuptools/downloads/ez_setup.py)
|
||||
2. Run `python ez_setup.py`
|
||||
3. Then rerun your upgrade (e.g. `pip install --upgrade pyramid`)
|
||||
|
||||
|
||||
Cause
|
||||
~~~~~
|
||||
|
||||
distribute-0.7.3 is just an empty wrapper that only serves to require the new
|
||||
setuptools (setuptools>=0.7) so that it will be installed. (If you don't know
|
||||
yet, the "new setuptools" is a merge of distribute and setuptools back into one
|
||||
project).
|
||||
|
||||
distribute-0.7.3 does its job well, when the upgrade is done in isolation.
|
||||
E.g. if you're currently on distribute-0.6.X, then running `pip install -U
|
||||
setuptools` works fine to upgrade you to setuptools>=0.7.
|
||||
|
||||
The problem occurs when:
|
||||
|
||||
1. you are currently using an older distribute (i.e. 0.6.X)
|
||||
2. and you try to use pip to upgrade a package that *depends* on setuptools or
|
||||
distribute.
|
||||
|
||||
As part of the upgrade process, pip builds an install list that ends up
|
||||
including distribute-0.7.3 and setuptools>=0.7 , but they can end up being
|
||||
separated by other dependencies in the list, so what can happen is this:
|
||||
|
||||
1. pip uninstalls the existing distribute
|
||||
2. pip installs distribute-0.7.3 (which has no importable setuptools, that pip
|
||||
*needs* internally to function)
|
||||
3. pip moves on to install another dependency (before setuptools>=0.7) and is
|
||||
unable to proceed without the setuptools package
|
||||
|
||||
Note that pip v1.4 has fixes to prevent this. distribute-0.7.3 (or
|
||||
setuptools>=0.7) by themselves cannot prevent this kind of problem.
|
||||
|
||||
|
||||
.. _setuptools: https://pypi.python.org/pypi/setuptools
|
||||
.. _distribute: https://pypi.python.org/pypi/distribute
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
pip
|
||||
===
|
||||
|
||||
`User list <http://groups.google.com/group/python-virtualenv>`_ |
|
||||
`Dev list <http://groups.google.com/group/pypa-dev>`_ |
|
||||
`Github <https://github.com/pypa/pip>`_ |
|
||||
`PyPI <https://pypi.python.org/pypi/pip/>`_ |
|
||||
User IRC: #pip |
|
||||
Dev IRC: #pypa
|
||||
|
||||
The `PyPA recommended
|
||||
<https://python-packaging-user-guide.readthedocs.org/en/latest/current.html>`_
|
||||
tool for installing and managing Python packages.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
quickstart
|
||||
installing
|
||||
user_guide
|
||||
reference/index
|
||||
development
|
||||
news
|
||||
|
||||
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
.. _`Installation`:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Python & OS Support
|
||||
-------------------
|
||||
|
||||
pip works with CPython versions 2.6, 2.7, 3.1, 3.2, 3.3, 3.4 and also pypy.
|
||||
|
||||
pip works on Unix/Linux, OS X, and Windows.
|
||||
|
||||
.. note::
|
||||
|
||||
Python 2.5 was supported through v1.3.1, and Python 2.4 was supported through v1.1.
|
||||
|
||||
|
||||
.. _`get-pip`:
|
||||
|
||||
Install or Upgrade pip
|
||||
----------------------
|
||||
|
||||
To install or upgrade pip, securely download `get-pip.py
|
||||
<https://raw.github.com/pypa/pip/master/contrib/get-pip.py>`_. [1]_
|
||||
|
||||
Then run the following (which may require administrator access)::
|
||||
|
||||
$ python get-pip.py
|
||||
|
||||
If `setuptools`_ (or `distribute`_) is not already installed, ``get-pip.py`` will
|
||||
install `setuptools`_ for you. [2]_
|
||||
|
||||
To upgrade an existing `setuptools`_ (or `distribute`_), run ``pip install -U setuptools`` [3]_
|
||||
|
||||
|
||||
Using Package Managers
|
||||
----------------------
|
||||
|
||||
On Linux, pip will generally be available for the system install of python using
|
||||
the system package manager, although often the latest version will be
|
||||
unavailable.
|
||||
|
||||
On Debian and Ubuntu::
|
||||
|
||||
$ sudo apt-get install python-pip
|
||||
|
||||
On Fedora::
|
||||
|
||||
$ sudo yum install python-pip
|
||||
|
||||
|
||||
.. [1] "Secure" in this context means using a modern browser or a
|
||||
tool like `curl` that verifies SSL certificates when downloading from
|
||||
https URLs.
|
||||
|
||||
.. [2] Beginning with pip v1.5.1, ``get-pip.py`` stopped requiring setuptools to
|
||||
be installed first.
|
||||
|
||||
.. [3] Although using ``pip install --upgrade setuptools`` to upgrade from
|
||||
distribute to setuptools works in isolation, it's possible to get
|
||||
"ImportError: No module named setuptools" when using pip<1.4 to upgrade a
|
||||
package that depends on setuptools or distribute. See :doc:`here for
|
||||
details <distribute_setuptools>`.
|
||||
|
||||
.. _setuptools: https://pypi.python.org/pypi/setuptools
|
||||
.. _distribute: https://pypi.python.org/pypi/distribute
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
:orphan:
|
||||
|
||||
================
|
||||
Internal Details
|
||||
================
|
||||
|
||||
This content is now covered in the :doc:`Reference Guide <reference/index>`
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
=============
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
.. include:: ../CHANGES.txt
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
First, :doc:`Install pip <installing>`.
|
||||
|
||||
Install a package from `PyPI`_:
|
||||
|
||||
::
|
||||
|
||||
$ pip install SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
Show what files were installed:
|
||||
|
||||
::
|
||||
|
||||
$ pip show --files SomePackage
|
||||
Name: SomePackage
|
||||
Version: 1.0
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Files:
|
||||
../somepackage/__init__.py
|
||||
[...]
|
||||
|
||||
List what packages are outdated:
|
||||
|
||||
::
|
||||
|
||||
$ pip list --outdated
|
||||
SomePackage (Current: 1.0 Latest: 2.0)
|
||||
|
||||
Upgrade a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip install --upgrade SomePackage
|
||||
[...]
|
||||
Found existing installation: SomePackage 1.0
|
||||
Uninstalling SomePackage:
|
||||
Successfully uninstalled SomePackage
|
||||
Running setup.py install for SomePackage
|
||||
Successfully installed SomePackage
|
||||
|
||||
Uninstall a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip uninstall SomePackage
|
||||
Uninstalling SomePackage:
|
||||
/my/env/lib/pythonx.x/site-packages/somepackage
|
||||
Proceed (y/n)? y
|
||||
Successfully uninstalled SomePackage
|
||||
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
===============
|
||||
Reference Guide
|
||||
===============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
pip
|
||||
pip_install
|
||||
pip_uninstall
|
||||
pip_freeze
|
||||
pip_list
|
||||
pip_show
|
||||
pip_search
|
||||
pip_wheel
|
||||
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
|
||||
pip
|
||||
---
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
::
|
||||
|
||||
pip <command> [options]
|
||||
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
|
||||
.. _`Logging`:
|
||||
|
||||
Logging
|
||||
=======
|
||||
|
||||
Console logging
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
pip offers :ref:`-v, --verbose <--verbose>` and :ref:`-q, --quiet <--quiet>`
|
||||
to control the console log level. Each option can be used multiple times and
|
||||
used together. One ``-v`` increases the verbosity by one, whereas one ``-q`` decreases it by
|
||||
one.
|
||||
|
||||
The series of log levels, in order, are as follows::
|
||||
|
||||
VERBOSE_DEBUG, DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL
|
||||
|
||||
``NOTIFY`` is the default level.
|
||||
|
||||
A few examples on how the parameters work to affect the level:
|
||||
|
||||
* specifying nothing results in ``NOTIFY``
|
||||
* ``-v`` results in ``INFO``
|
||||
* ``-vv`` results in ``DEBUG``
|
||||
* ``-q`` results in ``WARN``
|
||||
* ``-vq`` results in ``NOTIFY``
|
||||
|
||||
The most practical use case for users is either ``-v`` or ``-vv`` to see
|
||||
additional logging to help troubleshoot an issue.
|
||||
|
||||
|
||||
.. _`FileLogging`:
|
||||
|
||||
File logging
|
||||
~~~~~~~~~~~~
|
||||
|
||||
pip offers the :ref:`--log <--log>` option for specifying a file where a maximum
|
||||
verbosity log will be kept. This option is empty by default. This log appends
|
||||
to previous logging.
|
||||
|
||||
Additionally, when commands fail (i.e. return a non-zero exit code), pip writes
|
||||
a "failure log" for the failed command. This log overwrites previous
|
||||
logging. The default location is as follows:
|
||||
|
||||
* On Unix and Mac OS X: :file:`$HOME/.pip/pip.log`
|
||||
* On Windows, the configuration file is: :file:`%HOME%\\pip\\pip.log`
|
||||
|
||||
The option for the failure log, is :ref:`--log-file <--log-file>`.
|
||||
|
||||
Both logs add a line per execution to specify the date and what pip executable wrote the log.
|
||||
|
||||
Like all pip options, ``--log`` and ``log-file``, can also be set as an environment
|
||||
variable, or placed into the pip config file. See the :ref:`Configuration`
|
||||
section.
|
||||
|
||||
.. _`General Options`:
|
||||
|
||||
General Options
|
||||
***************
|
||||
|
||||
.. pip-general-options::
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
.. _`pip freeze`:
|
||||
|
||||
pip freeze
|
||||
-----------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: freeze
|
||||
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: freeze
|
||||
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: freeze
|
||||
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1) Generate output suitable for a requirements file.
|
||||
|
||||
::
|
||||
|
||||
$ pip freeze
|
||||
Jinja2==2.6
|
||||
Pygments==1.5
|
||||
Sphinx==1.1.3
|
||||
docutils==0.9.1
|
||||
|
||||
|
||||
2) Generate a requirements file and then install from it in another environment.
|
||||
|
||||
::
|
||||
|
||||
$ env1/bin/pip freeze > requirements.txt
|
||||
$ env2/bin/pip install -r requirements.txt
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
|
||||
.. _`pip install`:
|
||||
|
||||
pip install
|
||||
-----------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: install
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: install
|
||||
|
||||
|
||||
.. _`Requirements File Format`:
|
||||
|
||||
Requirements File Format
|
||||
++++++++++++++++++++++++
|
||||
|
||||
Each line of the requirements file indicates something to be installed,
|
||||
and like arguments to :ref:`pip install`, the following forms are supported::
|
||||
|
||||
<requirement specifier>
|
||||
<archive url/path>
|
||||
[-e] <local project path>
|
||||
[-e] <vcs project url>
|
||||
|
||||
See the :ref:`pip install Examples<pip install Examples>` for examples of all these forms.
|
||||
|
||||
A line beginning with ``#`` is treated as a comment and ignored.
|
||||
|
||||
Additionally, the following Package Index Options are supported:
|
||||
|
||||
* :ref:`-i, --index-url <--index-url>`
|
||||
* :ref:`--extra-index-url <--extra-index-url>`
|
||||
* :ref:`--no-index <--no-index>`
|
||||
* :ref:`-f, --find-links <--find-links>`
|
||||
* :ref:`--allow-external <--allow-external>`
|
||||
* :ref:`--allow-all-external <--allow-external>`
|
||||
* :ref:`--allow-unverified <--allow-unverified>`
|
||||
|
||||
For example, to specify :ref:`--no-index <--no-index>` and 2 :ref:`--find-links <--find-links>` locations:
|
||||
|
||||
::
|
||||
|
||||
--no-index
|
||||
--find-links /my/local/archives
|
||||
--find-links http://some.archives.com/archives
|
||||
|
||||
|
||||
Lastly, if you wish, you can refer to other requirements files, like this::
|
||||
|
||||
-r more_requirements.txt
|
||||
|
||||
.. _`Requirement Specifiers`:
|
||||
|
||||
Requirement Specifiers
|
||||
++++++++++++++++++++++
|
||||
|
||||
pip supports installing from "requirement specifiers" as implemented in
|
||||
`pkg_resources Requirements <http://packages.python.org/setuptools/pkg_resources.html#requirement-objects>`_
|
||||
|
||||
Some Examples:
|
||||
|
||||
::
|
||||
|
||||
'FooProject >= 1.2'
|
||||
Fizzy [foo, bar]
|
||||
'PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1'
|
||||
SomethingWhoseVersionIDontCareAbout
|
||||
|
||||
.. note::
|
||||
|
||||
Use single or double quotes around specifiers to avoid ``>`` and ``<`` being
|
||||
interpreted as shell redirects. e.g. ``pip install 'FooProject>=1.2'``.
|
||||
|
||||
|
||||
|
||||
|
||||
.. _`Pre Release Versions`:
|
||||
|
||||
Pre-release Versions
|
||||
++++++++++++++++++++
|
||||
|
||||
Starting with v1.4, pip will only install stable versions as specified by
|
||||
`PEP426`_ by default. If a version cannot be parsed as a compliant `PEP426`_
|
||||
version then it is assumed to be a pre-release.
|
||||
|
||||
If a Requirement specifier includes a pre-release or development version
|
||||
(e.g. ``>=0.0.dev0``) then pip will allow pre-release and development versions
|
||||
for that requirement. This does not include the != flag.
|
||||
|
||||
The ``pip install`` command also supports a :ref:`--pre <install_--pre>` flag
|
||||
that will enable installing pre-releases and development releases.
|
||||
|
||||
|
||||
.. _PEP426: http://www.python.org/dev/peps/pep-0426
|
||||
|
||||
.. _`Externally Hosted Files`:
|
||||
|
||||
Externally Hosted Files
|
||||
+++++++++++++++++++++++
|
||||
|
||||
Starting with v1.4, pip will warn about installing any file that does not come
|
||||
from the primary index. As of version 1.5, pip defaults to ignoring these files
|
||||
unless asked to consider them.
|
||||
|
||||
The ``pip install`` command supports a
|
||||
:ref:`--allow-external PROJECT <--allow-external>` option that will enable
|
||||
installing links that are linked directly from the simple index but to an
|
||||
external host that also have a supported hash fragment. Externally hosted
|
||||
files for all projects may be enabled using the
|
||||
:ref:`--allow-all-external <--allow-all-external>` flag to the ``pip install``
|
||||
command.
|
||||
|
||||
The ``pip install`` command also supports a
|
||||
:ref:`--allow-unverified PROJECT <--allow-unverified>` option that will enable
|
||||
installing insecurely linked files. These are either directly linked (as above)
|
||||
files without a hash, or files that are linked from either the home page or the
|
||||
download url of a package.
|
||||
|
||||
These options can be used in a requirements file. Assuming some fictional
|
||||
`ExternalPackage` that is hosted external and unverified, then your requirements
|
||||
file would be like so::
|
||||
|
||||
--allow-external ExternalPackage
|
||||
--allow-unverified ExternalPackage
|
||||
ExternalPackage
|
||||
|
||||
|
||||
.. _`VCS Support`:
|
||||
|
||||
VCS Support
|
||||
+++++++++++
|
||||
|
||||
pip supports installing from Git, Mercurial, Subversion and Bazaar, and detects
|
||||
the type of VCS using url prefixes: "git+", "hg+", "bzr+", "svn+".
|
||||
|
||||
pip requires a working VCS command on your path: git, hg, svn, or bzr.
|
||||
|
||||
VCS projects can be installed in :ref:`editable mode <editable-installs>` (using
|
||||
the :ref:`--editable <install_--editable>` option) or not.
|
||||
|
||||
* For editable installs, the clone location by default is "<venv
|
||||
path>/src/SomeProject" in virtual environments, and "<cwd>/src/SomeProject"
|
||||
for global installs. The :ref:`--src <install_--src>` option can be used to
|
||||
modify this location.
|
||||
* For non-editable installs, the project is built locally in a temp dir and then
|
||||
installed normally.
|
||||
|
||||
The url suffix "egg=<project name>" is used by pip in it's dependency logic to
|
||||
identify the project prior to pip downloading and analyzing the metadata.
|
||||
|
||||
Git
|
||||
~~~
|
||||
|
||||
pip currently supports cloning over ``git``, ``git+https`` and ``git+ssh``:
|
||||
|
||||
Here are the supported forms::
|
||||
|
||||
[-e] git+git://git.myproject.org/MyProject#egg=MyProject
|
||||
[-e] git+https://git.myproject.org/MyProject#egg=MyProject
|
||||
[-e] git+ssh://git.myproject.org/MyProject#egg=MyProject
|
||||
-e git+git@git.myproject.org:MyProject#egg=MyProject
|
||||
|
||||
Passing branch names, a commit hash or a tag name is possible like so::
|
||||
|
||||
[-e] git://git.myproject.org/MyProject.git@master#egg=MyProject
|
||||
[-e] git://git.myproject.org/MyProject.git@v1.0#egg=MyProject
|
||||
[-e] git://git.myproject.org/MyProject.git@da39a3ee5e6b4b0d3255bfef95601890afd80709#egg=MyProject
|
||||
|
||||
Mercurial
|
||||
~~~~~~~~~
|
||||
|
||||
The supported schemes are: ``hg+http``, ``hg+https``,
|
||||
``hg+static-http`` and ``hg+ssh``.
|
||||
|
||||
Here are the supported forms::
|
||||
|
||||
[-e] hg+http://hg.myproject.org/MyProject#egg=MyProject
|
||||
[-e] hg+https://hg.myproject.org/MyProject#egg=MyProject
|
||||
[-e] hg+ssh://hg.myproject.org/MyProject#egg=MyProject
|
||||
|
||||
You can also specify a revision number, a revision hash, a tag name or a local
|
||||
branch name like so::
|
||||
|
||||
[-e] hg+http://hg.myproject.org/MyProject@da39a3ee5e6b#egg=MyProject
|
||||
[-e] hg+http://hg.myproject.org/MyProject@2019#egg=MyProject
|
||||
[-e] hg+http://hg.myproject.org/MyProject@v1.0#egg=MyProject
|
||||
[-e] hg+http://hg.myproject.org/MyProject@special_feature#egg=MyProject
|
||||
|
||||
Subversion
|
||||
~~~~~~~~~~
|
||||
|
||||
pip supports the URL schemes ``svn``, ``svn+svn``, ``svn+http``, ``svn+https``, ``svn+ssh``.
|
||||
|
||||
You can also give specific revisions to an SVN URL, like so::
|
||||
|
||||
[-e] svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject
|
||||
[-e] svn+http://svn.myproject.org/svn/MyProject/trunk@2019#egg=MyProject
|
||||
|
||||
which will check out revision 2019. ``@{20080101}`` would also check
|
||||
out the revision from 2008-01-01. You can only check out specific
|
||||
revisions using ``-e svn+...``.
|
||||
|
||||
Bazaar
|
||||
~~~~~~
|
||||
|
||||
pip supports Bazaar using the ``bzr+http``, ``bzr+https``, ``bzr+ssh``,
|
||||
``bzr+sftp``, ``bzr+ftp`` and ``bzr+lp`` schemes.
|
||||
|
||||
Here are the supported forms::
|
||||
|
||||
[-e] bzr+http://bzr.myproject.org/MyProject/trunk#egg=MyProject
|
||||
[-e] bzr+sftp://user@myproject.org/MyProject/trunk#egg=MyProject
|
||||
[-e] bzr+ssh://user@myproject.org/MyProject/trunk#egg=MyProject
|
||||
[-e] bzr+ftp://user@myproject.org/MyProject/trunk#egg=MyProject
|
||||
[-e] bzr+lp:MyProject#egg=MyProject
|
||||
|
||||
Tags or revisions can be installed like so::
|
||||
|
||||
[-e] bzr+https://bzr.myproject.org/MyProject/trunk@2019#egg=MyProject
|
||||
[-e] bzr+http://bzr.myproject.org/MyProject/trunk@v1.0#egg=MyProject
|
||||
|
||||
|
||||
Finding Packages
|
||||
++++++++++++++++
|
||||
|
||||
pip searches for packages on `PyPI`_ using the
|
||||
`http simple interface <http://pypi.python.org/simple>`_,
|
||||
which is documented `here <http://packages.python.org/setuptools/easy_install.html#package-index-api>`_
|
||||
and `there <http://www.python.org/dev/peps/pep-0301/>`_
|
||||
|
||||
pip offers a number of Package Index Options for modifying how packages are found.
|
||||
|
||||
See the :ref:`pip install Examples<pip install Examples>`.
|
||||
|
||||
|
||||
.. _`SSL Certificate Verification`:
|
||||
|
||||
SSL Certificate Verification
|
||||
++++++++++++++++++++++++++++
|
||||
|
||||
Starting with v1.3, pip provides SSL certificate verification over https, for the purpose
|
||||
of providing secure, certified downloads from PyPI.
|
||||
|
||||
|
||||
Hash Verification
|
||||
+++++++++++++++++
|
||||
|
||||
PyPI provides md5 hashes in the hash fragment of package download urls.
|
||||
|
||||
pip supports checking this, as well as any of the
|
||||
guaranteed hashlib algorithms (sha1, sha224, sha384, sha256, sha512, md5).
|
||||
|
||||
The hash fragment is case sensitive (i.e. sha1 not SHA1).
|
||||
|
||||
This check is only intended to provide basic download corruption protection.
|
||||
It is not intended to provide security against tampering. For that,
|
||||
see :ref:`SSL Certificate Verification`
|
||||
|
||||
|
||||
Download Cache
|
||||
++++++++++++++
|
||||
|
||||
pip offers a :ref:`--download-cache <install_--download-cache>` option for
|
||||
installs to prevent redundant downloads of archives from PyPI.
|
||||
|
||||
The point of this cache is *not* to circumvent the index crawling process, but
|
||||
to *just* prevent redundant downloads.
|
||||
|
||||
Items are stored in this cache based on the url the archive was found at, not
|
||||
simply the archive name.
|
||||
|
||||
If you want a fast/local install solution that circumvents crawling PyPI, see
|
||||
the :ref:`Fast & Local Installs`.
|
||||
|
||||
Like all options, :ref:`--download-cache <install_--download-cache>`, can also
|
||||
be set as an environment variable, or placed into the pip config file. See the
|
||||
:ref:`Configuration` section.
|
||||
|
||||
|
||||
.. _`editable-installs`:
|
||||
|
||||
"Editable" Installs
|
||||
+++++++++++++++++++
|
||||
|
||||
"Editable" installs are fundamentally `"setuptools develop mode"
|
||||
<http://packages.python.org/setuptools/setuptools.html#development-mode>`_
|
||||
installs.
|
||||
|
||||
You can install local projects or VCS projects in "editable" mode::
|
||||
|
||||
$ pip install -e path/to/SomeProject
|
||||
$ pip install -e git+http://repo/my_project.git#egg=SomeProject
|
||||
|
||||
For local projects, the "SomeProject.egg-info" directory is created relative to
|
||||
the project path. This is one advantage over just using ``setup.py develop``,
|
||||
which creates the "egg-info" directly relative the current working directory.
|
||||
|
||||
|
||||
|
||||
Controlling setup_requires
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
Setuptools offers the ``setup_requires`` `setup() keyword
|
||||
<http://pythonhosted.org/setuptools/setuptools.html#new-and-changed-setup-keywords>`_
|
||||
for specifying dependencies that need to be present in order for the `setup.py`
|
||||
script to run. Internally, Setuptools uses ``easy_install`` to fulfill these
|
||||
dependencies.
|
||||
|
||||
pip has no way to control how these dependencies are located. None of the
|
||||
Package Index Options have an effect.
|
||||
|
||||
The solution is to configure a "system" or "personal" `Distutils configuration
|
||||
file
|
||||
<http://docs.python.org/2/install/index.html#distutils-configuration-files>`_ to
|
||||
manage the fulfillment.
|
||||
|
||||
For example, to have the dependency located at an alternate index, add this:
|
||||
|
||||
::
|
||||
|
||||
[easy_install]
|
||||
index_url = https://my.index-mirror.com
|
||||
|
||||
To have the dependency located from a local directory and not crawl PyPI, add this:
|
||||
|
||||
::
|
||||
|
||||
[easy_install]
|
||||
allow_hosts = ''
|
||||
find_links = file:///path/to/local/archives
|
||||
|
||||
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: install
|
||||
|
||||
.. pip-index-options::
|
||||
|
||||
|
||||
.. _`pip install Examples`:
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1) Install `SomePackage` and it's dependencies from `PyPI`_ using :ref:`Requirement Specifiers`
|
||||
|
||||
::
|
||||
|
||||
$ pip install SomePackage # latest version
|
||||
$ pip install SomePackage==1.0.4 # specific version
|
||||
$ pip install 'SomePackage>=1.0.4' # minimum version
|
||||
|
||||
|
||||
2) Install a list of requirements specified in a file. See the :ref:`Requirements files <Requirements Files>`.
|
||||
|
||||
::
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
|
||||
3) Upgrade an already installed `SomePackage` to the latest from PyPI.
|
||||
|
||||
::
|
||||
|
||||
$ pip install --upgrade SomePackage
|
||||
|
||||
|
||||
4) Install a local project in "editable" mode. See the section on :ref:`Editable Installs <editable-installs>`.
|
||||
|
||||
::
|
||||
|
||||
$ pip install -e . # project in current directory
|
||||
$ pip install -e path/to/project # project in another directory
|
||||
|
||||
|
||||
5) Install a project from VCS in "editable" mode. See the sections on :ref:`VCS Support <VCS Support>` and :ref:`Editable Installs <editable-installs>`.
|
||||
|
||||
::
|
||||
|
||||
$ pip install -e git+https://git.repo/some_pkg.git#egg=SomePackage # from git
|
||||
$ pip install -e hg+https://hg.repo/some_pkg.git#egg=SomePackage # from mercurial
|
||||
$ pip install -e svn+svn://svn.repo/some_pkg/trunk/#egg=SomePackage # from svn
|
||||
$ pip install -e git+https://git.repo/some_pkg.git@feature#egg=SomePackage # from 'feature' branch
|
||||
$ pip install -e git+https://git.repo/some_repo.git@egg=subdir&subdirectory=subdir_path # install a python package from a repo subdirectory
|
||||
|
||||
6) Install a package with `setuptools extras`_.
|
||||
|
||||
::
|
||||
|
||||
$ pip install SomePackage[PDF]
|
||||
$ pip install SomePackage[PDF]==3.0
|
||||
$ pip install -e .[PDF]==3.0 # editable project in current directory
|
||||
|
||||
|
||||
7) Install a particular source archive file.
|
||||
|
||||
::
|
||||
|
||||
$ pip install ./downloads/SomePackage-1.0.4.tar.gz
|
||||
$ pip install http://my.package.repo/SomePackage-1.0.4.zip
|
||||
|
||||
|
||||
8) Install from alternative package repositories.
|
||||
|
||||
Install from a different index, and not `PyPI`_ ::
|
||||
|
||||
$ pip install --index-url http://my.package.repo/simple/ SomePackage
|
||||
|
||||
Search an additional index during install, in addition to `PyPI`_ ::
|
||||
|
||||
$ pip install --extra-index-url http://my.package.repo/simple SomePackage
|
||||
|
||||
Install from a local flat directory containing archives (and don't scan indexes)::
|
||||
|
||||
$ pip install --no-index --find-links=file:///local/dir/ SomePackage
|
||||
$ pip install --no-index --find-links=/local/dir/ SomePackage
|
||||
$ pip install --no-index --find-links=relative/dir/ SomePackage
|
||||
|
||||
|
||||
9) Find pre-release and development versions, in addition to stable versions. By default, pip only finds stable versions.
|
||||
|
||||
::
|
||||
|
||||
$ pip install --pre SomePackage
|
||||
|
||||
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/
|
||||
.. _setuptools extras: http://packages.python.org/setuptools/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
.. _`pip list`:
|
||||
|
||||
pip list
|
||||
---------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: list
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: list
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: list
|
||||
|
||||
.. pip-index-options::
|
||||
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1) List installed packages.
|
||||
|
||||
::
|
||||
|
||||
$ pip list
|
||||
Pygments (1.5)
|
||||
docutils (0.9.1)
|
||||
Sphinx (1.1.2)
|
||||
Jinja2 (2.6)
|
||||
|
||||
2) List outdated packages (excluding editables), and the latest version available
|
||||
|
||||
::
|
||||
|
||||
$ pip list --outdated
|
||||
docutils (Current: 0.9.1 Latest: 0.10)
|
||||
Sphinx (Current: 1.1.2 Latest: 1.1.3)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
.. _`pip search`:
|
||||
|
||||
pip search
|
||||
----------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: search
|
||||
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: search
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: search
|
||||
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1. Search for "peppercorn"
|
||||
|
||||
::
|
||||
|
||||
$ pip search peppercorn
|
||||
pepperedform - Helpers for using peppercorn with formprocess.
|
||||
peppercorn - A library for converting a token stream into [...]
|
||||
|
||||
.. _`pip wheel`:
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
.. _`pip show`:
|
||||
|
||||
pip show
|
||||
--------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: show
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: show
|
||||
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: show
|
||||
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1. Show information about a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip show sphinx
|
||||
---
|
||||
Name: Sphinx
|
||||
Version: 1.1.3
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Requires: Pygments, Jinja2, docutils
|
||||
@@ -0,0 +1,37 @@
|
||||
.. _`pip uninstall`:
|
||||
|
||||
pip uninstall
|
||||
-------------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: uninstall
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: uninstall
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: uninstall
|
||||
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1) Uninstall a package.
|
||||
|
||||
::
|
||||
|
||||
$ pip uninstall simplejson
|
||||
Uninstalling simplejson:
|
||||
/home/me/env/lib/python2.7/site-packages/simplejson
|
||||
/home/me/env/lib/python2.7/site-packages/simplejson-2.2.1-py2.7.egg-info
|
||||
Proceed (y/n)? y
|
||||
Successfully uninstalled simplejson
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
.. _`pip wheel`:
|
||||
|
||||
pip wheel
|
||||
---------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. pip-command-usage:: wheel
|
||||
|
||||
|
||||
Description
|
||||
***********
|
||||
|
||||
.. pip-command-description:: wheel
|
||||
|
||||
|
||||
Options
|
||||
*******
|
||||
|
||||
.. pip-command-options:: wheel
|
||||
|
||||
.. pip-index-options::
|
||||
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
1. Build wheels for a requirement (and all its dependencies), and then install
|
||||
|
||||
::
|
||||
|
||||
$ pip wheel --wheel-dir=/tmp/wheelhouse SomePackage
|
||||
$ pip install --no-index --find-links=/tmp/wheelhouse SomePackage
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
:orphan:
|
||||
|
||||
==========
|
||||
Usage
|
||||
==========
|
||||
|
||||
The "Usage" section is now covered in the :doc:`Reference Guide <reference/index>`
|
||||
|
||||
Vendored
+516
@@ -0,0 +1,516 @@
|
||||
==========
|
||||
User Guide
|
||||
==========
|
||||
|
||||
.. contents::
|
||||
|
||||
Installing Packages
|
||||
*******************
|
||||
|
||||
pip supports installing from `PyPI`_, version control, local projects, and
|
||||
directly from distribution files.
|
||||
|
||||
|
||||
The most common scenario is to install from `PyPI`_ using :ref:`Requirement
|
||||
Specifiers`
|
||||
|
||||
::
|
||||
|
||||
$ pip install SomePackage # latest version
|
||||
$ pip install SomePackage==1.0.4 # specific version
|
||||
$ pip install 'SomePackage>=1.0.4' # minimum version
|
||||
|
||||
|
||||
For more information and examples, see the :ref:`pip install` reference.
|
||||
|
||||
|
||||
.. _`Requirements Files`:
|
||||
|
||||
Requirements Files
|
||||
******************
|
||||
|
||||
"Requirements files" are files containing a list of items to be
|
||||
installed using :ref:`pip install` like so:
|
||||
|
||||
::
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
||||
Details on the format of the files are here: :ref:`Requirements File Format`.
|
||||
|
||||
Logically, a Requirements file is just a list of :ref:`pip install` arguments
|
||||
placed in a file.
|
||||
|
||||
In practice, there are 4 common uses of Requirements files:
|
||||
|
||||
1. Requirements files are used to hold the result from :ref:`pip freeze` for the
|
||||
purpose of achieving :ref:`repeatable installations <Repeatability>`. In
|
||||
this case, your requirement file contains a pinned version of everything that
|
||||
was installed when `pip freeze` was run.
|
||||
|
||||
::
|
||||
|
||||
pip freeze > requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
2. Requirements files are used to force pip to properly resolve dependencies.
|
||||
As it is now, pip `doesn't have true dependency resolution
|
||||
<https://github.com/pypa/pip/issues/988>`_, but instead simply uses the first
|
||||
specification it finds for a project. E.g if `pkg1` requires `pkg3>=1.0` and
|
||||
`pkg2` requires `pkg3>=1.0,<=2.0`, and if `pkg1` is resolved first, pip will
|
||||
only use `pkg3>=1.0`, and could easily end up installing a version of `pkg3`
|
||||
that conflicts with the needs of `pkg2`. To solve this problem, you can
|
||||
place `pkg3>=1.0,<=2.0` (i.e. the correct specification) into your
|
||||
requirements file directly along with the other top level requirements. Like
|
||||
so:
|
||||
|
||||
::
|
||||
|
||||
pkg1
|
||||
pkg2
|
||||
pkg3>=1.0,<=2.0
|
||||
|
||||
|
||||
3. Requirements files are used to force pip to install an alternate version of a
|
||||
sub-dependency. For example, suppose `ProjectA` in your requirements file
|
||||
requires `ProjectB`, but the latest version (v1.3) has a bug, you can force
|
||||
pip to accept earlier versions like so:
|
||||
|
||||
::
|
||||
|
||||
ProjectA
|
||||
ProjectB<1.3
|
||||
|
||||
4. Requirements files are used to override a dependency with a local patch that
|
||||
lives in version control. For example, suppose a dependency,
|
||||
`SomeDependency` from PyPI has a bug, and you can't wait for an upstream fix.
|
||||
You could clone/copy the src, make the fix, and place it in vcs with the tag
|
||||
`sometag`. You'd reference it in your requirements file with a line like so:
|
||||
|
||||
::
|
||||
|
||||
git+https://myvcs.com/some_dependency@sometag#egg=SomeDependency
|
||||
|
||||
If `SomeDependency` was previously a top-level requirement in your
|
||||
requirements file, then **replace** that line with the new line. If
|
||||
`SomeDependency` is a sub-dependency, then **add** the new line.
|
||||
|
||||
|
||||
It's important to be clear that pip determines package dependencies using
|
||||
`install_requires metadata
|
||||
<http://pythonhosted.org/setuptools/setuptools.html#declaring-dependencies>`_,
|
||||
not by discovering `requirements.txt` files embedded in projects.
|
||||
|
||||
See also:
|
||||
|
||||
* :ref:`Requirements File Format`
|
||||
* :ref:`pip freeze`
|
||||
* `"setup.py vs requirements.txt" (an article by Donald Stufft)
|
||||
<https://caremad.io/blog/setup-vs-requirement/>`_
|
||||
|
||||
|
||||
|
||||
.. _`Installing from Wheels`:
|
||||
|
||||
Installing from Wheels
|
||||
**********************
|
||||
|
||||
"Wheel" is a built, archive format that can greatly speed installation compared
|
||||
to building and installing from source archives. For more information, see the
|
||||
`Wheel docs <http://wheel.readthedocs.org>`_ ,
|
||||
`PEP427 <http://www.python.org/dev/peps/pep-0427>`_, and
|
||||
`PEP425 <http://www.python.org/dev/peps/pep-0425>`_
|
||||
|
||||
Pip prefers Wheels where they are available. To disable this, use the
|
||||
:ref:`--no-use-wheel <install_--no-use-wheel>` flag for :ref:`pip install`.
|
||||
|
||||
If no satisfactory wheels are found, pip will default to finding source archives.
|
||||
|
||||
|
||||
To install directly from a wheel archive:
|
||||
|
||||
::
|
||||
|
||||
pip install SomePackage-1.0-py2.py3-none-any.whl
|
||||
|
||||
|
||||
For the cases where wheels are not available, pip offers :ref:`pip wheel` as a
|
||||
convenience, to build wheels for all your requirements and dependencies.
|
||||
|
||||
:ref:`pip wheel` requires the `wheel package
|
||||
<https://pypi.python.org/pypi/wheel>`_ to be installed, which provides the
|
||||
"bdist_wheel" setuptools extension that it uses.
|
||||
|
||||
To build wheels for your requirements and all their dependencies to a local directory:
|
||||
|
||||
::
|
||||
|
||||
pip install wheel
|
||||
pip wheel --wheel-dir=/local/wheels -r requirements.txt
|
||||
|
||||
|
||||
And *then* to install those requirements just using your local directory of wheels (and not from PyPI):
|
||||
|
||||
::
|
||||
|
||||
pip install --no-index --find-links=/local/wheels -r requirements.txt
|
||||
|
||||
|
||||
Uninstalling Packages
|
||||
*********************
|
||||
|
||||
pip is able to uninstall most packages like so:
|
||||
|
||||
::
|
||||
|
||||
$ pip uninstall SomePackage
|
||||
|
||||
pip also performs an automatic uninstall of an old version of a package
|
||||
before upgrading to a newer version.
|
||||
|
||||
For more information and examples, see the :ref:`pip uninstall` reference.
|
||||
|
||||
|
||||
Listing Packages
|
||||
****************
|
||||
|
||||
To list installed packages:
|
||||
|
||||
::
|
||||
|
||||
$ pip list
|
||||
Pygments (1.5)
|
||||
docutils (0.9.1)
|
||||
Sphinx (1.1.2)
|
||||
Jinja2 (2.6)
|
||||
|
||||
To list outdated packages, and show the latest version available:
|
||||
|
||||
::
|
||||
|
||||
$ pip list --outdated
|
||||
docutils (Current: 0.9.1 Latest: 0.10)
|
||||
Sphinx (Current: 1.1.2 Latest: 1.1.3)
|
||||
|
||||
|
||||
To show details about an installed package:
|
||||
|
||||
::
|
||||
|
||||
$ pip show sphinx
|
||||
---
|
||||
Name: Sphinx
|
||||
Version: 1.1.3
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Requires: Pygments, Jinja2, docutils
|
||||
|
||||
|
||||
For more information and examples, see the :ref:`pip list` and :ref:`pip show`
|
||||
reference pages.
|
||||
|
||||
|
||||
Searching for Packages
|
||||
**********************
|
||||
|
||||
pip can search `PyPI`_ for packages using the ``pip search``
|
||||
command::
|
||||
|
||||
$ pip search "query"
|
||||
|
||||
The query will be used to search the names and summaries of all
|
||||
packages.
|
||||
|
||||
For more information and examples, see the :ref:`pip search` reference.
|
||||
|
||||
.. _`Configuration`:
|
||||
|
||||
Configuration
|
||||
*************
|
||||
|
||||
.. _config-file:
|
||||
|
||||
Config file
|
||||
------------
|
||||
|
||||
pip allows you to set all command line option defaults in a standard ini
|
||||
style config file.
|
||||
|
||||
The names and locations of the configuration files vary slightly across
|
||||
platforms.
|
||||
|
||||
* On Unix and Mac OS X the configuration file is: :file:`$HOME/.pip/pip.conf`
|
||||
* On Windows, the configuration file is: :file:`%HOME%\\pip\\pip.ini`
|
||||
|
||||
You can set a custom path location for the config file using the environment variable ``PIP_CONFIG_FILE``.
|
||||
|
||||
In a virtual environment, an additional config file will be read from the base
|
||||
directory of the virtualenv (``sys.prefix`` as reported by Python). The base
|
||||
name of the file is the same as the user configuration file (:file:`pip.conf`
|
||||
on Unix and OSX, :file:`pip.ini` on Windows). Values in the virtualenv-specific
|
||||
configuration file take precedence over those in the user's configuration file
|
||||
(whether from the user home or specified via ``PIP_CONFIG_FILE``).
|
||||
|
||||
The names of the settings are derived from the long command line option, e.g.
|
||||
if you want to use a different package index (``--index-url``) and set the
|
||||
HTTP timeout (``--default-timeout``) to 60 seconds your config file would
|
||||
look like this:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[global]
|
||||
timeout = 60
|
||||
index-url = http://download.zope.org/ppix
|
||||
|
||||
Each subcommand can be configured optionally in its own section so that every
|
||||
global setting with the same name will be overridden; e.g. decreasing the
|
||||
``timeout`` to ``10`` seconds when running the `freeze`
|
||||
(`Freezing Requirements <./#freezing-requirements>`_) command and using
|
||||
``60`` seconds for all other commands is possible with:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[global]
|
||||
timeout = 60
|
||||
|
||||
[freeze]
|
||||
timeout = 10
|
||||
|
||||
|
||||
Boolean options like ``--ignore-installed`` or ``--no-dependencies`` can be
|
||||
set like this:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[install]
|
||||
ignore-installed = true
|
||||
no-dependencies = yes
|
||||
|
||||
Appending options like ``--find-links`` can be written on multiple lines:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[global]
|
||||
find-links =
|
||||
http://download.example.com
|
||||
|
||||
[install]
|
||||
find-links =
|
||||
http://mirror1.example.com
|
||||
http://mirror2.example.com
|
||||
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
|
||||
pip's command line options can be set with environment variables using the
|
||||
format ``PIP_<UPPER_LONG_NAME>`` . Dashes (``-``) have to be replaced with
|
||||
underscores (``_``).
|
||||
|
||||
For example, to set the default timeout::
|
||||
|
||||
export PIP_DEFAULT_TIMEOUT=60
|
||||
|
||||
This is the same as passing the option to pip directly::
|
||||
|
||||
pip --default-timeout=60 [...]
|
||||
|
||||
To set options that can be set multiple times on the command line, just add
|
||||
spaces in between values. For example::
|
||||
|
||||
export PIP_FIND_LINKS="http://mirror1.example.com http://mirror2.example.com"
|
||||
|
||||
is the same as calling::
|
||||
|
||||
pip install --find-links=http://mirror1.example.com --find-links=http://mirror2.example.com
|
||||
|
||||
|
||||
Config Precedence
|
||||
-----------------
|
||||
|
||||
Command line options have precedence over environment variables, which have precedence over the config file.
|
||||
|
||||
Within the config file, command specific sections have precedence over the global section.
|
||||
|
||||
Examples:
|
||||
|
||||
- ``--host=foo`` overrides ``PIP_HOST=foo``
|
||||
- ``PIP_HOST=foo`` overrides a config file with ``[global] host = foo``
|
||||
- A command specific section in the config file ``[<command>] host = bar``
|
||||
overrides the option with same name in the ``[global]`` config file section
|
||||
|
||||
|
||||
Command Completion
|
||||
------------------
|
||||
|
||||
pip comes with support for command line completion in bash and zsh.
|
||||
|
||||
To setup for bash::
|
||||
|
||||
$ pip completion --bash >> ~/.profile
|
||||
|
||||
To setup for zsh::
|
||||
|
||||
$ pip completion --zsh >> ~/.zprofile
|
||||
|
||||
Alternatively, you can use the result of the ``completion`` command
|
||||
directly with the eval function of you shell, e.g. by adding the following to your startup file::
|
||||
|
||||
eval "`pip completion --bash`"
|
||||
|
||||
|
||||
|
||||
.. _`Fast & Local Installs`:
|
||||
|
||||
Fast & Local Installs
|
||||
*********************
|
||||
|
||||
Often, you will want a fast install from local archives, without probing PyPI.
|
||||
|
||||
First, download the archives that fulfill your requirements::
|
||||
|
||||
$ pip install --download <DIR> -r requirements.txt
|
||||
|
||||
Then, install using :ref:`--find-links <--find-links>` and :ref:`--no-index <--no-index>`::
|
||||
|
||||
$ pip install --no-index --find-links=[file://]<DIR> -r requirements.txt
|
||||
|
||||
|
||||
Non-recursive upgrades
|
||||
************************
|
||||
|
||||
``pip install --upgrade`` is currently written to perform a recursive upgrade.
|
||||
|
||||
E.g. supposing:
|
||||
|
||||
* `SomePackage-1.0` requires `AnotherPackage>=1.0`
|
||||
* `SomePackage-2.0` requires `AnotherPackage>=1.0` and `OneMorePoject==1.0`
|
||||
* `SomePackage-1.0` and `AnotherPackage-1.0` are currently installed
|
||||
* `SomePackage-2.0` and `AnotherPackage-2.0` are the latest versions available on PyPI.
|
||||
|
||||
Running ``pip install --upgrade SomePackage`` would upgrade `SomePackage` *and* `AnotherPackage`
|
||||
despite `AnotherPackage` already being satisifed.
|
||||
|
||||
If you would like to perform a non-recursive upgrade perform these 2 steps::
|
||||
|
||||
pip install --upgrade --no-deps SomePackage
|
||||
pip install SomePackage
|
||||
|
||||
The first line will upgrade `SomePackage`, but not dependencies like `AnotherPackage`. The 2nd line will fill in new dependencies like `OneMorePackage`.
|
||||
|
||||
|
||||
User Installs
|
||||
*************
|
||||
|
||||
With Python 2.6 came the `"user scheme" for installation
|
||||
<http://docs.python.org/install/index.html#alternate-installation-the-user-scheme>`_,
|
||||
which means that all Python distributions support an alternative install
|
||||
location that is specific to a user. The default location for each OS is
|
||||
explained in the python documentation for the `site.USER_BASE
|
||||
<http://docs.python.org/library/site.html#site.USER_BASE>`_ variable. This mode
|
||||
of installation can be turned on by specifying the :ref:`--user
|
||||
<install_--user>` option to ``pip install``.
|
||||
|
||||
Moreover, the "user scheme" can be customized by setting the
|
||||
``PYTHONUSERBASE`` environment variable, which updates the value of ``site.USER_BASE``.
|
||||
|
||||
To install "SomePackage" into an environment with site.USER_BASE customized to '/myappenv', do the following::
|
||||
|
||||
export PYTHONUSERBASE=/myappenv
|
||||
pip install --user SomePackage
|
||||
|
||||
|
||||
``pip install --user`` follows four rules:
|
||||
|
||||
#. When globally installed packages are on the python path, and they *conflict*
|
||||
with the installation requirements, they are ignored, and *not*
|
||||
uninstalled.
|
||||
#. When globally installed packages are on the python path, and they *satisfy*
|
||||
the installation requirements, pip does nothing, and reports that
|
||||
requirement is satisfied (similar to how global packages can satisfy
|
||||
requirements when installing packages in a ``--system-site-packages``
|
||||
virtualenv).
|
||||
#. pip will not perform a ``--user`` install in a ``--no-site-packages``
|
||||
virtualenv (i.e. the default kind of virtualenv), due to the user site not
|
||||
being on the python path. The installation would be pointless.
|
||||
#. In a ``--system-site-packages`` virtualenv, pip will not install a package
|
||||
that conflicts with a package in the virtualenv site-packages. The --user
|
||||
installation would lack sys.path precedence and be pointless.
|
||||
|
||||
|
||||
To make the rules clearer, here are some examples:
|
||||
|
||||
|
||||
From within a ``--no-site-packages`` virtualenv (i.e. the default kind)::
|
||||
|
||||
$ pip install --user SomePackage
|
||||
Can not perform a '--user' install. User site-packages are not visible in this virtualenv.
|
||||
|
||||
|
||||
From within a ``--system-site-packages`` virtualenv where ``SomePackage==0.3`` is already installed in the virtualenv::
|
||||
|
||||
$ pip install --user SomePackage==0.4
|
||||
Will not install to the user site because it will lack sys.path precedence
|
||||
|
||||
|
||||
From within a real python, where ``SomePackage`` is *not* installed globally::
|
||||
|
||||
$ pip install --user SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
|
||||
From within a real python, where ``SomePackage`` *is* installed globally, but is *not* the latest version::
|
||||
|
||||
$ pip install --user SomePackage
|
||||
[...]
|
||||
Requirement already satisfied (use --upgrade to upgrade)
|
||||
|
||||
$ pip install --user --upgrade SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
|
||||
From within a real python, where ``SomePackage`` *is* installed globally, and is the latest version::
|
||||
|
||||
$ pip install --user SomePackage
|
||||
[...]
|
||||
Requirement already satisfied (use --upgrade to upgrade)
|
||||
|
||||
$ pip install --user --upgrade SomePackage
|
||||
[...]
|
||||
Requirement already up-to-date: SomePackage
|
||||
|
||||
# force the install
|
||||
$ pip install --user --ignore-installed SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
|
||||
.. _`Repeatability`:
|
||||
|
||||
Ensuring Repeatability
|
||||
**********************
|
||||
|
||||
Three things are required to fully guarantee a repeatable installation using requirements files.
|
||||
|
||||
1. The requirements file was generated by ``pip freeze`` or you're sure it only
|
||||
contains requirements that specify a specific version.
|
||||
|
||||
2. The installation is performed using :ref:`--no-deps <install_--no-deps>`.
|
||||
This guarantees that only what is explicitly listed in the requirements file is
|
||||
installed.
|
||||
|
||||
3. The installation is performed against an index or find-links location that is
|
||||
guaranteed to *not* allow archives to be changed and updated without a
|
||||
version increase. Unfortunately, this is *not* true on PyPI. It is possible
|
||||
for the same pypi distribution to have a different hash over time. Project
|
||||
authors are allowed to delete a distribution, and then upload a new one with
|
||||
the same name and version, but a different hash. See `Issue #1175
|
||||
<https://github.com/pypa/pip/issues/1175>`_ for plans to add hash
|
||||
confirmation to pip, or a new "lock file" notion, but for now, know that the `peep
|
||||
project <https://pypi.python.org/pypi/peep>`_ offers this feature on top of pip
|
||||
using requirements file comments.
|
||||
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: pip
|
||||
Version: 1.5.4
|
||||
Summary: A tool for installing and managing Python packages.
|
||||
Home-page: http://www.pip-installer.org
|
||||
Author: The pip developers
|
||||
Author-email: python-virtualenv@groups.google.com
|
||||
License: MIT
|
||||
Description:
|
||||
Project Info
|
||||
============
|
||||
|
||||
* Project Page: https://github.com/pypa/pip
|
||||
* Install howto: http://www.pip-installer.org/en/latest/installing.html
|
||||
* Changelog: http://www.pip-installer.org/en/latest/news.html
|
||||
* Bug Tracking: https://github.com/pypa/pip/issues
|
||||
* Mailing list: http://groups.google.com/group/python-virtualenv
|
||||
* Docs: http://www.pip-installer.org/
|
||||
* User IRC: #pip on Freenode.
|
||||
* Dev IRC: #pypa on Freenode.
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
First, :doc:`Install pip <installing>`.
|
||||
|
||||
Install a package from `PyPI`_:
|
||||
|
||||
::
|
||||
|
||||
$ pip install SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
Show what files were installed:
|
||||
|
||||
::
|
||||
|
||||
$ pip show --files SomePackage
|
||||
Name: SomePackage
|
||||
Version: 1.0
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Files:
|
||||
../somepackage/__init__.py
|
||||
[...]
|
||||
|
||||
List what packages are outdated:
|
||||
|
||||
::
|
||||
|
||||
$ pip list --outdated
|
||||
SomePackage (Current: 1.0 Latest: 2.0)
|
||||
|
||||
Upgrade a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip install --upgrade SomePackage
|
||||
[...]
|
||||
Found existing installation: SomePackage 1.0
|
||||
Uninstalling SomePackage:
|
||||
Successfully uninstalled SomePackage
|
||||
Running setup.py install for SomePackage
|
||||
Successfully installed SomePackage
|
||||
|
||||
Uninstall a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip uninstall SomePackage
|
||||
Uninstalling SomePackage:
|
||||
/my/env/lib/pythonx.x/site-packages/somepackage
|
||||
Proceed (y/n)? y
|
||||
Successfully uninstalled SomePackage
|
||||
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/
|
||||
|
||||
Keywords: easy_install distutils setuptools egg virtualenv
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Topic :: Software Development :: Build Tools
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.1
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
AUTHORS.txt
|
||||
CHANGES.txt
|
||||
LICENSE.txt
|
||||
MANIFEST.in
|
||||
PROJECT.txt
|
||||
README.rst
|
||||
setup.cfg
|
||||
setup.py
|
||||
docs/configuration.rst
|
||||
docs/cookbook.rst
|
||||
docs/development.rst
|
||||
docs/distribute_setuptools.rst
|
||||
docs/index.rst
|
||||
docs/installing.rst
|
||||
docs/logic.rst
|
||||
docs/news.rst
|
||||
docs/quickstart.rst
|
||||
docs/usage.rst
|
||||
docs/user_guide.rst
|
||||
docs/reference/index.rst
|
||||
docs/reference/pip.rst
|
||||
docs/reference/pip_freeze.rst
|
||||
docs/reference/pip_install.rst
|
||||
docs/reference/pip_list.rst
|
||||
docs/reference/pip_search.rst
|
||||
docs/reference/pip_show.rst
|
||||
docs/reference/pip_uninstall.rst
|
||||
docs/reference/pip_wheel.rst
|
||||
pip/__init__.py
|
||||
pip/__main__.py
|
||||
pip/basecommand.py
|
||||
pip/baseparser.py
|
||||
pip/cmdoptions.py
|
||||
pip/download.py
|
||||
pip/exceptions.py
|
||||
pip/index.py
|
||||
pip/locations.py
|
||||
pip/log.py
|
||||
pip/pep425tags.py
|
||||
pip/req.py
|
||||
pip/runner.py
|
||||
pip/status_codes.py
|
||||
pip/util.py
|
||||
pip/wheel.py
|
||||
pip.egg-info/PKG-INFO
|
||||
pip.egg-info/SOURCES.txt
|
||||
pip.egg-info/dependency_links.txt
|
||||
pip.egg-info/entry_points.txt
|
||||
pip.egg-info/not-zip-safe
|
||||
pip.egg-info/requires.txt
|
||||
pip.egg-info/top_level.txt
|
||||
pip/_vendor/__init__.py
|
||||
pip/_vendor/pkg_resources.py
|
||||
pip/_vendor/re-vendor.py
|
||||
pip/_vendor/six.py
|
||||
pip/_vendor/_markerlib/__init__.py
|
||||
pip/_vendor/_markerlib/markers.py
|
||||
pip/_vendor/colorama/__init__.py
|
||||
pip/_vendor/colorama/ansi.py
|
||||
pip/_vendor/colorama/ansitowin32.py
|
||||
pip/_vendor/colorama/initialise.py
|
||||
pip/_vendor/colorama/win32.py
|
||||
pip/_vendor/colorama/winterm.py
|
||||
pip/_vendor/distlib/__init__.py
|
||||
pip/_vendor/distlib/compat.py
|
||||
pip/_vendor/distlib/database.py
|
||||
pip/_vendor/distlib/index.py
|
||||
pip/_vendor/distlib/locators.py
|
||||
pip/_vendor/distlib/manifest.py
|
||||
pip/_vendor/distlib/markers.py
|
||||
pip/_vendor/distlib/metadata.py
|
||||
pip/_vendor/distlib/resources.py
|
||||
pip/_vendor/distlib/scripts.py
|
||||
pip/_vendor/distlib/t32.exe
|
||||
pip/_vendor/distlib/t64.exe
|
||||
pip/_vendor/distlib/util.py
|
||||
pip/_vendor/distlib/version.py
|
||||
pip/_vendor/distlib/w32.exe
|
||||
pip/_vendor/distlib/w64.exe
|
||||
pip/_vendor/distlib/wheel.py
|
||||
pip/_vendor/distlib/_backport/__init__.py
|
||||
pip/_vendor/distlib/_backport/misc.py
|
||||
pip/_vendor/distlib/_backport/shutil.py
|
||||
pip/_vendor/distlib/_backport/sysconfig.cfg
|
||||
pip/_vendor/distlib/_backport/sysconfig.py
|
||||
pip/_vendor/distlib/_backport/tarfile.py
|
||||
pip/_vendor/html5lib/__init__.py
|
||||
pip/_vendor/html5lib/constants.py
|
||||
pip/_vendor/html5lib/html5parser.py
|
||||
pip/_vendor/html5lib/ihatexml.py
|
||||
pip/_vendor/html5lib/inputstream.py
|
||||
pip/_vendor/html5lib/sanitizer.py
|
||||
pip/_vendor/html5lib/tokenizer.py
|
||||
pip/_vendor/html5lib/utils.py
|
||||
pip/_vendor/html5lib/filters/__init__.py
|
||||
pip/_vendor/html5lib/filters/_base.py
|
||||
pip/_vendor/html5lib/filters/alphabeticalattributes.py
|
||||
pip/_vendor/html5lib/filters/inject_meta_charset.py
|
||||
pip/_vendor/html5lib/filters/lint.py
|
||||
pip/_vendor/html5lib/filters/optionaltags.py
|
||||
pip/_vendor/html5lib/filters/sanitizer.py
|
||||
pip/_vendor/html5lib/filters/whitespace.py
|
||||
pip/_vendor/html5lib/serializer/__init__.py
|
||||
pip/_vendor/html5lib/serializer/htmlserializer.py
|
||||
pip/_vendor/html5lib/treebuilders/__init__.py
|
||||
pip/_vendor/html5lib/treebuilders/_base.py
|
||||
pip/_vendor/html5lib/treebuilders/dom.py
|
||||
pip/_vendor/html5lib/treebuilders/etree.py
|
||||
pip/_vendor/html5lib/treebuilders/etree_lxml.py
|
||||
pip/_vendor/html5lib/treewalkers/__init__.py
|
||||
pip/_vendor/html5lib/treewalkers/_base.py
|
||||
pip/_vendor/html5lib/treewalkers/dom.py
|
||||
pip/_vendor/html5lib/treewalkers/etree.py
|
||||
pip/_vendor/html5lib/treewalkers/genshistream.py
|
||||
pip/_vendor/html5lib/treewalkers/lxmletree.py
|
||||
pip/_vendor/html5lib/treewalkers/pulldom.py
|
||||
pip/_vendor/html5lib/trie/__init__.py
|
||||
pip/_vendor/html5lib/trie/_base.py
|
||||
pip/_vendor/html5lib/trie/datrie.py
|
||||
pip/_vendor/html5lib/trie/py.py
|
||||
pip/_vendor/requests/__init__.py
|
||||
pip/_vendor/requests/adapters.py
|
||||
pip/_vendor/requests/api.py
|
||||
pip/_vendor/requests/auth.py
|
||||
pip/_vendor/requests/cacert.pem
|
||||
pip/_vendor/requests/certs.py
|
||||
pip/_vendor/requests/compat.py
|
||||
pip/_vendor/requests/cookies.py
|
||||
pip/_vendor/requests/exceptions.py
|
||||
pip/_vendor/requests/hooks.py
|
||||
pip/_vendor/requests/models.py
|
||||
pip/_vendor/requests/sessions.py
|
||||
pip/_vendor/requests/status_codes.py
|
||||
pip/_vendor/requests/structures.py
|
||||
pip/_vendor/requests/utils.py
|
||||
pip/_vendor/requests/packages/__init__.py
|
||||
pip/_vendor/requests/packages/charade/__init__.py
|
||||
pip/_vendor/requests/packages/charade/__main__.py
|
||||
pip/_vendor/requests/packages/charade/big5freq.py
|
||||
pip/_vendor/requests/packages/charade/big5prober.py
|
||||
pip/_vendor/requests/packages/charade/chardistribution.py
|
||||
pip/_vendor/requests/packages/charade/charsetgroupprober.py
|
||||
pip/_vendor/requests/packages/charade/charsetprober.py
|
||||
pip/_vendor/requests/packages/charade/codingstatemachine.py
|
||||
pip/_vendor/requests/packages/charade/compat.py
|
||||
pip/_vendor/requests/packages/charade/constants.py
|
||||
pip/_vendor/requests/packages/charade/cp949prober.py
|
||||
pip/_vendor/requests/packages/charade/escprober.py
|
||||
pip/_vendor/requests/packages/charade/escsm.py
|
||||
pip/_vendor/requests/packages/charade/eucjpprober.py
|
||||
pip/_vendor/requests/packages/charade/euckrfreq.py
|
||||
pip/_vendor/requests/packages/charade/euckrprober.py
|
||||
pip/_vendor/requests/packages/charade/euctwfreq.py
|
||||
pip/_vendor/requests/packages/charade/euctwprober.py
|
||||
pip/_vendor/requests/packages/charade/gb2312freq.py
|
||||
pip/_vendor/requests/packages/charade/gb2312prober.py
|
||||
pip/_vendor/requests/packages/charade/hebrewprober.py
|
||||
pip/_vendor/requests/packages/charade/jisfreq.py
|
||||
pip/_vendor/requests/packages/charade/jpcntx.py
|
||||
pip/_vendor/requests/packages/charade/langbulgarianmodel.py
|
||||
pip/_vendor/requests/packages/charade/langcyrillicmodel.py
|
||||
pip/_vendor/requests/packages/charade/langgreekmodel.py
|
||||
pip/_vendor/requests/packages/charade/langhebrewmodel.py
|
||||
pip/_vendor/requests/packages/charade/langhungarianmodel.py
|
||||
pip/_vendor/requests/packages/charade/langthaimodel.py
|
||||
pip/_vendor/requests/packages/charade/latin1prober.py
|
||||
pip/_vendor/requests/packages/charade/mbcharsetprober.py
|
||||
pip/_vendor/requests/packages/charade/mbcsgroupprober.py
|
||||
pip/_vendor/requests/packages/charade/mbcssm.py
|
||||
pip/_vendor/requests/packages/charade/sbcharsetprober.py
|
||||
pip/_vendor/requests/packages/charade/sbcsgroupprober.py
|
||||
pip/_vendor/requests/packages/charade/sjisprober.py
|
||||
pip/_vendor/requests/packages/charade/universaldetector.py
|
||||
pip/_vendor/requests/packages/charade/utf8prober.py
|
||||
pip/_vendor/requests/packages/chardet/__init__.py
|
||||
pip/_vendor/requests/packages/chardet/big5freq.py
|
||||
pip/_vendor/requests/packages/chardet/big5prober.py
|
||||
pip/_vendor/requests/packages/chardet/chardetect.py
|
||||
pip/_vendor/requests/packages/chardet/chardistribution.py
|
||||
pip/_vendor/requests/packages/chardet/charsetgroupprober.py
|
||||
pip/_vendor/requests/packages/chardet/charsetprober.py
|
||||
pip/_vendor/requests/packages/chardet/codingstatemachine.py
|
||||
pip/_vendor/requests/packages/chardet/compat.py
|
||||
pip/_vendor/requests/packages/chardet/constants.py
|
||||
pip/_vendor/requests/packages/chardet/cp949prober.py
|
||||
pip/_vendor/requests/packages/chardet/escprober.py
|
||||
pip/_vendor/requests/packages/chardet/escsm.py
|
||||
pip/_vendor/requests/packages/chardet/eucjpprober.py
|
||||
pip/_vendor/requests/packages/chardet/euckrfreq.py
|
||||
pip/_vendor/requests/packages/chardet/euckrprober.py
|
||||
pip/_vendor/requests/packages/chardet/euctwfreq.py
|
||||
pip/_vendor/requests/packages/chardet/euctwprober.py
|
||||
pip/_vendor/requests/packages/chardet/gb2312freq.py
|
||||
pip/_vendor/requests/packages/chardet/gb2312prober.py
|
||||
pip/_vendor/requests/packages/chardet/hebrewprober.py
|
||||
pip/_vendor/requests/packages/chardet/jisfreq.py
|
||||
pip/_vendor/requests/packages/chardet/jpcntx.py
|
||||
pip/_vendor/requests/packages/chardet/langbulgarianmodel.py
|
||||
pip/_vendor/requests/packages/chardet/langcyrillicmodel.py
|
||||
pip/_vendor/requests/packages/chardet/langgreekmodel.py
|
||||
pip/_vendor/requests/packages/chardet/langhebrewmodel.py
|
||||
pip/_vendor/requests/packages/chardet/langhungarianmodel.py
|
||||
pip/_vendor/requests/packages/chardet/langthaimodel.py
|
||||
pip/_vendor/requests/packages/chardet/latin1prober.py
|
||||
pip/_vendor/requests/packages/chardet/mbcharsetprober.py
|
||||
pip/_vendor/requests/packages/chardet/mbcsgroupprober.py
|
||||
pip/_vendor/requests/packages/chardet/mbcssm.py
|
||||
pip/_vendor/requests/packages/chardet/sbcharsetprober.py
|
||||
pip/_vendor/requests/packages/chardet/sbcsgroupprober.py
|
||||
pip/_vendor/requests/packages/chardet/sjisprober.py
|
||||
pip/_vendor/requests/packages/chardet/universaldetector.py
|
||||
pip/_vendor/requests/packages/chardet/utf8prober.py
|
||||
pip/_vendor/requests/packages/urllib3/__init__.py
|
||||
pip/_vendor/requests/packages/urllib3/_collections.py
|
||||
pip/_vendor/requests/packages/urllib3/connection.py
|
||||
pip/_vendor/requests/packages/urllib3/connectionpool.py
|
||||
pip/_vendor/requests/packages/urllib3/exceptions.py
|
||||
pip/_vendor/requests/packages/urllib3/fields.py
|
||||
pip/_vendor/requests/packages/urllib3/filepost.py
|
||||
pip/_vendor/requests/packages/urllib3/poolmanager.py
|
||||
pip/_vendor/requests/packages/urllib3/request.py
|
||||
pip/_vendor/requests/packages/urllib3/response.py
|
||||
pip/_vendor/requests/packages/urllib3/util.py
|
||||
pip/_vendor/requests/packages/urllib3/contrib/__init__.py
|
||||
pip/_vendor/requests/packages/urllib3/contrib/ntlmpool.py
|
||||
pip/_vendor/requests/packages/urllib3/contrib/pyopenssl.py
|
||||
pip/_vendor/requests/packages/urllib3/packages/__init__.py
|
||||
pip/_vendor/requests/packages/urllib3/packages/ordered_dict.py
|
||||
pip/_vendor/requests/packages/urllib3/packages/six.py
|
||||
pip/_vendor/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py
|
||||
pip/_vendor/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py
|
||||
pip/backwardcompat/__init__.py
|
||||
pip/commands/__init__.py
|
||||
pip/commands/bundle.py
|
||||
pip/commands/completion.py
|
||||
pip/commands/freeze.py
|
||||
pip/commands/help.py
|
||||
pip/commands/install.py
|
||||
pip/commands/list.py
|
||||
pip/commands/search.py
|
||||
pip/commands/show.py
|
||||
pip/commands/uninstall.py
|
||||
pip/commands/unzip.py
|
||||
pip/commands/wheel.py
|
||||
pip/commands/zip.py
|
||||
pip/vcs/__init__.py
|
||||
pip/vcs/bazaar.py
|
||||
pip/vcs/git.py
|
||||
pip/vcs/mercurial.py
|
||||
pip/vcs/subversion.py
|
||||
@@ -0,0 +1,5 @@
|
||||
[console_scripts]
|
||||
pip = pip:main
|
||||
pip2.7 = pip:main
|
||||
pip2 = pip:main
|
||||
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
[testing]
|
||||
pytest
|
||||
virtualenv>=1.10
|
||||
scripttest>=1.3
|
||||
mock
|
||||
+1
@@ -0,0 +1 @@
|
||||
pip
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import optparse
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
from pip.exceptions import InstallationError, CommandError, PipError
|
||||
from pip.log import logger
|
||||
from pip.util import get_installed_distributions, get_prog
|
||||
from pip.vcs import git, mercurial, subversion, bazaar # noqa
|
||||
from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
||||
from pip.commands import commands, get_summaries, get_similar_commands
|
||||
|
||||
# This fixes a peculiarity when importing via __import__ - as we are
|
||||
# initialising the pip module, "from pip import cmdoptions" is recursive
|
||||
# and appears not to work properly in that situation.
|
||||
import pip.cmdoptions
|
||||
cmdoptions = pip.cmdoptions
|
||||
|
||||
# The version as used in the setup.py and the docs conf.py
|
||||
__version__ = "1.5.4"
|
||||
|
||||
|
||||
def autocomplete():
|
||||
"""Command and option completion for the main option parser (and options)
|
||||
and its subcommands (and options).
|
||||
|
||||
Enable by sourcing one of the completion shell scripts (bash or zsh).
|
||||
"""
|
||||
# Don't complete if user hasn't sourced bash_completion file.
|
||||
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||||
return
|
||||
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
try:
|
||||
current = cwords[cword - 1]
|
||||
except IndexError:
|
||||
current = ''
|
||||
|
||||
subcommands = [cmd for cmd, summary in get_summaries()]
|
||||
options = []
|
||||
# subcommand
|
||||
try:
|
||||
subcommand_name = [w for w in cwords if w in subcommands][0]
|
||||
except IndexError:
|
||||
subcommand_name = None
|
||||
|
||||
parser = create_main_parser()
|
||||
# subcommand options
|
||||
if subcommand_name:
|
||||
# special case: 'help' subcommand has no options
|
||||
if subcommand_name == 'help':
|
||||
sys.exit(1)
|
||||
# special case: list locally installed dists for uninstall command
|
||||
if subcommand_name == 'uninstall' and not current.startswith('-'):
|
||||
installed = []
|
||||
lc = current.lower()
|
||||
for dist in get_installed_distributions(local_only=True):
|
||||
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
|
||||
installed.append(dist.key)
|
||||
# if there are no dists installed, fall back to option completion
|
||||
if installed:
|
||||
for dist in installed:
|
||||
print(dist)
|
||||
sys.exit(1)
|
||||
|
||||
subcommand = commands[subcommand_name]()
|
||||
options += [(opt.get_opt_string(), opt.nargs)
|
||||
for opt in subcommand.parser.option_list_all
|
||||
if opt.help != optparse.SUPPRESS_HELP]
|
||||
|
||||
# filter out previously specified options from available options
|
||||
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||||
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||||
# filter options by current input
|
||||
options = [(k, v) for k, v in options if k.startswith(current)]
|
||||
for option in options:
|
||||
opt_label = option[0]
|
||||
# append '=' to options which require args
|
||||
if option[1]:
|
||||
opt_label += '='
|
||||
print(opt_label)
|
||||
else:
|
||||
# show main parser options only when necessary
|
||||
if current.startswith('-') or current.startswith('--'):
|
||||
opts = [i.option_list for i in parser.option_groups]
|
||||
opts.append(parser.option_list)
|
||||
opts = (o for it in opts for o in it)
|
||||
|
||||
subcommands += [i.get_opt_string() for i in opts
|
||||
if i.help != optparse.SUPPRESS_HELP]
|
||||
|
||||
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_main_parser():
|
||||
parser_kw = {
|
||||
'usage': '\n%prog <command> [options]',
|
||||
'add_help_option': False,
|
||||
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||
'name': 'global',
|
||||
'prog': get_prog(),
|
||||
}
|
||||
|
||||
parser = ConfigOptionParser(**parser_kw)
|
||||
parser.disable_interspersed_args()
|
||||
|
||||
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
parser.version = 'pip %s from %s (python %s)' % (
|
||||
__version__, pip_pkg_dir, sys.version[:3])
|
||||
|
||||
# add the general options
|
||||
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||
parser.add_option_group(gen_opts)
|
||||
|
||||
parser.main = True # so the help formatter knows
|
||||
|
||||
# create command listing for description
|
||||
command_summaries = get_summaries()
|
||||
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
|
||||
parser.description = '\n'.join(description)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def parseopts(args):
|
||||
parser = create_main_parser()
|
||||
|
||||
# Note: parser calls disable_interspersed_args(), so the result of this call
|
||||
# is to split the initial args into the general options before the
|
||||
# subcommand and everything else.
|
||||
# For example:
|
||||
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
||||
# general_options: ['--timeout==5']
|
||||
# args_else: ['install', '--user', 'INITools']
|
||||
general_options, args_else = parser.parse_args(args)
|
||||
|
||||
# --version
|
||||
if general_options.version:
|
||||
sys.stdout.write(parser.version)
|
||||
sys.stdout.write(os.linesep)
|
||||
sys.exit()
|
||||
|
||||
# pip || pip help -> print_help()
|
||||
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# the subcommand name
|
||||
cmd_name = args_else[0].lower()
|
||||
|
||||
#all the args without the subcommand
|
||||
cmd_args = args[:]
|
||||
cmd_args.remove(args_else[0].lower())
|
||||
|
||||
if cmd_name not in commands:
|
||||
guess = get_similar_commands(cmd_name)
|
||||
|
||||
msg = ['unknown command "%s"' % cmd_name]
|
||||
if guess:
|
||||
msg.append('maybe you meant "%s"' % guess)
|
||||
|
||||
raise CommandError(' - '.join(msg))
|
||||
|
||||
return cmd_name, cmd_args
|
||||
|
||||
|
||||
def main(initial_args=None):
|
||||
if initial_args is None:
|
||||
initial_args = sys.argv[1:]
|
||||
|
||||
autocomplete()
|
||||
|
||||
try:
|
||||
cmd_name, cmd_args = parseopts(initial_args)
|
||||
except PipError:
|
||||
e = sys.exc_info()[1]
|
||||
sys.stderr.write("ERROR: %s" % e)
|
||||
sys.stderr.write(os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
command = commands[cmd_name]()
|
||||
return command.main(cmd_args)
|
||||
|
||||
|
||||
def bootstrap():
|
||||
"""
|
||||
Bootstrapping function to be called from install-pip.py script.
|
||||
"""
|
||||
pkgs = ['pip']
|
||||
try:
|
||||
import setuptools
|
||||
except ImportError:
|
||||
pkgs.append('setuptools')
|
||||
return main(['install', '--upgrade'] + pkgs + sys.argv[1:])
|
||||
|
||||
############################################################
|
||||
## Writing freeze files
|
||||
|
||||
|
||||
class FrozenRequirement(object):
|
||||
|
||||
def __init__(self, name, req, editable, comments=()):
|
||||
self.name = name
|
||||
self.req = req
|
||||
self.editable = editable
|
||||
self.comments = comments
|
||||
|
||||
_rev_re = re.compile(r'-r(\d+)$')
|
||||
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist, dependency_links, find_tags=False):
|
||||
location = os.path.normcase(os.path.abspath(dist.location))
|
||||
comments = []
|
||||
from pip.vcs import vcs, get_src_requirement
|
||||
if vcs.get_backend_name(location):
|
||||
editable = True
|
||||
try:
|
||||
req = get_src_requirement(dist, location, find_tags)
|
||||
except InstallationError:
|
||||
ex = sys.exc_info()[1]
|
||||
logger.warn("Error when trying to get requirement for VCS system %s, falling back to uneditable format" % ex)
|
||||
req = None
|
||||
if req is None:
|
||||
logger.warn('Could not determine repository location of %s' % location)
|
||||
comments.append('## !! Could not determine repository location')
|
||||
req = dist.as_requirement()
|
||||
editable = False
|
||||
else:
|
||||
editable = False
|
||||
req = dist.as_requirement()
|
||||
specs = req.specs
|
||||
assert len(specs) == 1 and specs[0][0] == '=='
|
||||
version = specs[0][1]
|
||||
ver_match = cls._rev_re.search(version)
|
||||
date_match = cls._date_re.search(version)
|
||||
if ver_match or date_match:
|
||||
svn_backend = vcs.get_backend('svn')
|
||||
if svn_backend:
|
||||
svn_location = svn_backend(
|
||||
).get_location(dist, dependency_links)
|
||||
if not svn_location:
|
||||
logger.warn(
|
||||
'Warning: cannot find svn location for %s' % req)
|
||||
comments.append('## FIXME: could not find svn URL in dependency_links for this package:')
|
||||
else:
|
||||
comments.append('# Installing as editable to satisfy requirement %s:' % req)
|
||||
if ver_match:
|
||||
rev = ver_match.group(1)
|
||||
else:
|
||||
rev = '{%s}' % date_match.group(1)
|
||||
editable = True
|
||||
req = '%s@%s#egg=%s' % (svn_location, rev, cls.egg_name(dist))
|
||||
return cls(dist.project_name, req, editable, comments)
|
||||
|
||||
@staticmethod
|
||||
def egg_name(dist):
|
||||
name = dist.egg_name()
|
||||
match = re.search(r'-py\d\.\d$', name)
|
||||
if match:
|
||||
name = name[:match.start()]
|
||||
return name
|
||||
|
||||
def __str__(self):
|
||||
req = self.req
|
||||
if self.editable:
|
||||
req = '-e %s' % req
|
||||
return '\n'.join(list(self.comments) + [str(req)]) + '\n'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit = main()
|
||||
if exit:
|
||||
sys.exit(exit)
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import sys
|
||||
from .runner import run
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit = run()
|
||||
if exit:
|
||||
sys.exit(exit)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
pip._vendor is for vendoring dependencies of pip to prevent needing pip to
|
||||
depend on something external.
|
||||
|
||||
Files inside of pip._vendor should be considered immutable and should only be
|
||||
updated to versions from upstream.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
@@ -0,0 +1,16 @@
|
||||
try:
|
||||
import ast
|
||||
from pip._vendor._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)()
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# -*- 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
|
||||
}
|
||||
|
||||
for var in list(_VARS.keys()):
|
||||
if '.' in var:
|
||||
_VARS[var.replace('.', '_')] = _VARS[var]
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
from .initialise import init, deinit, reinit
|
||||
from .ansi import Fore, Back, Style
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
VERSION = '0.2.7'
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
'''
|
||||
This module generates ANSI character codes to printing colors to terminals.
|
||||
See: http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
'''
|
||||
|
||||
CSI = '\033['
|
||||
|
||||
def code_to_chars(code):
|
||||
return CSI + str(code) + 'm'
|
||||
|
||||
class AnsiCodes(object):
|
||||
def __init__(self, codes):
|
||||
for name in dir(codes):
|
||||
if not name.startswith('_'):
|
||||
value = getattr(codes, name)
|
||||
setattr(self, name, code_to_chars(value))
|
||||
|
||||
class AnsiFore:
|
||||
BLACK = 30
|
||||
RED = 31
|
||||
GREEN = 32
|
||||
YELLOW = 33
|
||||
BLUE = 34
|
||||
MAGENTA = 35
|
||||
CYAN = 36
|
||||
WHITE = 37
|
||||
RESET = 39
|
||||
|
||||
class AnsiBack:
|
||||
BLACK = 40
|
||||
RED = 41
|
||||
GREEN = 42
|
||||
YELLOW = 43
|
||||
BLUE = 44
|
||||
MAGENTA = 45
|
||||
CYAN = 46
|
||||
WHITE = 47
|
||||
RESET = 49
|
||||
|
||||
class AnsiStyle:
|
||||
BRIGHT = 1
|
||||
DIM = 2
|
||||
NORMAL = 22
|
||||
RESET_ALL = 0
|
||||
|
||||
Fore = AnsiCodes( AnsiFore )
|
||||
Back = AnsiCodes( AnsiBack )
|
||||
Style = AnsiCodes( AnsiStyle )
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import re
|
||||
import sys
|
||||
|
||||
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
|
||||
from .winterm import WinTerm, WinColor, WinStyle
|
||||
from .win32 import windll
|
||||
|
||||
|
||||
if windll is not None:
|
||||
winterm = WinTerm()
|
||||
|
||||
|
||||
def is_a_tty(stream):
|
||||
return hasattr(stream, 'isatty') and stream.isatty()
|
||||
|
||||
|
||||
class StreamWrapper(object):
|
||||
'''
|
||||
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
||||
attribute access apart from method 'write()', which is delegated to our
|
||||
Converter instance.
|
||||
'''
|
||||
def __init__(self, wrapped, converter):
|
||||
# double-underscore everything to prevent clashes with names of
|
||||
# attributes on the wrapped stream object.
|
||||
self.__wrapped = wrapped
|
||||
self.__convertor = converter
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__wrapped, name)
|
||||
|
||||
def write(self, text):
|
||||
self.__convertor.write(text)
|
||||
|
||||
|
||||
class AnsiToWin32(object):
|
||||
'''
|
||||
Implements a 'write()' method which, on Windows, will strip ANSI character
|
||||
sequences from the text, and if outputting to a tty, will convert them into
|
||||
win32 function calls.
|
||||
'''
|
||||
ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
|
||||
# The wrapped stream (normally sys.stdout or sys.stderr)
|
||||
self.wrapped = wrapped
|
||||
|
||||
# should we reset colors to defaults after every .write()
|
||||
self.autoreset = autoreset
|
||||
|
||||
# create the proxy wrapping our output stream
|
||||
self.stream = StreamWrapper(wrapped, self)
|
||||
|
||||
on_windows = sys.platform.startswith('win')
|
||||
|
||||
# should we strip ANSI sequences from our output?
|
||||
if strip is None:
|
||||
strip = on_windows
|
||||
self.strip = strip
|
||||
|
||||
# should we should convert ANSI sequences into win32 calls?
|
||||
if convert is None:
|
||||
convert = on_windows and is_a_tty(wrapped)
|
||||
self.convert = convert
|
||||
|
||||
# dict of ansi codes to win32 functions and parameters
|
||||
self.win32_calls = self.get_win32_calls()
|
||||
|
||||
# are we wrapping stderr?
|
||||
self.on_stderr = self.wrapped is sys.stderr
|
||||
|
||||
|
||||
def should_wrap(self):
|
||||
'''
|
||||
True if this class is actually needed. If false, then the output
|
||||
stream will not be affected, nor will win32 calls be issued, so
|
||||
wrapping stdout is not actually required. This will generally be
|
||||
False on non-Windows platforms, unless optional functionality like
|
||||
autoreset has been requested using kwargs to init()
|
||||
'''
|
||||
return self.convert or self.strip or self.autoreset
|
||||
|
||||
|
||||
def get_win32_calls(self):
|
||||
if self.convert and winterm:
|
||||
return {
|
||||
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
|
||||
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
|
||||
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
|
||||
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
|
||||
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
|
||||
AnsiFore.RED: (winterm.fore, WinColor.RED),
|
||||
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
|
||||
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
|
||||
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
|
||||
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
|
||||
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
|
||||
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
|
||||
AnsiFore.RESET: (winterm.fore, ),
|
||||
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
|
||||
AnsiBack.RED: (winterm.back, WinColor.RED),
|
||||
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
|
||||
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
|
||||
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
|
||||
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
|
||||
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
|
||||
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
|
||||
AnsiBack.RESET: (winterm.back, ),
|
||||
}
|
||||
|
||||
|
||||
def write(self, text):
|
||||
if self.strip or self.convert:
|
||||
self.write_and_convert(text)
|
||||
else:
|
||||
self.wrapped.write(text)
|
||||
self.wrapped.flush()
|
||||
if self.autoreset:
|
||||
self.reset_all()
|
||||
|
||||
|
||||
def reset_all(self):
|
||||
if self.convert:
|
||||
self.call_win32('m', (0,))
|
||||
elif is_a_tty(self.wrapped):
|
||||
self.wrapped.write(Style.RESET_ALL)
|
||||
|
||||
|
||||
def write_and_convert(self, text):
|
||||
'''
|
||||
Write the given text to our wrapped stream, stripping any ANSI
|
||||
sequences from the text, and optionally converting them into win32
|
||||
calls.
|
||||
'''
|
||||
cursor = 0
|
||||
for match in self.ANSI_RE.finditer(text):
|
||||
start, end = match.span()
|
||||
self.write_plain_text(text, cursor, start)
|
||||
self.convert_ansi(*match.groups())
|
||||
cursor = end
|
||||
self.write_plain_text(text, cursor, len(text))
|
||||
|
||||
|
||||
def write_plain_text(self, text, start, end):
|
||||
if start < end:
|
||||
self.wrapped.write(text[start:end])
|
||||
self.wrapped.flush()
|
||||
|
||||
|
||||
def convert_ansi(self, paramstring, command):
|
||||
if self.convert:
|
||||
params = self.extract_params(paramstring)
|
||||
self.call_win32(command, params)
|
||||
|
||||
|
||||
def extract_params(self, paramstring):
|
||||
def split(paramstring):
|
||||
for p in paramstring.split(';'):
|
||||
if p != '':
|
||||
yield int(p)
|
||||
return tuple(split(paramstring))
|
||||
|
||||
|
||||
def call_win32(self, command, params):
|
||||
if params == []:
|
||||
params = [0]
|
||||
if command == 'm':
|
||||
for param in params:
|
||||
if param in self.win32_calls:
|
||||
func_args = self.win32_calls[param]
|
||||
func = func_args[0]
|
||||
args = func_args[1:]
|
||||
kwargs = dict(on_stderr=self.on_stderr)
|
||||
func(*args, **kwargs)
|
||||
elif command in ('H', 'f'): # set cursor position
|
||||
func = winterm.set_cursor_position
|
||||
func(params, on_stderr=self.on_stderr)
|
||||
elif command in ('J'):
|
||||
func = winterm.erase_data
|
||||
func(params, on_stderr=self.on_stderr)
|
||||
elif command == 'A':
|
||||
if params == () or params == None:
|
||||
num_rows = 1
|
||||
else:
|
||||
num_rows = params[0]
|
||||
func = winterm.cursor_up
|
||||
func(num_rows, on_stderr=self.on_stderr)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import atexit
|
||||
import sys
|
||||
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
|
||||
orig_stdout = sys.stdout
|
||||
orig_stderr = sys.stderr
|
||||
|
||||
wrapped_stdout = sys.stdout
|
||||
wrapped_stderr = sys.stderr
|
||||
|
||||
atexit_done = False
|
||||
|
||||
|
||||
def reset_all():
|
||||
AnsiToWin32(orig_stdout).reset_all()
|
||||
|
||||
|
||||
def init(autoreset=False, convert=None, strip=None, wrap=True):
|
||||
|
||||
if not wrap and any([autoreset, convert, strip]):
|
||||
raise ValueError('wrap=False conflicts with any other arg=True')
|
||||
|
||||
global wrapped_stdout, wrapped_stderr
|
||||
sys.stdout = wrapped_stdout = \
|
||||
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
|
||||
sys.stderr = wrapped_stderr = \
|
||||
wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
|
||||
|
||||
global atexit_done
|
||||
if not atexit_done:
|
||||
atexit.register(reset_all)
|
||||
atexit_done = True
|
||||
|
||||
|
||||
def deinit():
|
||||
sys.stdout = orig_stdout
|
||||
sys.stderr = orig_stderr
|
||||
|
||||
|
||||
def reinit():
|
||||
sys.stdout = wrapped_stdout
|
||||
sys.stderr = wrapped_stdout
|
||||
|
||||
|
||||
def wrap_stream(stream, convert, strip, autoreset, wrap):
|
||||
if wrap:
|
||||
wrapper = AnsiToWin32(stream,
|
||||
convert=convert, strip=strip, autoreset=autoreset)
|
||||
if wrapper.should_wrap():
|
||||
stream = wrapper.stream
|
||||
return stream
|
||||
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
|
||||
# from winbase.h
|
||||
STDOUT = -11
|
||||
STDERR = -12
|
||||
|
||||
try:
|
||||
from ctypes import windll
|
||||
from ctypes import wintypes
|
||||
except ImportError:
|
||||
windll = None
|
||||
SetConsoleTextAttribute = lambda *_: None
|
||||
else:
|
||||
from ctypes import (
|
||||
byref, Structure, c_char, c_short, c_uint32, c_ushort, POINTER
|
||||
)
|
||||
|
||||
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
||||
"""struct in wincon.h."""
|
||||
_fields_ = [
|
||||
("dwSize", wintypes._COORD),
|
||||
("dwCursorPosition", wintypes._COORD),
|
||||
("wAttributes", wintypes.WORD),
|
||||
("srWindow", wintypes.SMALL_RECT),
|
||||
("dwMaximumWindowSize", wintypes._COORD),
|
||||
]
|
||||
def __str__(self):
|
||||
return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
|
||||
self.dwSize.Y, self.dwSize.X
|
||||
, self.dwCursorPosition.Y, self.dwCursorPosition.X
|
||||
, self.wAttributes
|
||||
, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
|
||||
, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
|
||||
)
|
||||
|
||||
_GetStdHandle = windll.kernel32.GetStdHandle
|
||||
_GetStdHandle.argtypes = [
|
||||
wintypes.DWORD,
|
||||
]
|
||||
_GetStdHandle.restype = wintypes.HANDLE
|
||||
|
||||
_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
|
||||
_GetConsoleScreenBufferInfo.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
POINTER(CONSOLE_SCREEN_BUFFER_INFO),
|
||||
]
|
||||
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
|
||||
|
||||
_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
|
||||
_SetConsoleTextAttribute.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
wintypes.WORD,
|
||||
]
|
||||
_SetConsoleTextAttribute.restype = wintypes.BOOL
|
||||
|
||||
_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
|
||||
_SetConsoleCursorPosition.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
wintypes._COORD,
|
||||
]
|
||||
_SetConsoleCursorPosition.restype = wintypes.BOOL
|
||||
|
||||
_FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
|
||||
_FillConsoleOutputCharacterA.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
c_char,
|
||||
wintypes.DWORD,
|
||||
wintypes._COORD,
|
||||
POINTER(wintypes.DWORD),
|
||||
]
|
||||
_FillConsoleOutputCharacterA.restype = wintypes.BOOL
|
||||
|
||||
_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
|
||||
_FillConsoleOutputAttribute.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
wintypes.WORD,
|
||||
wintypes.DWORD,
|
||||
wintypes._COORD,
|
||||
POINTER(wintypes.DWORD),
|
||||
]
|
||||
_FillConsoleOutputAttribute.restype = wintypes.BOOL
|
||||
|
||||
handles = {
|
||||
STDOUT: _GetStdHandle(STDOUT),
|
||||
STDERR: _GetStdHandle(STDERR),
|
||||
}
|
||||
|
||||
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
|
||||
handle = handles[stream_id]
|
||||
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
success = _GetConsoleScreenBufferInfo(
|
||||
handle, byref(csbi))
|
||||
return csbi
|
||||
|
||||
def SetConsoleTextAttribute(stream_id, attrs):
|
||||
handle = handles[stream_id]
|
||||
return _SetConsoleTextAttribute(handle, attrs)
|
||||
|
||||
def SetConsoleCursorPosition(stream_id, position):
|
||||
position = wintypes._COORD(*position)
|
||||
# If the position is out of range, do nothing.
|
||||
if position.Y <= 0 or position.X <= 0:
|
||||
return
|
||||
# Adjust for Windows' SetConsoleCursorPosition:
|
||||
# 1. being 0-based, while ANSI is 1-based.
|
||||
# 2. expecting (x,y), while ANSI uses (y,x).
|
||||
adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1)
|
||||
# Adjust for viewport's scroll position
|
||||
sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
|
||||
adjusted_position.Y += sr.Top
|
||||
adjusted_position.X += sr.Left
|
||||
# Resume normal processing
|
||||
handle = handles[stream_id]
|
||||
return _SetConsoleCursorPosition(handle, adjusted_position)
|
||||
|
||||
def FillConsoleOutputCharacter(stream_id, char, length, start):
|
||||
handle = handles[stream_id]
|
||||
char = c_char(char)
|
||||
length = wintypes.DWORD(length)
|
||||
num_written = wintypes.DWORD(0)
|
||||
# Note that this is hard-coded for ANSI (vs wide) bytes.
|
||||
success = _FillConsoleOutputCharacterA(
|
||||
handle, char, length, start, byref(num_written))
|
||||
return num_written.value
|
||||
|
||||
def FillConsoleOutputAttribute(stream_id, attr, length, start):
|
||||
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
|
||||
handle = handles[stream_id]
|
||||
attribute = wintypes.WORD(attr)
|
||||
length = wintypes.DWORD(length)
|
||||
num_written = wintypes.DWORD(0)
|
||||
# Note that this is hard-coded for ANSI (vs wide) bytes.
|
||||
return _FillConsoleOutputAttribute(
|
||||
handle, attribute, length, start, byref(num_written))
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
from . import win32
|
||||
|
||||
|
||||
# from wincon.h
|
||||
class WinColor(object):
|
||||
BLACK = 0
|
||||
BLUE = 1
|
||||
GREEN = 2
|
||||
CYAN = 3
|
||||
RED = 4
|
||||
MAGENTA = 5
|
||||
YELLOW = 6
|
||||
GREY = 7
|
||||
|
||||
# from wincon.h
|
||||
class WinStyle(object):
|
||||
NORMAL = 0x00 # dim text, dim background
|
||||
BRIGHT = 0x08 # bright text, dim background
|
||||
|
||||
|
||||
class WinTerm(object):
|
||||
|
||||
def __init__(self):
|
||||
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
|
||||
self.set_attrs(self._default)
|
||||
self._default_fore = self._fore
|
||||
self._default_back = self._back
|
||||
self._default_style = self._style
|
||||
|
||||
def get_attrs(self):
|
||||
return self._fore + self._back * 16 + self._style
|
||||
|
||||
def set_attrs(self, value):
|
||||
self._fore = value & 7
|
||||
self._back = (value >> 4) & 7
|
||||
self._style = value & WinStyle.BRIGHT
|
||||
|
||||
def reset_all(self, on_stderr=None):
|
||||
self.set_attrs(self._default)
|
||||
self.set_console(attrs=self._default)
|
||||
|
||||
def fore(self, fore=None, on_stderr=False):
|
||||
if fore is None:
|
||||
fore = self._default_fore
|
||||
self._fore = fore
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def back(self, back=None, on_stderr=False):
|
||||
if back is None:
|
||||
back = self._default_back
|
||||
self._back = back
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def style(self, style=None, on_stderr=False):
|
||||
if style is None:
|
||||
style = self._default_style
|
||||
self._style = style
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def set_console(self, attrs=None, on_stderr=False):
|
||||
if attrs is None:
|
||||
attrs = self.get_attrs()
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
win32.SetConsoleTextAttribute(handle, attrs)
|
||||
|
||||
def get_position(self, handle):
|
||||
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
|
||||
# Because Windows coordinates are 0-based,
|
||||
# and win32.SetConsoleCursorPosition expects 1-based.
|
||||
position.X += 1
|
||||
position.Y += 1
|
||||
return position
|
||||
|
||||
def set_cursor_position(self, position=None, on_stderr=False):
|
||||
if position is None:
|
||||
#I'm not currently tracking the position, so there is no default.
|
||||
#position = self.get_position()
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
win32.SetConsoleCursorPosition(handle, position)
|
||||
|
||||
def cursor_up(self, num_rows=0, on_stderr=False):
|
||||
if num_rows == 0:
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
position = self.get_position(handle)
|
||||
adjusted_position = (position.Y - num_rows, position.X)
|
||||
self.set_cursor_position(adjusted_position, on_stderr)
|
||||
|
||||
def erase_data(self, mode=0, on_stderr=False):
|
||||
# 0 (or None) should clear from the cursor to the end of the screen.
|
||||
# 1 should clear from the cursor to the beginning of the screen.
|
||||
# 2 should clear the entire screen. (And maybe move cursor to (1,1)?)
|
||||
#
|
||||
# At the moment, I only support mode 2. From looking at the API, it
|
||||
# should be possible to calculate a different number of bytes to clear,
|
||||
# and to do so relative to the cursor position.
|
||||
if mode[0] not in (2,):
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
# here's where we'll home the cursor
|
||||
coord_screen = win32.COORD(0,0)
|
||||
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
||||
# get the number of character cells in the current buffer
|
||||
dw_con_size = csbi.dwSize.X * csbi.dwSize.Y
|
||||
# fill the entire screen with blanks
|
||||
win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen)
|
||||
# now set the buffer's attributes accordingly
|
||||
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen );
|
||||
# put the cursor at (0, 0)
|
||||
win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y))
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import logging
|
||||
|
||||
__version__ = '0.1.7'
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError: # pragma: no cover
|
||||
class NullHandler(logging.Handler):
|
||||
def handle(self, record): pass
|
||||
def emit(self, record): pass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(NullHandler())
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Modules copied from Python 3 standard libraries, for internal use only.
|
||||
|
||||
Individual classes and functions are found in d2._backport.misc. Intended
|
||||
usage is to always import things missing from 3.1 from that module: the
|
||||
built-in/stdlib objects will be used if found.
|
||||
"""
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Backports for individual classes and functions."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
__all__ = ['cache_from_source', 'callable', 'fsencode']
|
||||
|
||||
|
||||
try:
|
||||
from imp import cache_from_source
|
||||
except ImportError:
|
||||
def cache_from_source(py_file, debug=__debug__):
|
||||
ext = debug and 'c' or 'o'
|
||||
return py_file + ext
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
from collections import Callable
|
||||
|
||||
def callable(obj):
|
||||
return isinstance(obj, Callable)
|
||||
|
||||
|
||||
try:
|
||||
fsencode = os.fsencode
|
||||
except AttributeError:
|
||||
def fsencode(filename):
|
||||
if isinstance(filename, bytes):
|
||||
return filename
|
||||
elif isinstance(filename, str):
|
||||
return filename.encode(sys.getfilesystemencoding())
|
||||
else:
|
||||
raise TypeError("expect bytes or str, not %s" %
|
||||
type(filename).__name__)
|
||||
@@ -0,0 +1,761 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Utility functions for copying and archiving files and directory trees.
|
||||
|
||||
XXX The functions here don't copy the resource fork or other metadata on Mac.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
from os.path import abspath
|
||||
import fnmatch
|
||||
import collections
|
||||
import errno
|
||||
from . import tarfile
|
||||
|
||||
try:
|
||||
import bz2
|
||||
_BZ2_SUPPORTED = True
|
||||
except ImportError:
|
||||
_BZ2_SUPPORTED = False
|
||||
|
||||
try:
|
||||
from pwd import getpwnam
|
||||
except ImportError:
|
||||
getpwnam = None
|
||||
|
||||
try:
|
||||
from grp import getgrnam
|
||||
except ImportError:
|
||||
getgrnam = None
|
||||
|
||||
__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
|
||||
"copytree", "move", "rmtree", "Error", "SpecialFileError",
|
||||
"ExecError", "make_archive", "get_archive_formats",
|
||||
"register_archive_format", "unregister_archive_format",
|
||||
"get_unpack_formats", "register_unpack_format",
|
||||
"unregister_unpack_format", "unpack_archive", "ignore_patterns"]
|
||||
|
||||
class Error(EnvironmentError):
|
||||
pass
|
||||
|
||||
class SpecialFileError(EnvironmentError):
|
||||
"""Raised when trying to do a kind of operation (e.g. copying) which is
|
||||
not supported on a special file (e.g. a named pipe)"""
|
||||
|
||||
class ExecError(EnvironmentError):
|
||||
"""Raised when a command could not be executed"""
|
||||
|
||||
class ReadError(EnvironmentError):
|
||||
"""Raised when an archive cannot be read"""
|
||||
|
||||
class RegistryError(Exception):
|
||||
"""Raised when a registery operation with the archiving
|
||||
and unpacking registeries fails"""
|
||||
|
||||
|
||||
try:
|
||||
WindowsError
|
||||
except NameError:
|
||||
WindowsError = None
|
||||
|
||||
def copyfileobj(fsrc, fdst, length=16*1024):
|
||||
"""copy data from file-like object fsrc to file-like object fdst"""
|
||||
while 1:
|
||||
buf = fsrc.read(length)
|
||||
if not buf:
|
||||
break
|
||||
fdst.write(buf)
|
||||
|
||||
def _samefile(src, dst):
|
||||
# Macintosh, Unix.
|
||||
if hasattr(os.path, 'samefile'):
|
||||
try:
|
||||
return os.path.samefile(src, dst)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# All other platforms: check for same pathname.
|
||||
return (os.path.normcase(os.path.abspath(src)) ==
|
||||
os.path.normcase(os.path.abspath(dst)))
|
||||
|
||||
def copyfile(src, dst):
|
||||
"""Copy data from src to dst"""
|
||||
if _samefile(src, dst):
|
||||
raise Error("`%s` and `%s` are the same file" % (src, dst))
|
||||
|
||||
for fn in [src, dst]:
|
||||
try:
|
||||
st = os.stat(fn)
|
||||
except OSError:
|
||||
# File most likely does not exist
|
||||
pass
|
||||
else:
|
||||
# XXX What about other special files? (sockets, devices...)
|
||||
if stat.S_ISFIFO(st.st_mode):
|
||||
raise SpecialFileError("`%s` is a named pipe" % fn)
|
||||
|
||||
with open(src, 'rb') as fsrc:
|
||||
with open(dst, 'wb') as fdst:
|
||||
copyfileobj(fsrc, fdst)
|
||||
|
||||
def copymode(src, dst):
|
||||
"""Copy mode bits from src to dst"""
|
||||
if hasattr(os, 'chmod'):
|
||||
st = os.stat(src)
|
||||
mode = stat.S_IMODE(st.st_mode)
|
||||
os.chmod(dst, mode)
|
||||
|
||||
def copystat(src, dst):
|
||||
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
|
||||
st = os.stat(src)
|
||||
mode = stat.S_IMODE(st.st_mode)
|
||||
if hasattr(os, 'utime'):
|
||||
os.utime(dst, (st.st_atime, st.st_mtime))
|
||||
if hasattr(os, 'chmod'):
|
||||
os.chmod(dst, mode)
|
||||
if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
|
||||
try:
|
||||
os.chflags(dst, st.st_flags)
|
||||
except OSError as why:
|
||||
if (not hasattr(errno, 'EOPNOTSUPP') or
|
||||
why.errno != errno.EOPNOTSUPP):
|
||||
raise
|
||||
|
||||
def copy(src, dst):
|
||||
"""Copy data and mode bits ("cp src dst").
|
||||
|
||||
The destination may be a directory.
|
||||
|
||||
"""
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
copyfile(src, dst)
|
||||
copymode(src, dst)
|
||||
|
||||
def copy2(src, dst):
|
||||
"""Copy data and all stat info ("cp -p src dst").
|
||||
|
||||
The destination may be a directory.
|
||||
|
||||
"""
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
copyfile(src, dst)
|
||||
copystat(src, dst)
|
||||
|
||||
def ignore_patterns(*patterns):
|
||||
"""Function that can be used as copytree() ignore parameter.
|
||||
|
||||
Patterns is a sequence of glob-style patterns
|
||||
that are used to exclude files"""
|
||||
def _ignore_patterns(path, names):
|
||||
ignored_names = []
|
||||
for pattern in patterns:
|
||||
ignored_names.extend(fnmatch.filter(names, pattern))
|
||||
return set(ignored_names)
|
||||
return _ignore_patterns
|
||||
|
||||
def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
|
||||
ignore_dangling_symlinks=False):
|
||||
"""Recursively copy a directory tree.
|
||||
|
||||
The destination directory must not already exist.
|
||||
If exception(s) occur, an Error is raised with a list of reasons.
|
||||
|
||||
If the optional symlinks flag is true, symbolic links in the
|
||||
source tree result in symbolic links in the destination tree; if
|
||||
it is false, the contents of the files pointed to by symbolic
|
||||
links are copied. If the file pointed by the symlink doesn't
|
||||
exist, an exception will be added in the list of errors raised in
|
||||
an Error exception at the end of the copy process.
|
||||
|
||||
You can set the optional ignore_dangling_symlinks flag to true if you
|
||||
want to silence this exception. Notice that this has no effect on
|
||||
platforms that don't support os.symlink.
|
||||
|
||||
The optional ignore argument is a callable. If given, it
|
||||
is called with the `src` parameter, which is the directory
|
||||
being visited by copytree(), and `names` which is the list of
|
||||
`src` contents, as returned by os.listdir():
|
||||
|
||||
callable(src, names) -> ignored_names
|
||||
|
||||
Since copytree() is called recursively, the callable will be
|
||||
called once for each directory that is copied. It returns a
|
||||
list of names relative to the `src` directory that should
|
||||
not be copied.
|
||||
|
||||
The optional copy_function argument is a callable that will be used
|
||||
to copy each file. It will be called with the source path and the
|
||||
destination path as arguments. By default, copy2() is used, but any
|
||||
function that supports the same signature (like copy()) can be used.
|
||||
|
||||
"""
|
||||
names = os.listdir(src)
|
||||
if ignore is not None:
|
||||
ignored_names = ignore(src, names)
|
||||
else:
|
||||
ignored_names = set()
|
||||
|
||||
os.makedirs(dst)
|
||||
errors = []
|
||||
for name in names:
|
||||
if name in ignored_names:
|
||||
continue
|
||||
srcname = os.path.join(src, name)
|
||||
dstname = os.path.join(dst, name)
|
||||
try:
|
||||
if os.path.islink(srcname):
|
||||
linkto = os.readlink(srcname)
|
||||
if symlinks:
|
||||
os.symlink(linkto, dstname)
|
||||
else:
|
||||
# ignore dangling symlink if the flag is on
|
||||
if not os.path.exists(linkto) and ignore_dangling_symlinks:
|
||||
continue
|
||||
# otherwise let the copy occurs. copy2 will raise an error
|
||||
copy_function(srcname, dstname)
|
||||
elif os.path.isdir(srcname):
|
||||
copytree(srcname, dstname, symlinks, ignore, copy_function)
|
||||
else:
|
||||
# Will raise a SpecialFileError for unsupported file types
|
||||
copy_function(srcname, dstname)
|
||||
# catch the Error from the recursive copytree so that we can
|
||||
# continue with other files
|
||||
except Error as err:
|
||||
errors.extend(err.args[0])
|
||||
except EnvironmentError as why:
|
||||
errors.append((srcname, dstname, str(why)))
|
||||
try:
|
||||
copystat(src, dst)
|
||||
except OSError as why:
|
||||
if WindowsError is not None and isinstance(why, WindowsError):
|
||||
# Copying file access times may fail on Windows
|
||||
pass
|
||||
else:
|
||||
errors.extend((src, dst, str(why)))
|
||||
if errors:
|
||||
raise Error(errors)
|
||||
|
||||
def rmtree(path, ignore_errors=False, onerror=None):
|
||||
"""Recursively delete a directory tree.
|
||||
|
||||
If ignore_errors is set, errors are ignored; otherwise, if onerror
|
||||
is set, it is called to handle the error with arguments (func,
|
||||
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
|
||||
path is the argument to that function that caused it to fail; and
|
||||
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
|
||||
is false and onerror is None, an exception is raised.
|
||||
|
||||
"""
|
||||
if ignore_errors:
|
||||
def onerror(*args):
|
||||
pass
|
||||
elif onerror is None:
|
||||
def onerror(*args):
|
||||
raise
|
||||
try:
|
||||
if os.path.islink(path):
|
||||
# symlinks to directories are forbidden, see bug #1669
|
||||
raise OSError("Cannot call rmtree on a symbolic link")
|
||||
except OSError:
|
||||
onerror(os.path.islink, path, sys.exc_info())
|
||||
# can't continue even if onerror hook returns
|
||||
return
|
||||
names = []
|
||||
try:
|
||||
names = os.listdir(path)
|
||||
except os.error:
|
||||
onerror(os.listdir, path, sys.exc_info())
|
||||
for name in names:
|
||||
fullname = os.path.join(path, name)
|
||||
try:
|
||||
mode = os.lstat(fullname).st_mode
|
||||
except os.error:
|
||||
mode = 0
|
||||
if stat.S_ISDIR(mode):
|
||||
rmtree(fullname, ignore_errors, onerror)
|
||||
else:
|
||||
try:
|
||||
os.remove(fullname)
|
||||
except os.error:
|
||||
onerror(os.remove, fullname, sys.exc_info())
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except os.error:
|
||||
onerror(os.rmdir, path, sys.exc_info())
|
||||
|
||||
|
||||
def _basename(path):
|
||||
# A basename() variant which first strips the trailing slash, if present.
|
||||
# Thus we always get the last component of the path, even for directories.
|
||||
return os.path.basename(path.rstrip(os.path.sep))
|
||||
|
||||
def move(src, dst):
|
||||
"""Recursively move a file or directory to another location. This is
|
||||
similar to the Unix "mv" command.
|
||||
|
||||
If the destination is a directory or a symlink to a directory, the source
|
||||
is moved inside the directory. The destination path must not already
|
||||
exist.
|
||||
|
||||
If the destination already exists but is not a directory, it may be
|
||||
overwritten depending on os.rename() semantics.
|
||||
|
||||
If the destination is on our current filesystem, then rename() is used.
|
||||
Otherwise, src is copied to the destination and then removed.
|
||||
A lot more could be done here... A look at a mv.c shows a lot of
|
||||
the issues this implementation glosses over.
|
||||
|
||||
"""
|
||||
real_dst = dst
|
||||
if os.path.isdir(dst):
|
||||
if _samefile(src, dst):
|
||||
# We might be on a case insensitive filesystem,
|
||||
# perform the rename anyway.
|
||||
os.rename(src, dst)
|
||||
return
|
||||
|
||||
real_dst = os.path.join(dst, _basename(src))
|
||||
if os.path.exists(real_dst):
|
||||
raise Error("Destination path '%s' already exists" % real_dst)
|
||||
try:
|
||||
os.rename(src, real_dst)
|
||||
except OSError:
|
||||
if os.path.isdir(src):
|
||||
if _destinsrc(src, dst):
|
||||
raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
|
||||
copytree(src, real_dst, symlinks=True)
|
||||
rmtree(src)
|
||||
else:
|
||||
copy2(src, real_dst)
|
||||
os.unlink(src)
|
||||
|
||||
def _destinsrc(src, dst):
|
||||
src = abspath(src)
|
||||
dst = abspath(dst)
|
||||
if not src.endswith(os.path.sep):
|
||||
src += os.path.sep
|
||||
if not dst.endswith(os.path.sep):
|
||||
dst += os.path.sep
|
||||
return dst.startswith(src)
|
||||
|
||||
def _get_gid(name):
|
||||
"""Returns a gid, given a group name."""
|
||||
if getgrnam is None or name is None:
|
||||
return None
|
||||
try:
|
||||
result = getgrnam(name)
|
||||
except KeyError:
|
||||
result = None
|
||||
if result is not None:
|
||||
return result[2]
|
||||
return None
|
||||
|
||||
def _get_uid(name):
|
||||
"""Returns an uid, given a user name."""
|
||||
if getpwnam is None or name is None:
|
||||
return None
|
||||
try:
|
||||
result = getpwnam(name)
|
||||
except KeyError:
|
||||
result = None
|
||||
if result is not None:
|
||||
return result[2]
|
||||
return None
|
||||
|
||||
def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
||||
owner=None, group=None, logger=None):
|
||||
"""Create a (possibly compressed) tar file from all the files under
|
||||
'base_dir'.
|
||||
|
||||
'compress' must be "gzip" (the default), "bzip2", or None.
|
||||
|
||||
'owner' and 'group' can be used to define an owner and a group for the
|
||||
archive that is being built. If not provided, the current owner and group
|
||||
will be used.
|
||||
|
||||
The output tar file will be named 'base_name' + ".tar", possibly plus
|
||||
the appropriate compression extension (".gz", or ".bz2").
|
||||
|
||||
Returns the output filename.
|
||||
"""
|
||||
tar_compression = {'gzip': 'gz', None: ''}
|
||||
compress_ext = {'gzip': '.gz'}
|
||||
|
||||
if _BZ2_SUPPORTED:
|
||||
tar_compression['bzip2'] = 'bz2'
|
||||
compress_ext['bzip2'] = '.bz2'
|
||||
|
||||
# flags for compression program, each element of list will be an argument
|
||||
if compress is not None and compress not in compress_ext:
|
||||
raise ValueError("bad value for 'compress', or compression format not "
|
||||
"supported : {0}".format(compress))
|
||||
|
||||
archive_name = base_name + '.tar' + compress_ext.get(compress, '')
|
||||
archive_dir = os.path.dirname(archive_name)
|
||||
|
||||
if not os.path.exists(archive_dir):
|
||||
if logger is not None:
|
||||
logger.info("creating %s", archive_dir)
|
||||
if not dry_run:
|
||||
os.makedirs(archive_dir)
|
||||
|
||||
# creating the tarball
|
||||
if logger is not None:
|
||||
logger.info('Creating tar archive')
|
||||
|
||||
uid = _get_uid(owner)
|
||||
gid = _get_gid(group)
|
||||
|
||||
def _set_uid_gid(tarinfo):
|
||||
if gid is not None:
|
||||
tarinfo.gid = gid
|
||||
tarinfo.gname = group
|
||||
if uid is not None:
|
||||
tarinfo.uid = uid
|
||||
tarinfo.uname = owner
|
||||
return tarinfo
|
||||
|
||||
if not dry_run:
|
||||
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
|
||||
try:
|
||||
tar.add(base_dir, filter=_set_uid_gid)
|
||||
finally:
|
||||
tar.close()
|
||||
|
||||
return archive_name
|
||||
|
||||
def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
|
||||
# XXX see if we want to keep an external call here
|
||||
if verbose:
|
||||
zipoptions = "-r"
|
||||
else:
|
||||
zipoptions = "-rq"
|
||||
from distutils.errors import DistutilsExecError
|
||||
from distutils.spawn import spawn
|
||||
try:
|
||||
spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
|
||||
except DistutilsExecError:
|
||||
# XXX really should distinguish between "couldn't find
|
||||
# external 'zip' command" and "zip failed".
|
||||
raise ExecError("unable to create zip file '%s': "
|
||||
"could neither import the 'zipfile' module nor "
|
||||
"find a standalone zip utility") % zip_filename
|
||||
|
||||
def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
|
||||
"""Create a zip file from all the files under 'base_dir'.
|
||||
|
||||
The output zip file will be named 'base_name' + ".zip". Uses either the
|
||||
"zipfile" Python module (if available) or the InfoZIP "zip" utility
|
||||
(if installed and found on the default search path). If neither tool is
|
||||
available, raises ExecError. Returns the name of the output zip
|
||||
file.
|
||||
"""
|
||||
zip_filename = base_name + ".zip"
|
||||
archive_dir = os.path.dirname(base_name)
|
||||
|
||||
if not os.path.exists(archive_dir):
|
||||
if logger is not None:
|
||||
logger.info("creating %s", archive_dir)
|
||||
if not dry_run:
|
||||
os.makedirs(archive_dir)
|
||||
|
||||
# If zipfile module is not available, try spawning an external 'zip'
|
||||
# command.
|
||||
try:
|
||||
import zipfile
|
||||
except ImportError:
|
||||
zipfile = None
|
||||
|
||||
if zipfile is None:
|
||||
_call_external_zip(base_dir, zip_filename, verbose, dry_run)
|
||||
else:
|
||||
if logger is not None:
|
||||
logger.info("creating '%s' and adding '%s' to it",
|
||||
zip_filename, base_dir)
|
||||
|
||||
if not dry_run:
|
||||
zip = zipfile.ZipFile(zip_filename, "w",
|
||||
compression=zipfile.ZIP_DEFLATED)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(base_dir):
|
||||
for name in filenames:
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
if os.path.isfile(path):
|
||||
zip.write(path, path)
|
||||
if logger is not None:
|
||||
logger.info("adding '%s'", path)
|
||||
zip.close()
|
||||
|
||||
return zip_filename
|
||||
|
||||
_ARCHIVE_FORMATS = {
|
||||
'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
|
||||
'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
|
||||
'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
|
||||
'zip': (_make_zipfile, [], "ZIP file"),
|
||||
}
|
||||
|
||||
if _BZ2_SUPPORTED:
|
||||
_ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
|
||||
"bzip2'ed tar-file")
|
||||
|
||||
def get_archive_formats():
|
||||
"""Returns a list of supported formats for archiving and unarchiving.
|
||||
|
||||
Each element of the returned sequence is a tuple (name, description)
|
||||
"""
|
||||
formats = [(name, registry[2]) for name, registry in
|
||||
_ARCHIVE_FORMATS.items()]
|
||||
formats.sort()
|
||||
return formats
|
||||
|
||||
def register_archive_format(name, function, extra_args=None, description=''):
|
||||
"""Registers an archive format.
|
||||
|
||||
name is the name of the format. function is the callable that will be
|
||||
used to create archives. If provided, extra_args is a sequence of
|
||||
(name, value) tuples that will be passed as arguments to the callable.
|
||||
description can be provided to describe the format, and will be returned
|
||||
by the get_archive_formats() function.
|
||||
"""
|
||||
if extra_args is None:
|
||||
extra_args = []
|
||||
if not isinstance(function, collections.Callable):
|
||||
raise TypeError('The %s object is not callable' % function)
|
||||
if not isinstance(extra_args, (tuple, list)):
|
||||
raise TypeError('extra_args needs to be a sequence')
|
||||
for element in extra_args:
|
||||
if not isinstance(element, (tuple, list)) or len(element) !=2:
|
||||
raise TypeError('extra_args elements are : (arg_name, value)')
|
||||
|
||||
_ARCHIVE_FORMATS[name] = (function, extra_args, description)
|
||||
|
||||
def unregister_archive_format(name):
|
||||
del _ARCHIVE_FORMATS[name]
|
||||
|
||||
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
|
||||
dry_run=0, owner=None, group=None, logger=None):
|
||||
"""Create an archive file (eg. zip or tar).
|
||||
|
||||
'base_name' is the name of the file to create, minus any format-specific
|
||||
extension; 'format' is the archive format: one of "zip", "tar", "bztar"
|
||||
or "gztar".
|
||||
|
||||
'root_dir' is a directory that will be the root directory of the
|
||||
archive; ie. we typically chdir into 'root_dir' before creating the
|
||||
archive. 'base_dir' is the directory where we start archiving from;
|
||||
ie. 'base_dir' will be the common prefix of all files and
|
||||
directories in the archive. 'root_dir' and 'base_dir' both default
|
||||
to the current directory. Returns the name of the archive file.
|
||||
|
||||
'owner' and 'group' are used when creating a tar archive. By default,
|
||||
uses the current owner and group.
|
||||
"""
|
||||
save_cwd = os.getcwd()
|
||||
if root_dir is not None:
|
||||
if logger is not None:
|
||||
logger.debug("changing into '%s'", root_dir)
|
||||
base_name = os.path.abspath(base_name)
|
||||
if not dry_run:
|
||||
os.chdir(root_dir)
|
||||
|
||||
if base_dir is None:
|
||||
base_dir = os.curdir
|
||||
|
||||
kwargs = {'dry_run': dry_run, 'logger': logger}
|
||||
|
||||
try:
|
||||
format_info = _ARCHIVE_FORMATS[format]
|
||||
except KeyError:
|
||||
raise ValueError("unknown archive format '%s'" % format)
|
||||
|
||||
func = format_info[0]
|
||||
for arg, val in format_info[1]:
|
||||
kwargs[arg] = val
|
||||
|
||||
if format != 'zip':
|
||||
kwargs['owner'] = owner
|
||||
kwargs['group'] = group
|
||||
|
||||
try:
|
||||
filename = func(base_name, base_dir, **kwargs)
|
||||
finally:
|
||||
if root_dir is not None:
|
||||
if logger is not None:
|
||||
logger.debug("changing back to '%s'", save_cwd)
|
||||
os.chdir(save_cwd)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_unpack_formats():
|
||||
"""Returns a list of supported formats for unpacking.
|
||||
|
||||
Each element of the returned sequence is a tuple
|
||||
(name, extensions, description)
|
||||
"""
|
||||
formats = [(name, info[0], info[3]) for name, info in
|
||||
_UNPACK_FORMATS.items()]
|
||||
formats.sort()
|
||||
return formats
|
||||
|
||||
def _check_unpack_options(extensions, function, extra_args):
|
||||
"""Checks what gets registered as an unpacker."""
|
||||
# first make sure no other unpacker is registered for this extension
|
||||
existing_extensions = {}
|
||||
for name, info in _UNPACK_FORMATS.items():
|
||||
for ext in info[0]:
|
||||
existing_extensions[ext] = name
|
||||
|
||||
for extension in extensions:
|
||||
if extension in existing_extensions:
|
||||
msg = '%s is already registered for "%s"'
|
||||
raise RegistryError(msg % (extension,
|
||||
existing_extensions[extension]))
|
||||
|
||||
if not isinstance(function, collections.Callable):
|
||||
raise TypeError('The registered function must be a callable')
|
||||
|
||||
|
||||
def register_unpack_format(name, extensions, function, extra_args=None,
|
||||
description=''):
|
||||
"""Registers an unpack format.
|
||||
|
||||
`name` is the name of the format. `extensions` is a list of extensions
|
||||
corresponding to the format.
|
||||
|
||||
`function` is the callable that will be
|
||||
used to unpack archives. The callable will receive archives to unpack.
|
||||
If it's unable to handle an archive, it needs to raise a ReadError
|
||||
exception.
|
||||
|
||||
If provided, `extra_args` is a sequence of
|
||||
(name, value) tuples that will be passed as arguments to the callable.
|
||||
description can be provided to describe the format, and will be returned
|
||||
by the get_unpack_formats() function.
|
||||
"""
|
||||
if extra_args is None:
|
||||
extra_args = []
|
||||
_check_unpack_options(extensions, function, extra_args)
|
||||
_UNPACK_FORMATS[name] = extensions, function, extra_args, description
|
||||
|
||||
def unregister_unpack_format(name):
|
||||
"""Removes the pack format from the registery."""
|
||||
del _UNPACK_FORMATS[name]
|
||||
|
||||
def _ensure_directory(path):
|
||||
"""Ensure that the parent directory of `path` exists"""
|
||||
dirname = os.path.dirname(path)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
def _unpack_zipfile(filename, extract_dir):
|
||||
"""Unpack zip `filename` to `extract_dir`
|
||||
"""
|
||||
try:
|
||||
import zipfile
|
||||
except ImportError:
|
||||
raise ReadError('zlib not supported, cannot unpack this archive.')
|
||||
|
||||
if not zipfile.is_zipfile(filename):
|
||||
raise ReadError("%s is not a zip file" % filename)
|
||||
|
||||
zip = zipfile.ZipFile(filename)
|
||||
try:
|
||||
for info in zip.infolist():
|
||||
name = info.filename
|
||||
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if name.startswith('/') or '..' in name:
|
||||
continue
|
||||
|
||||
target = os.path.join(extract_dir, *name.split('/'))
|
||||
if not target:
|
||||
continue
|
||||
|
||||
_ensure_directory(target)
|
||||
if not name.endswith('/'):
|
||||
# file
|
||||
data = zip.read(info.filename)
|
||||
f = open(target, 'wb')
|
||||
try:
|
||||
f.write(data)
|
||||
finally:
|
||||
f.close()
|
||||
del data
|
||||
finally:
|
||||
zip.close()
|
||||
|
||||
def _unpack_tarfile(filename, extract_dir):
|
||||
"""Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
|
||||
"""
|
||||
try:
|
||||
tarobj = tarfile.open(filename)
|
||||
except tarfile.TarError:
|
||||
raise ReadError(
|
||||
"%s is not a compressed or uncompressed tar file" % filename)
|
||||
try:
|
||||
tarobj.extractall(extract_dir)
|
||||
finally:
|
||||
tarobj.close()
|
||||
|
||||
_UNPACK_FORMATS = {
|
||||
'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
|
||||
'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
|
||||
'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
|
||||
}
|
||||
|
||||
if _BZ2_SUPPORTED:
|
||||
_UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
|
||||
"bzip2'ed tar-file")
|
||||
|
||||
def _find_unpack_format(filename):
|
||||
for name, info in _UNPACK_FORMATS.items():
|
||||
for extension in info[0]:
|
||||
if filename.endswith(extension):
|
||||
return name
|
||||
return None
|
||||
|
||||
def unpack_archive(filename, extract_dir=None, format=None):
|
||||
"""Unpack an archive.
|
||||
|
||||
`filename` is the name of the archive.
|
||||
|
||||
`extract_dir` is the name of the target directory, where the archive
|
||||
is unpacked. If not provided, the current working directory is used.
|
||||
|
||||
`format` is the archive format: one of "zip", "tar", or "gztar". Or any
|
||||
other registered format. If not provided, unpack_archive will use the
|
||||
filename extension and see if an unpacker was registered for that
|
||||
extension.
|
||||
|
||||
In case none is found, a ValueError is raised.
|
||||
"""
|
||||
if extract_dir is None:
|
||||
extract_dir = os.getcwd()
|
||||
|
||||
if format is not None:
|
||||
try:
|
||||
format_info = _UNPACK_FORMATS[format]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown unpack format '{0}'".format(format))
|
||||
|
||||
func = format_info[1]
|
||||
func(filename, extract_dir, **dict(format_info[2]))
|
||||
else:
|
||||
# we need to look at the registered unpackers supported extensions
|
||||
format = _find_unpack_format(filename)
|
||||
if format is None:
|
||||
raise ReadError("Unknown archive format '{0}'".format(filename))
|
||||
|
||||
func = _UNPACK_FORMATS[format][1]
|
||||
kwargs = dict(_UNPACK_FORMATS[format][2])
|
||||
func(filename, extract_dir, **kwargs)
|
||||
@@ -0,0 +1,84 @@
|
||||
[posix_prefix]
|
||||
# Configuration directories. Some of these come straight out of the
|
||||
# configure script. They are for implementing the other variables, not to
|
||||
# be used directly in [resource_locations].
|
||||
confdir = /etc
|
||||
datadir = /usr/share
|
||||
libdir = /usr/lib
|
||||
statedir = /var
|
||||
# User resource directory
|
||||
local = ~/.local/{distribution.name}
|
||||
|
||||
stdlib = {base}/lib/python{py_version_short}
|
||||
platstdlib = {platbase}/lib/python{py_version_short}
|
||||
purelib = {base}/lib/python{py_version_short}/site-packages
|
||||
platlib = {platbase}/lib/python{py_version_short}/site-packages
|
||||
include = {base}/include/python{py_version_short}{abiflags}
|
||||
platinclude = {platbase}/include/python{py_version_short}{abiflags}
|
||||
data = {base}
|
||||
|
||||
[posix_home]
|
||||
stdlib = {base}/lib/python
|
||||
platstdlib = {base}/lib/python
|
||||
purelib = {base}/lib/python
|
||||
platlib = {base}/lib/python
|
||||
include = {base}/include/python
|
||||
platinclude = {base}/include/python
|
||||
scripts = {base}/bin
|
||||
data = {base}
|
||||
|
||||
[nt]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2_home]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[nt_user]
|
||||
stdlib = {userbase}/Python{py_version_nodot}
|
||||
platstdlib = {userbase}/Python{py_version_nodot}
|
||||
purelib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
platlib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
include = {userbase}/Python{py_version_nodot}/Include
|
||||
scripts = {userbase}/Scripts
|
||||
data = {userbase}
|
||||
|
||||
[posix_user]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[osx_framework_user]
|
||||
stdlib = {userbase}/lib/python
|
||||
platstdlib = {userbase}/lib/python
|
||||
purelib = {userbase}/lib/python/site-packages
|
||||
platlib = {userbase}/lib/python/site-packages
|
||||
include = {userbase}/include
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
@@ -0,0 +1,788 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Access to Python's configuration information."""
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from os.path import pardir, realpath
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
|
||||
__all__ = [
|
||||
'get_config_h_filename',
|
||||
'get_config_var',
|
||||
'get_config_vars',
|
||||
'get_makefile_filename',
|
||||
'get_path',
|
||||
'get_path_names',
|
||||
'get_paths',
|
||||
'get_platform',
|
||||
'get_python_version',
|
||||
'get_scheme_names',
|
||||
'parse_config_h',
|
||||
]
|
||||
|
||||
|
||||
def _safe_realpath(path):
|
||||
try:
|
||||
return realpath(path)
|
||||
except OSError:
|
||||
return path
|
||||
|
||||
|
||||
if sys.executable:
|
||||
_PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
|
||||
else:
|
||||
# sys.executable can be empty if argv[0] has been changed and Python is
|
||||
# unable to retrieve the real program name
|
||||
_PROJECT_BASE = _safe_realpath(os.getcwd())
|
||||
|
||||
if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
|
||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
|
||||
# PC/VS7.1
|
||||
if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
|
||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
|
||||
# PC/AMD64
|
||||
if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
|
||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
|
||||
|
||||
|
||||
def is_python_build():
|
||||
for fn in ("Setup.dist", "Setup.local"):
|
||||
if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
|
||||
return True
|
||||
return False
|
||||
|
||||
_PYTHON_BUILD = is_python_build()
|
||||
|
||||
_cfg_read = False
|
||||
|
||||
def _ensure_cfg_read():
|
||||
global _cfg_read
|
||||
if not _cfg_read:
|
||||
from distlib.resources import finder
|
||||
backport_package = __name__.rsplit('.', 1)[0]
|
||||
_finder = finder(backport_package)
|
||||
_cfgfile = _finder.find('sysconfig.cfg')
|
||||
assert _cfgfile, 'sysconfig.cfg exists'
|
||||
with _cfgfile.as_stream() as s:
|
||||
_SCHEMES.readfp(s)
|
||||
if _PYTHON_BUILD:
|
||||
for scheme in ('posix_prefix', 'posix_home'):
|
||||
_SCHEMES.set(scheme, 'include', '{srcdir}/Include')
|
||||
_SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
|
||||
|
||||
_cfg_read = True
|
||||
|
||||
|
||||
_SCHEMES = configparser.RawConfigParser()
|
||||
_VAR_REPL = re.compile(r'\{([^{]*?)\}')
|
||||
|
||||
def _expand_globals(config):
|
||||
_ensure_cfg_read()
|
||||
if config.has_section('globals'):
|
||||
globals = config.items('globals')
|
||||
else:
|
||||
globals = tuple()
|
||||
|
||||
sections = config.sections()
|
||||
for section in sections:
|
||||
if section == 'globals':
|
||||
continue
|
||||
for option, value in globals:
|
||||
if config.has_option(section, option):
|
||||
continue
|
||||
config.set(section, option, value)
|
||||
config.remove_section('globals')
|
||||
|
||||
# now expanding local variables defined in the cfg file
|
||||
#
|
||||
for section in config.sections():
|
||||
variables = dict(config.items(section))
|
||||
|
||||
def _replacer(matchobj):
|
||||
name = matchobj.group(1)
|
||||
if name in variables:
|
||||
return variables[name]
|
||||
return matchobj.group(0)
|
||||
|
||||
for option, value in config.items(section):
|
||||
config.set(section, option, _VAR_REPL.sub(_replacer, value))
|
||||
|
||||
#_expand_globals(_SCHEMES)
|
||||
|
||||
# FIXME don't rely on sys.version here, its format is an implementation detail
|
||||
# of CPython, use sys.version_info or sys.hexversion
|
||||
_PY_VERSION = sys.version.split()[0]
|
||||
_PY_VERSION_SHORT = sys.version[:3]
|
||||
_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2]
|
||||
_PREFIX = os.path.normpath(sys.prefix)
|
||||
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
|
||||
_CONFIG_VARS = None
|
||||
_USER_BASE = None
|
||||
|
||||
|
||||
def _subst_vars(path, local_vars):
|
||||
"""In the string `path`, replace tokens like {some.thing} with the
|
||||
corresponding value from the map `local_vars`.
|
||||
|
||||
If there is no corresponding value, leave the token unchanged.
|
||||
"""
|
||||
def _replacer(matchobj):
|
||||
name = matchobj.group(1)
|
||||
if name in local_vars:
|
||||
return local_vars[name]
|
||||
elif name in os.environ:
|
||||
return os.environ[name]
|
||||
return matchobj.group(0)
|
||||
return _VAR_REPL.sub(_replacer, path)
|
||||
|
||||
|
||||
def _extend_dict(target_dict, other_dict):
|
||||
target_keys = target_dict.keys()
|
||||
for key, value in other_dict.items():
|
||||
if key in target_keys:
|
||||
continue
|
||||
target_dict[key] = value
|
||||
|
||||
|
||||
def _expand_vars(scheme, vars):
|
||||
res = {}
|
||||
if vars is None:
|
||||
vars = {}
|
||||
_extend_dict(vars, get_config_vars())
|
||||
|
||||
for key, value in _SCHEMES.items(scheme):
|
||||
if os.name in ('posix', 'nt'):
|
||||
value = os.path.expanduser(value)
|
||||
res[key] = os.path.normpath(_subst_vars(value, vars))
|
||||
return res
|
||||
|
||||
|
||||
def format_value(value, vars):
|
||||
def _replacer(matchobj):
|
||||
name = matchobj.group(1)
|
||||
if name in vars:
|
||||
return vars[name]
|
||||
return matchobj.group(0)
|
||||
return _VAR_REPL.sub(_replacer, value)
|
||||
|
||||
|
||||
def _get_default_scheme():
|
||||
if os.name == 'posix':
|
||||
# the default scheme for posix is posix_prefix
|
||||
return 'posix_prefix'
|
||||
return os.name
|
||||
|
||||
|
||||
def _getuserbase():
|
||||
env_base = os.environ.get("PYTHONUSERBASE", None)
|
||||
|
||||
def joinuser(*args):
|
||||
return os.path.expanduser(os.path.join(*args))
|
||||
|
||||
# what about 'os2emx', 'riscos' ?
|
||||
if os.name == "nt":
|
||||
base = os.environ.get("APPDATA") or "~"
|
||||
if env_base:
|
||||
return env_base
|
||||
else:
|
||||
return joinuser(base, "Python")
|
||||
|
||||
if sys.platform == "darwin":
|
||||
framework = get_config_var("PYTHONFRAMEWORK")
|
||||
if framework:
|
||||
if env_base:
|
||||
return env_base
|
||||
else:
|
||||
return joinuser("~", "Library", framework, "%d.%d" %
|
||||
sys.version_info[:2])
|
||||
|
||||
if env_base:
|
||||
return env_base
|
||||
else:
|
||||
return joinuser("~", ".local")
|
||||
|
||||
|
||||
def _parse_makefile(filename, vars=None):
|
||||
"""Parse a Makefile-style file.
|
||||
|
||||
A dictionary containing name/value pairs is returned. If an
|
||||
optional dictionary is passed in as the second argument, it is
|
||||
used instead of a new dictionary.
|
||||
"""
|
||||
# Regexes needed for parsing Makefile (and similar syntaxes,
|
||||
# like old-style Setup files).
|
||||
_variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
|
||||
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
|
||||
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
|
||||
|
||||
if vars is None:
|
||||
vars = {}
|
||||
done = {}
|
||||
notdone = {}
|
||||
|
||||
with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('#') or line.strip() == '':
|
||||
continue
|
||||
m = _variable_rx.match(line)
|
||||
if m:
|
||||
n, v = m.group(1, 2)
|
||||
v = v.strip()
|
||||
# `$$' is a literal `$' in make
|
||||
tmpv = v.replace('$$', '')
|
||||
|
||||
if "$" in tmpv:
|
||||
notdone[n] = v
|
||||
else:
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
# insert literal `$'
|
||||
done[n] = v.replace('$$', '$')
|
||||
else:
|
||||
done[n] = v
|
||||
|
||||
# do variable interpolation here
|
||||
variables = list(notdone.keys())
|
||||
|
||||
# Variables with a 'PY_' prefix in the makefile. These need to
|
||||
# be made available without that prefix through sysconfig.
|
||||
# Special care is needed to ensure that variable expansion works, even
|
||||
# if the expansion uses the name without a prefix.
|
||||
renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
|
||||
|
||||
while len(variables) > 0:
|
||||
for name in tuple(variables):
|
||||
value = notdone[name]
|
||||
m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
|
||||
if m is not None:
|
||||
n = m.group(1)
|
||||
found = True
|
||||
if n in done:
|
||||
item = str(done[n])
|
||||
elif n in notdone:
|
||||
# get it on a subsequent round
|
||||
found = False
|
||||
elif n in os.environ:
|
||||
# do it like make: fall back to environment
|
||||
item = os.environ[n]
|
||||
|
||||
elif n in renamed_variables:
|
||||
if (name.startswith('PY_') and
|
||||
name[3:] in renamed_variables):
|
||||
item = ""
|
||||
|
||||
elif 'PY_' + n in notdone:
|
||||
found = False
|
||||
|
||||
else:
|
||||
item = str(done['PY_' + n])
|
||||
|
||||
else:
|
||||
done[n] = item = ""
|
||||
|
||||
if found:
|
||||
after = value[m.end():]
|
||||
value = value[:m.start()] + item + after
|
||||
if "$" in after:
|
||||
notdone[name] = value
|
||||
else:
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
done[name] = value.strip()
|
||||
else:
|
||||
done[name] = value
|
||||
variables.remove(name)
|
||||
|
||||
if (name.startswith('PY_') and
|
||||
name[3:] in renamed_variables):
|
||||
|
||||
name = name[3:]
|
||||
if name not in done:
|
||||
done[name] = value
|
||||
|
||||
else:
|
||||
# bogus variable reference (e.g. "prefix=$/opt/python");
|
||||
# just drop it since we can't deal
|
||||
done[name] = value
|
||||
variables.remove(name)
|
||||
|
||||
# strip spurious spaces
|
||||
for k, v in done.items():
|
||||
if isinstance(v, str):
|
||||
done[k] = v.strip()
|
||||
|
||||
# save the results in the global dictionary
|
||||
vars.update(done)
|
||||
return vars
|
||||
|
||||
|
||||
def get_makefile_filename():
|
||||
"""Return the path of the Makefile."""
|
||||
if _PYTHON_BUILD:
|
||||
return os.path.join(_PROJECT_BASE, "Makefile")
|
||||
if hasattr(sys, 'abiflags'):
|
||||
config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
|
||||
else:
|
||||
config_dir_name = 'config'
|
||||
return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
|
||||
|
||||
|
||||
def _init_posix(vars):
|
||||
"""Initialize the module as appropriate for POSIX systems."""
|
||||
# load the installed Makefile:
|
||||
makefile = get_makefile_filename()
|
||||
try:
|
||||
_parse_makefile(makefile, vars)
|
||||
except IOError as e:
|
||||
msg = "invalid Python installation: unable to open %s" % makefile
|
||||
if hasattr(e, "strerror"):
|
||||
msg = msg + " (%s)" % e.strerror
|
||||
raise IOError(msg)
|
||||
# load the installed pyconfig.h:
|
||||
config_h = get_config_h_filename()
|
||||
try:
|
||||
with open(config_h) as f:
|
||||
parse_config_h(f, vars)
|
||||
except IOError as e:
|
||||
msg = "invalid Python installation: unable to open %s" % config_h
|
||||
if hasattr(e, "strerror"):
|
||||
msg = msg + " (%s)" % e.strerror
|
||||
raise IOError(msg)
|
||||
# On AIX, there are wrong paths to the linker scripts in the Makefile
|
||||
# -- these paths are relative to the Python source, but when installed
|
||||
# the scripts are in another directory.
|
||||
if _PYTHON_BUILD:
|
||||
vars['LDSHARED'] = vars['BLDSHARED']
|
||||
|
||||
|
||||
def _init_non_posix(vars):
|
||||
"""Initialize the module as appropriate for NT"""
|
||||
# set basic install directories
|
||||
vars['LIBDEST'] = get_path('stdlib')
|
||||
vars['BINLIBDEST'] = get_path('platstdlib')
|
||||
vars['INCLUDEPY'] = get_path('include')
|
||||
vars['SO'] = '.pyd'
|
||||
vars['EXE'] = '.exe'
|
||||
vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
|
||||
vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
|
||||
|
||||
#
|
||||
# public APIs
|
||||
#
|
||||
|
||||
|
||||
def parse_config_h(fp, vars=None):
|
||||
"""Parse a config.h-style file.
|
||||
|
||||
A dictionary containing name/value pairs is returned. If an
|
||||
optional dictionary is passed in as the second argument, it is
|
||||
used instead of a new dictionary.
|
||||
"""
|
||||
if vars is None:
|
||||
vars = {}
|
||||
define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
|
||||
undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
|
||||
|
||||
while True:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
m = define_rx.match(line)
|
||||
if m:
|
||||
n, v = m.group(1, 2)
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
vars[n] = v
|
||||
else:
|
||||
m = undef_rx.match(line)
|
||||
if m:
|
||||
vars[m.group(1)] = 0
|
||||
return vars
|
||||
|
||||
|
||||
def get_config_h_filename():
|
||||
"""Return the path of pyconfig.h."""
|
||||
if _PYTHON_BUILD:
|
||||
if os.name == "nt":
|
||||
inc_dir = os.path.join(_PROJECT_BASE, "PC")
|
||||
else:
|
||||
inc_dir = _PROJECT_BASE
|
||||
else:
|
||||
inc_dir = get_path('platinclude')
|
||||
return os.path.join(inc_dir, 'pyconfig.h')
|
||||
|
||||
|
||||
def get_scheme_names():
|
||||
"""Return a tuple containing the schemes names."""
|
||||
return tuple(sorted(_SCHEMES.sections()))
|
||||
|
||||
|
||||
def get_path_names():
|
||||
"""Return a tuple containing the paths names."""
|
||||
# xxx see if we want a static list
|
||||
return _SCHEMES.options('posix_prefix')
|
||||
|
||||
|
||||
def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
|
||||
"""Return a mapping containing an install scheme.
|
||||
|
||||
``scheme`` is the install scheme name. If not provided, it will
|
||||
return the default scheme for the current platform.
|
||||
"""
|
||||
_ensure_cfg_read()
|
||||
if expand:
|
||||
return _expand_vars(scheme, vars)
|
||||
else:
|
||||
return dict(_SCHEMES.items(scheme))
|
||||
|
||||
|
||||
def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
|
||||
"""Return a path corresponding to the scheme.
|
||||
|
||||
``scheme`` is the install scheme name.
|
||||
"""
|
||||
return get_paths(scheme, vars, expand)[name]
|
||||
|
||||
|
||||
def get_config_vars(*args):
|
||||
"""With no arguments, return a dictionary of all configuration
|
||||
variables relevant for the current platform.
|
||||
|
||||
On Unix, this means every variable defined in Python's installed Makefile;
|
||||
On Windows and Mac OS it's a much smaller set.
|
||||
|
||||
With arguments, return a list of values that result from looking up
|
||||
each argument in the configuration variable dictionary.
|
||||
"""
|
||||
global _CONFIG_VARS
|
||||
if _CONFIG_VARS is None:
|
||||
_CONFIG_VARS = {}
|
||||
# Normalized versions of prefix and exec_prefix are handy to have;
|
||||
# in fact, these are the standard versions used most places in the
|
||||
# distutils2 module.
|
||||
_CONFIG_VARS['prefix'] = _PREFIX
|
||||
_CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
|
||||
_CONFIG_VARS['py_version'] = _PY_VERSION
|
||||
_CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
|
||||
_CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
|
||||
_CONFIG_VARS['base'] = _PREFIX
|
||||
_CONFIG_VARS['platbase'] = _EXEC_PREFIX
|
||||
_CONFIG_VARS['projectbase'] = _PROJECT_BASE
|
||||
try:
|
||||
_CONFIG_VARS['abiflags'] = sys.abiflags
|
||||
except AttributeError:
|
||||
# sys.abiflags may not be defined on all platforms.
|
||||
_CONFIG_VARS['abiflags'] = ''
|
||||
|
||||
if os.name in ('nt', 'os2'):
|
||||
_init_non_posix(_CONFIG_VARS)
|
||||
if os.name == 'posix':
|
||||
_init_posix(_CONFIG_VARS)
|
||||
# Setting 'userbase' is done below the call to the
|
||||
# init function to enable using 'get_config_var' in
|
||||
# the init-function.
|
||||
if sys.version >= '2.6':
|
||||
_CONFIG_VARS['userbase'] = _getuserbase()
|
||||
|
||||
if 'srcdir' not in _CONFIG_VARS:
|
||||
_CONFIG_VARS['srcdir'] = _PROJECT_BASE
|
||||
else:
|
||||
_CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir'])
|
||||
|
||||
# Convert srcdir into an absolute path if it appears necessary.
|
||||
# Normally it is relative to the build directory. However, during
|
||||
# testing, for example, we might be running a non-installed python
|
||||
# from a different directory.
|
||||
if _PYTHON_BUILD and os.name == "posix":
|
||||
base = _PROJECT_BASE
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except OSError:
|
||||
cwd = None
|
||||
if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
|
||||
base != cwd):
|
||||
# srcdir is relative and we are not in the same directory
|
||||
# as the executable. Assume executable is in the build
|
||||
# directory and make srcdir absolute.
|
||||
srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
|
||||
_CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
kernel_version = os.uname()[2] # Kernel version (8.4.3)
|
||||
major_version = int(kernel_version.split('.')[0])
|
||||
|
||||
if major_version < 8:
|
||||
# On Mac OS X before 10.4, check if -arch and -isysroot
|
||||
# are in CFLAGS or LDFLAGS and remove them if they are.
|
||||
# This is needed when building extensions on a 10.3 system
|
||||
# using a universal build of python.
|
||||
for key in ('LDFLAGS', 'BASECFLAGS',
|
||||
# a number of derived variables. These need to be
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub('-arch\s+\w+\s', ' ', flags)
|
||||
flags = re.sub('-isysroot [^ \t]*', ' ', flags)
|
||||
_CONFIG_VARS[key] = flags
|
||||
else:
|
||||
# Allow the user to override the architecture flags using
|
||||
# an environment variable.
|
||||
# NOTE: This name was introduced by Apple in OSX 10.5 and
|
||||
# is used by several scripting languages distributed with
|
||||
# that OS release.
|
||||
if 'ARCHFLAGS' in os.environ:
|
||||
arch = os.environ['ARCHFLAGS']
|
||||
for key in ('LDFLAGS', 'BASECFLAGS',
|
||||
# a number of derived variables. These need to be
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub('-arch\s+\w+\s', ' ', flags)
|
||||
flags = flags + ' ' + arch
|
||||
_CONFIG_VARS[key] = flags
|
||||
|
||||
# If we're on OSX 10.5 or later and the user tries to
|
||||
# compiles an extension using an SDK that is not present
|
||||
# on the current machine it is better to not use an SDK
|
||||
# than to fail.
|
||||
#
|
||||
# The major usecase for this is users using a Python.org
|
||||
# binary installer on OSX 10.6: that installer uses
|
||||
# the 10.4u SDK, but that SDK is not installed by default
|
||||
# when you install Xcode.
|
||||
#
|
||||
CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
|
||||
m = re.search('-isysroot\s+(\S+)', CFLAGS)
|
||||
if m is not None:
|
||||
sdk = m.group(1)
|
||||
if not os.path.exists(sdk):
|
||||
for key in ('LDFLAGS', 'BASECFLAGS',
|
||||
# a number of derived variables. These need to be
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
|
||||
_CONFIG_VARS[key] = flags
|
||||
|
||||
if args:
|
||||
vals = []
|
||||
for name in args:
|
||||
vals.append(_CONFIG_VARS.get(name))
|
||||
return vals
|
||||
else:
|
||||
return _CONFIG_VARS
|
||||
|
||||
|
||||
def get_config_var(name):
|
||||
"""Return the value of a single variable using the dictionary returned by
|
||||
'get_config_vars()'.
|
||||
|
||||
Equivalent to get_config_vars().get(name)
|
||||
"""
|
||||
return get_config_vars().get(name)
|
||||
|
||||
|
||||
def get_platform():
|
||||
"""Return a string that identifies the current platform.
|
||||
|
||||
This is used mainly to distinguish platform-specific build directories and
|
||||
platform-specific built distributions. Typically includes the OS name
|
||||
and version and the architecture (as supplied by 'os.uname()'),
|
||||
although the exact information included depends on the OS; eg. for IRIX
|
||||
the architecture isn't particularly important (IRIX only runs on SGI
|
||||
hardware), but for Linux the kernel version isn't particularly
|
||||
important.
|
||||
|
||||
Examples of returned values:
|
||||
linux-i586
|
||||
linux-alpha (?)
|
||||
solaris-2.6-sun4u
|
||||
irix-5.3
|
||||
irix64-6.2
|
||||
|
||||
Windows will return one of:
|
||||
win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
|
||||
win-ia64 (64bit Windows on Itanium)
|
||||
win32 (all others - specifically, sys.platform is returned)
|
||||
|
||||
For other non-POSIX platforms, currently just returns 'sys.platform'.
|
||||
"""
|
||||
if os.name == 'nt':
|
||||
# sniff sys.version for architecture.
|
||||
prefix = " bit ("
|
||||
i = sys.version.find(prefix)
|
||||
if i == -1:
|
||||
return sys.platform
|
||||
j = sys.version.find(")", i)
|
||||
look = sys.version[i+len(prefix):j].lower()
|
||||
if look == 'amd64':
|
||||
return 'win-amd64'
|
||||
if look == 'itanium':
|
||||
return 'win-ia64'
|
||||
return sys.platform
|
||||
|
||||
if os.name != "posix" or not hasattr(os, 'uname'):
|
||||
# XXX what about the architecture? NT is Intel or Alpha,
|
||||
# Mac OS is M68k or PPC, etc.
|
||||
return sys.platform
|
||||
|
||||
# Try to distinguish various flavours of Unix
|
||||
osname, host, release, version, machine = os.uname()
|
||||
|
||||
# Convert the OS name to lowercase, remove '/' characters
|
||||
# (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
|
||||
osname = osname.lower().replace('/', '')
|
||||
machine = machine.replace(' ', '_')
|
||||
machine = machine.replace('/', '-')
|
||||
|
||||
if osname[:5] == "linux":
|
||||
# At least on Linux/Intel, 'machine' is the processor --
|
||||
# i386, etc.
|
||||
# XXX what about Alpha, SPARC, etc?
|
||||
return "%s-%s" % (osname, machine)
|
||||
elif osname[:5] == "sunos":
|
||||
if release[0] >= "5": # SunOS 5 == Solaris 2
|
||||
osname = "solaris"
|
||||
release = "%d.%s" % (int(release[0]) - 3, release[2:])
|
||||
# fall through to standard osname-release-machine representation
|
||||
elif osname[:4] == "irix": # could be "irix64"!
|
||||
return "%s-%s" % (osname, release)
|
||||
elif osname[:3] == "aix":
|
||||
return "%s-%s.%s" % (osname, version, release)
|
||||
elif osname[:6] == "cygwin":
|
||||
osname = "cygwin"
|
||||
rel_re = re.compile(r'[\d.]+')
|
||||
m = rel_re.match(release)
|
||||
if m:
|
||||
release = m.group()
|
||||
elif osname[:6] == "darwin":
|
||||
#
|
||||
# For our purposes, we'll assume that the system version from
|
||||
# distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
|
||||
# to. This makes the compatibility story a bit more sane because the
|
||||
# machine is going to compile and link as if it were
|
||||
# MACOSX_DEPLOYMENT_TARGET.
|
||||
cfgvars = get_config_vars()
|
||||
macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
|
||||
|
||||
if True:
|
||||
# Always calculate the release of the running machine,
|
||||
# needed to determine if we can build fat binaries or not.
|
||||
|
||||
macrelease = macver
|
||||
# Get the system version. Reading this plist is a documented
|
||||
# way to get the system version (see the documentation for
|
||||
# the Gestalt Manager)
|
||||
try:
|
||||
f = open('/System/Library/CoreServices/SystemVersion.plist')
|
||||
except IOError:
|
||||
# We're on a plain darwin box, fall back to the default
|
||||
# behaviour.
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
|
||||
r'<string>(.*?)</string>', f.read())
|
||||
finally:
|
||||
f.close()
|
||||
if m is not None:
|
||||
macrelease = '.'.join(m.group(1).split('.')[:2])
|
||||
# else: fall back to the default behaviour
|
||||
|
||||
if not macver:
|
||||
macver = macrelease
|
||||
|
||||
if macver:
|
||||
release = macver
|
||||
osname = "macosx"
|
||||
|
||||
if ((macrelease + '.') >= '10.4.' and
|
||||
'-arch' in get_config_vars().get('CFLAGS', '').strip()):
|
||||
# The universal build will build fat binaries, but not on
|
||||
# systems before 10.4
|
||||
#
|
||||
# Try to detect 4-way universal builds, those have machine-type
|
||||
# 'universal' instead of 'fat'.
|
||||
|
||||
machine = 'fat'
|
||||
cflags = get_config_vars().get('CFLAGS')
|
||||
|
||||
archs = re.findall('-arch\s+(\S+)', cflags)
|
||||
archs = tuple(sorted(set(archs)))
|
||||
|
||||
if len(archs) == 1:
|
||||
machine = archs[0]
|
||||
elif archs == ('i386', 'ppc'):
|
||||
machine = 'fat'
|
||||
elif archs == ('i386', 'x86_64'):
|
||||
machine = 'intel'
|
||||
elif archs == ('i386', 'ppc', 'x86_64'):
|
||||
machine = 'fat3'
|
||||
elif archs == ('ppc64', 'x86_64'):
|
||||
machine = 'fat64'
|
||||
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
|
||||
machine = 'universal'
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know machine value for archs=%r" % (archs,))
|
||||
|
||||
elif machine == 'i386':
|
||||
# On OSX the machine type returned by uname is always the
|
||||
# 32-bit variant, even if the executable architecture is
|
||||
# the 64-bit variant
|
||||
if sys.maxsize >= 2**32:
|
||||
machine = 'x86_64'
|
||||
|
||||
elif machine in ('PowerPC', 'Power_Macintosh'):
|
||||
# Pick a sane name for the PPC architecture.
|
||||
# See 'i386' case
|
||||
if sys.maxsize >= 2**32:
|
||||
machine = 'ppc64'
|
||||
else:
|
||||
machine = 'ppc'
|
||||
|
||||
return "%s-%s-%s" % (osname, release, machine)
|
||||
|
||||
|
||||
def get_python_version():
|
||||
return _PY_VERSION_SHORT
|
||||
|
||||
|
||||
def _print_dict(title, data):
|
||||
for index, (key, value) in enumerate(sorted(data.items())):
|
||||
if index == 0:
|
||||
print('%s: ' % (title))
|
||||
print('\t%s = "%s"' % (key, value))
|
||||
|
||||
|
||||
def _main():
|
||||
"""Display all information sysconfig detains."""
|
||||
print('Platform: "%s"' % get_platform())
|
||||
print('Python version: "%s"' % get_python_version())
|
||||
print('Current installation scheme: "%s"' % _get_default_scheme())
|
||||
print()
|
||||
_print_dict('Paths', get_paths())
|
||||
print()
|
||||
_print_dict('Variables', get_config_vars())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
||||
File diff suppressed because it is too large
Load Diff
+1064
File diff suppressed because it is too large
Load Diff
+1301
File diff suppressed because it is too large
Load Diff
+480
@@ -0,0 +1,480 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
try:
|
||||
from threading import Thread
|
||||
except ImportError:
|
||||
from dummy_threading import Thread
|
||||
|
||||
from distlib import DistlibException
|
||||
from distlib.compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
|
||||
urlparse, build_opener)
|
||||
from distlib.util import cached_property, zip_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_INDEX = 'https://pypi.python.org/pypi'
|
||||
DEFAULT_REALM = 'pypi'
|
||||
|
||||
class PackageIndex(object):
|
||||
"""
|
||||
This class represents a package index compatible with PyPI, the Python
|
||||
Package Index.
|
||||
"""
|
||||
|
||||
boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$'
|
||||
|
||||
def __init__(self, url=None):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param url: The URL of the index. If not specified, the URL for PyPI is
|
||||
used.
|
||||
"""
|
||||
self.url = url or DEFAULT_INDEX
|
||||
self.read_configuration()
|
||||
scheme, netloc, path, params, query, frag = urlparse(self.url)
|
||||
if params or query or frag or scheme not in ('http', 'https'):
|
||||
raise DistlibException('invalid repository: %s' % self.url)
|
||||
self.password_handler = None
|
||||
self.ssl_verifier = None
|
||||
self.gpg = None
|
||||
self.gpg_home = None
|
||||
with open(os.devnull, 'w') as sink:
|
||||
for s in ('gpg2', 'gpg'):
|
||||
try:
|
||||
rc = subprocess.check_call([s, '--version'], stdout=sink,
|
||||
stderr=sink)
|
||||
if rc == 0:
|
||||
self.gpg = s
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _get_pypirc_command(self):
|
||||
"""
|
||||
Get the distutils command for interacting with PyPI configurations.
|
||||
:return: the command.
|
||||
"""
|
||||
from distutils.core import Distribution
|
||||
from distutils.config import PyPIRCCommand
|
||||
d = Distribution()
|
||||
return PyPIRCCommand(d)
|
||||
|
||||
def read_configuration(self):
|
||||
"""
|
||||
Read the PyPI access configuration as supported by distutils, getting
|
||||
PyPI to do the acutal work. This populates ``username``, ``password``,
|
||||
``realm`` and ``url`` attributes from the configuration.
|
||||
"""
|
||||
# get distutils to do the work
|
||||
c = self._get_pypirc_command()
|
||||
c.repository = self.url
|
||||
cfg = c._read_pypirc()
|
||||
self.username = cfg.get('username')
|
||||
self.password = cfg.get('password')
|
||||
self.realm = cfg.get('realm', 'pypi')
|
||||
self.url = cfg.get('repository', self.url)
|
||||
|
||||
def save_configuration(self):
|
||||
"""
|
||||
Save the PyPI access configuration. You must have set ``username`` and
|
||||
``password`` attributes before calling this method.
|
||||
|
||||
Again, distutils is used to do the actual work.
|
||||
"""
|
||||
self.check_credentials()
|
||||
# get distutils to do the work
|
||||
c = self._get_pypirc_command()
|
||||
c._store_pypirc(self.username, self.password)
|
||||
|
||||
def check_credentials(self):
|
||||
"""
|
||||
Check that ``username`` and ``password`` have been set, and raise an
|
||||
exception if not.
|
||||
"""
|
||||
if self.username is None or self.password is None:
|
||||
raise DistlibException('username and password must be set')
|
||||
pm = HTTPPasswordMgr()
|
||||
_, netloc, _, _, _, _ = urlparse(self.url)
|
||||
pm.add_password(self.realm, netloc, self.username, self.password)
|
||||
self.password_handler = HTTPBasicAuthHandler(pm)
|
||||
|
||||
def register(self, metadata):
|
||||
"""
|
||||
Register a distribution on PyPI, using the provided metadata.
|
||||
|
||||
:param metadata: A :class:`Metadata` instance defining at least a name
|
||||
and version number for the distribution to be
|
||||
registered.
|
||||
:return: The HTTP response received from PyPI upon submission of the
|
||||
request.
|
||||
"""
|
||||
self.check_credentials()
|
||||
metadata.validate()
|
||||
d = metadata.todict()
|
||||
d[':action'] = 'verify'
|
||||
request = self.encode_request(d.items(), [])
|
||||
response = self.send_request(request)
|
||||
d[':action'] = 'submit'
|
||||
request = self.encode_request(d.items(), [])
|
||||
return self.send_request(request)
|
||||
|
||||
def _reader(self, name, stream, outbuf):
|
||||
"""
|
||||
Thread runner for reading lines of from a subprocess into a buffer.
|
||||
|
||||
:param name: The logical name of the stream (used for logging only).
|
||||
:param stream: The stream to read from. This will typically a pipe
|
||||
connected to the output stream of a subprocess.
|
||||
:param outbuf: The list to append the read lines to.
|
||||
"""
|
||||
while True:
|
||||
s = stream.readline()
|
||||
if not s:
|
||||
break
|
||||
s = s.decode('utf-8').rstrip()
|
||||
outbuf.append(s)
|
||||
logger.debug('%s: %s' % (name, s))
|
||||
stream.close()
|
||||
|
||||
def get_sign_command(self, filename, signer, sign_password):
|
||||
"""
|
||||
Return a suitable command for signing a file.
|
||||
|
||||
:param filename: The pathname to the file to be signed.
|
||||
:param signer: The identifier of the signer of the file.
|
||||
:param sign_password: The passphrase for the signer's
|
||||
private key used for signing.
|
||||
:return: The signing command as a list suitable to be
|
||||
passed to :class:`subprocess.Popen`.
|
||||
"""
|
||||
cmd = [self.gpg, '--status-fd', '2', '--no-tty']
|
||||
if self.gpg_home:
|
||||
cmd.extend(['--homedir', self.gpg_home])
|
||||
if sign_password is not None:
|
||||
cmd.extend(['--batch', '--passphrase-fd', '0'])
|
||||
td = tempfile.mkdtemp()
|
||||
sf = os.path.join(td, os.path.basename(filename) + '.asc')
|
||||
cmd.extend(['--detach-sign', '--armor', '--local-user',
|
||||
signer, '--output', sf, filename])
|
||||
logger.debug('invoking: %s', ' '.join(cmd))
|
||||
return cmd, sf
|
||||
|
||||
def run_command(self, cmd, input_data=None):
|
||||
"""
|
||||
Run a command in a child process , passing it any input data specified.
|
||||
|
||||
:param cmd: The command to run.
|
||||
:param input_data: If specified, this must be a byte string containing
|
||||
data to be sent to the child process.
|
||||
:return: A tuple consisting of the subprocess' exit code, a list of
|
||||
lines read from the subprocess' ``stdout``, and a list of
|
||||
lines read from the subprocess' ``stderr``.
|
||||
"""
|
||||
kwargs = {
|
||||
'stdout': subprocess.PIPE,
|
||||
'stderr': subprocess.PIPE,
|
||||
}
|
||||
if input_data is not None:
|
||||
kwargs['stdin'] = subprocess.PIPE
|
||||
stdout = []
|
||||
stderr = []
|
||||
p = subprocess.Popen(cmd, **kwargs)
|
||||
# We don't use communicate() here because we may need to
|
||||
# get clever with interacting with the command
|
||||
t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout))
|
||||
t1.start()
|
||||
t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr))
|
||||
t2.start()
|
||||
if input_data is not None:
|
||||
p.stdin.write(input_data)
|
||||
p.stdin.close()
|
||||
|
||||
p.wait()
|
||||
t1.join()
|
||||
t2.join()
|
||||
return p.returncode, stdout, stderr
|
||||
|
||||
def sign_file(self, filename, signer, sign_password):
|
||||
"""
|
||||
Sign a file.
|
||||
|
||||
:param filename: The pathname to the file to be signed.
|
||||
:param signer: The identifier of the signer of the file.
|
||||
:param sign_password: The passphrase for the signer's
|
||||
private key used for signing.
|
||||
:return: The absolute pathname of the file where the signature is
|
||||
stored.
|
||||
"""
|
||||
cmd, sig_file = self.get_sign_command(filename, signer, sign_password)
|
||||
rc, stdout, stderr = self.run_command(cmd,
|
||||
sign_password.encode('utf-8'))
|
||||
if rc != 0:
|
||||
raise DistlibException('sign command failed with error '
|
||||
'code %s' % rc)
|
||||
return sig_file
|
||||
|
||||
def upload_file(self, metadata, filename, signer=None, sign_password=None,
|
||||
filetype='sdist', pyversion='source'):
|
||||
"""
|
||||
Upload a release file to the index.
|
||||
|
||||
:param metadata: A :class:`Metadata` instance defining at least a name
|
||||
and version number for the file to be uploaded.
|
||||
:param filename: The pathname of the file to be uploaded.
|
||||
:param signer: The identifier of the signer of the file.
|
||||
:param sign_password: The passphrase for the signer's
|
||||
private key used for signing.
|
||||
:param filetype: The type of the file being uploaded. This is the
|
||||
distutils command which produced that file, e.g.
|
||||
``sdist`` or ``bdist_wheel``.
|
||||
:param pyversion: The version of Python which the release relates
|
||||
to. For code compatible with any Python, this would
|
||||
be ``source``, otherwise it would be e.g. ``3.2``.
|
||||
:return: The HTTP response received from PyPI upon submission of the
|
||||
request.
|
||||
"""
|
||||
self.check_credentials()
|
||||
if not os.path.exists(filename):
|
||||
raise DistlibException('not found: %s' % filename)
|
||||
metadata.validate()
|
||||
d = metadata.todict()
|
||||
sig_file = None
|
||||
if signer:
|
||||
if not self.gpg:
|
||||
logger.warning('no signing program available - not signed')
|
||||
else:
|
||||
sig_file = self.sign_file(filename, signer, sign_password)
|
||||
with open(filename, 'rb') as f:
|
||||
file_data = f.read()
|
||||
md5_digest = hashlib.md5(file_data).hexdigest()
|
||||
sha256_digest = hashlib.sha256(file_data).hexdigest()
|
||||
d.update({
|
||||
':action': 'file_upload',
|
||||
'protcol_version': '1',
|
||||
'filetype': filetype,
|
||||
'pyversion': pyversion,
|
||||
'md5_digest': md5_digest,
|
||||
'sha256_digest': sha256_digest,
|
||||
})
|
||||
files = [('content', os.path.basename(filename), file_data)]
|
||||
if sig_file:
|
||||
with open(sig_file, 'rb') as f:
|
||||
sig_data = f.read()
|
||||
files.append(('gpg_signature', os.path.basename(sig_file),
|
||||
sig_data))
|
||||
shutil.rmtree(os.path.dirname(sig_file))
|
||||
request = self.encode_request(d.items(), files)
|
||||
return self.send_request(request)
|
||||
|
||||
def upload_documentation(self, metadata, doc_dir):
|
||||
"""
|
||||
Upload documentation to the index.
|
||||
|
||||
:param metadata: A :class:`Metadata` instance defining at least a name
|
||||
and version number for the documentation to be
|
||||
uploaded.
|
||||
:param doc_dir: The pathname of the directory which contains the
|
||||
documentation. This should be the directory that
|
||||
contains the ``index.html`` for the documentation.
|
||||
:return: The HTTP response received from PyPI upon submission of the
|
||||
request.
|
||||
"""
|
||||
self.check_credentials()
|
||||
if not os.path.isdir(doc_dir):
|
||||
raise DistlibException('not a directory: %r' % doc_dir)
|
||||
fn = os.path.join(doc_dir, 'index.html')
|
||||
if not os.path.exists(fn):
|
||||
raise DistlibException('not found: %r' % fn)
|
||||
metadata.validate()
|
||||
name, version = metadata.name, metadata.version
|
||||
zip_data = zip_dir(doc_dir).getvalue()
|
||||
fields = [(':action', 'doc_upload'),
|
||||
('name', name), ('version', version)]
|
||||
files = [('content', name, zip_data)]
|
||||
request = self.encode_request(fields, files)
|
||||
return self.send_request(request)
|
||||
|
||||
def get_verify_command(self, signature_filename, data_filename):
|
||||
"""
|
||||
Return a suitable command for verifying a file.
|
||||
|
||||
:param signature_filename: The pathname to the file containing the
|
||||
signature.
|
||||
:param data_filename: The pathname to the file containing the
|
||||
signed data.
|
||||
:return: The verifying command as a list suitable to be
|
||||
passed to :class:`subprocess.Popen`.
|
||||
"""
|
||||
cmd = [self.gpg, '--status-fd', '2', '--no-tty']
|
||||
if self.gpg_home:
|
||||
cmd.extend(['--homedir', self.gpg_home])
|
||||
cmd.extend(['--verify', signature_filename, data_filename])
|
||||
logger.debug('invoking: %s', ' '.join(cmd))
|
||||
return cmd
|
||||
|
||||
def verify_signature(self, signature_filename, data_filename):
|
||||
"""
|
||||
Verify a signature for a file.
|
||||
|
||||
:param signature_filename: The pathname to the file containing the
|
||||
signature.
|
||||
:param data_filename: The pathname to the file containing the
|
||||
signed data.
|
||||
:return: True if the signature was verified, else False.
|
||||
"""
|
||||
if not self.gpg:
|
||||
raise DistlibException('verification unavailable because gpg '
|
||||
'unavailable')
|
||||
cmd = self.get_verify_command(signature_filename, data_filename)
|
||||
rc, stdout, stderr = self.run_command(cmd)
|
||||
if rc not in (0, 1):
|
||||
raise DistlibException('verify command failed with error '
|
||||
'code %s' % rc)
|
||||
return rc == 0
|
||||
|
||||
def download_file(self, url, destfile, digest=None, reporthook=None):
|
||||
"""
|
||||
This is a convenience method for downloading a file from an URL.
|
||||
Normally, this will be a file from the index, though currently
|
||||
no check is made for this (i.e. a file can be downloaded from
|
||||
anywhere).
|
||||
|
||||
The method is just like the :func:`urlretrieve` function in the
|
||||
standard library, except that it allows digest computation to be
|
||||
done during download and checking that the downloaded data
|
||||
matched any expected value.
|
||||
|
||||
:param url: The URL of the file to be downloaded (assumed to be
|
||||
available via an HTTP GET request).
|
||||
:param destfile: The pathname where the downloaded file is to be
|
||||
saved.
|
||||
:param digest: If specified, this must be a (hasher, value)
|
||||
tuple, where hasher is the algorithm used (e.g.
|
||||
``'md5'``) and ``value`` is the expected value.
|
||||
:param reporthook: The same as for :func:`urlretrieve` in the
|
||||
standard library.
|
||||
"""
|
||||
if digest is None:
|
||||
digester = None
|
||||
logger.debug('No digest specified')
|
||||
else:
|
||||
if isinstance(digest, (list, tuple)):
|
||||
hasher, digest = digest
|
||||
else:
|
||||
hasher = 'md5'
|
||||
digester = getattr(hashlib, hasher)()
|
||||
logger.debug('Digest specified: %s' % digest)
|
||||
# The following code is equivalent to urlretrieve.
|
||||
# We need to do it this way so that we can compute the
|
||||
# digest of the file as we go.
|
||||
with open(destfile, 'wb') as dfp:
|
||||
# addinfourl is not a context manager on 2.x
|
||||
# so we have to use try/finally
|
||||
sfp = self.send_request(Request(url))
|
||||
try:
|
||||
headers = sfp.info()
|
||||
blocksize = 8192
|
||||
size = -1
|
||||
read = 0
|
||||
blocknum = 0
|
||||
if "content-length" in headers:
|
||||
size = int(headers["Content-Length"])
|
||||
if reporthook:
|
||||
reporthook(blocknum, blocksize, size)
|
||||
while True:
|
||||
block = sfp.read(blocksize)
|
||||
if not block:
|
||||
break
|
||||
read += len(block)
|
||||
dfp.write(block)
|
||||
if digester:
|
||||
digester.update(block)
|
||||
blocknum += 1
|
||||
if reporthook:
|
||||
reporthook(blocknum, blocksize, size)
|
||||
finally:
|
||||
sfp.close()
|
||||
|
||||
# check that we got the whole file, if we can
|
||||
if size >= 0 and read < size:
|
||||
raise DistlibException(
|
||||
'retrieval incomplete: got only %d out of %d bytes'
|
||||
% (read, size))
|
||||
# if we have a digest, it must match.
|
||||
if digester:
|
||||
actual = digester.hexdigest()
|
||||
if digest != actual:
|
||||
raise DistlibException('%s digest mismatch for %s: expected '
|
||||
'%s, got %s' % (hasher, destfile,
|
||||
digest, actual))
|
||||
logger.debug('Digest verified: %s', digest)
|
||||
|
||||
def send_request(self, req):
|
||||
"""
|
||||
Send a standard library :class:`Request` to PyPI and return its
|
||||
response.
|
||||
|
||||
:param req: The request to send.
|
||||
:return: The HTTP response from PyPI (a standard library HTTPResponse).
|
||||
"""
|
||||
handlers = []
|
||||
if self.password_handler:
|
||||
handlers.append(self.password_handler)
|
||||
if self.ssl_verifier:
|
||||
handlers.append(self.ssl_verifier)
|
||||
opener = build_opener(*handlers)
|
||||
return opener.open(req)
|
||||
|
||||
def encode_request(self, fields, files):
|
||||
"""
|
||||
Encode fields and files for posting to an HTTP server.
|
||||
|
||||
:param fields: The fields to send as a list of (fieldname, value)
|
||||
tuples.
|
||||
:param files: The files to send as a list of (fieldname, filename,
|
||||
file_bytes) tuple.
|
||||
"""
|
||||
# Adapted from packaging, which in turn was adapted from
|
||||
# http://code.activestate.com/recipes/146306
|
||||
|
||||
parts = []
|
||||
boundary = self.boundary
|
||||
for k, values in fields:
|
||||
if not isinstance(values, (list, tuple)):
|
||||
values = [values]
|
||||
|
||||
for v in values:
|
||||
parts.extend((
|
||||
b'--' + boundary,
|
||||
('Content-Disposition: form-data; name="%s"' %
|
||||
k).encode('utf-8'),
|
||||
b'',
|
||||
v.encode('utf-8')))
|
||||
for key, filename, value in files:
|
||||
parts.extend((
|
||||
b'--' + boundary,
|
||||
('Content-Disposition: form-data; name="%s"; filename="%s"' %
|
||||
(key, filename)).encode('utf-8'),
|
||||
b'',
|
||||
value))
|
||||
|
||||
parts.extend((b'--' + boundary + b'--', b''))
|
||||
|
||||
body = b'\r\n'.join(parts)
|
||||
ct = b'multipart/form-data; boundary=' + boundary
|
||||
headers = {
|
||||
'Content-type': ct,
|
||||
'Content-length': str(len(body))
|
||||
}
|
||||
return Request(self.url, body, headers)
|
||||
+1187
File diff suppressed because it is too large
Load Diff
+364
@@ -0,0 +1,364 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""
|
||||
Class representing the list of files in a distribution.
|
||||
|
||||
Equivalent to distutils.filelist, but fixes some problems.
|
||||
"""
|
||||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from . import DistlibException
|
||||
from .compat import fsdecode
|
||||
from .util import convert_path
|
||||
|
||||
|
||||
__all__ = ['Manifest']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# a \ followed by some spaces + EOL
|
||||
_COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M)
|
||||
_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
|
||||
|
||||
|
||||
class Manifest(object):
|
||||
"""A list of files built by on exploring the filesystem and filtered by
|
||||
applying various patterns to what we find there.
|
||||
"""
|
||||
|
||||
def __init__(self, base=None):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param base: The base directory to explore under.
|
||||
"""
|
||||
self.base = os.path.abspath(os.path.normpath(base or os.getcwd()))
|
||||
self.prefix = self.base + os.sep
|
||||
self.allfiles = None
|
||||
self.files = set()
|
||||
|
||||
#
|
||||
# Public API
|
||||
#
|
||||
|
||||
def findall(self):
|
||||
"""Find all files under the base and set ``allfiles`` to the absolute
|
||||
pathnames of files found.
|
||||
"""
|
||||
from stat import S_ISREG, S_ISDIR, S_ISLNK
|
||||
|
||||
self.allfiles = allfiles = []
|
||||
root = self.base
|
||||
stack = [root]
|
||||
pop = stack.pop
|
||||
push = stack.append
|
||||
|
||||
while stack:
|
||||
root = pop()
|
||||
names = os.listdir(root)
|
||||
|
||||
for name in names:
|
||||
fullname = os.path.join(root, name)
|
||||
|
||||
# Avoid excess stat calls -- just one will do, thank you!
|
||||
stat = os.stat(fullname)
|
||||
mode = stat.st_mode
|
||||
if S_ISREG(mode):
|
||||
allfiles.append(fsdecode(fullname))
|
||||
elif S_ISDIR(mode) and not S_ISLNK(mode):
|
||||
push(fullname)
|
||||
|
||||
def add(self, item):
|
||||
"""
|
||||
Add a file to the manifest.
|
||||
|
||||
:param item: The pathname to add. This can be relative to the base.
|
||||
"""
|
||||
if not item.startswith(self.prefix):
|
||||
item = os.path.join(self.base, item)
|
||||
self.files.add(os.path.normpath(item))
|
||||
|
||||
def add_many(self, items):
|
||||
"""
|
||||
Add a list of files to the manifest.
|
||||
|
||||
:param items: The pathnames to add. These can be relative to the base.
|
||||
"""
|
||||
for item in items:
|
||||
self.add(item)
|
||||
|
||||
def sorted(self, wantdirs=False):
|
||||
"""
|
||||
Return sorted files in directory order
|
||||
"""
|
||||
|
||||
def add_dir(dirs, d):
|
||||
dirs.add(d)
|
||||
logger.debug('add_dir added %s', d)
|
||||
if d != self.base:
|
||||
parent, _ = os.path.split(d)
|
||||
assert parent not in ('', '/')
|
||||
add_dir(dirs, parent)
|
||||
|
||||
result = set(self.files) # make a copy!
|
||||
if wantdirs:
|
||||
dirs = set()
|
||||
for f in result:
|
||||
add_dir(dirs, os.path.dirname(f))
|
||||
result |= dirs
|
||||
return [os.path.join(*path_tuple) for path_tuple in
|
||||
sorted(os.path.split(path) for path in result)]
|
||||
|
||||
def clear(self):
|
||||
"""Clear all collected files."""
|
||||
self.files = set()
|
||||
self.allfiles = []
|
||||
|
||||
def process_directive(self, directive):
|
||||
"""
|
||||
Process a directive which either adds some files from ``allfiles`` to
|
||||
``files``, or removes some files from ``files``.
|
||||
|
||||
:param directive: The directive to process. This should be in a format
|
||||
compatible with distutils ``MANIFEST.in`` files:
|
||||
|
||||
http://docs.python.org/distutils/sourcedist.html#commands
|
||||
"""
|
||||
# Parse the line: split it up, make sure the right number of words
|
||||
# is there, and return the relevant words. 'action' is always
|
||||
# defined: it's the first word of the line. Which of the other
|
||||
# three are defined depends on the action; it'll be either
|
||||
# patterns, (dir and patterns), or (dirpattern).
|
||||
action, patterns, thedir, dirpattern = self._parse_directive(directive)
|
||||
|
||||
# OK, now we know that the action is valid and we have the
|
||||
# right number of words on the line for that action -- so we
|
||||
# can proceed with minimal error-checking.
|
||||
if action == 'include':
|
||||
for pattern in patterns:
|
||||
if not self._include_pattern(pattern, anchor=True):
|
||||
logger.warning('no files found matching %r', pattern)
|
||||
|
||||
elif action == 'exclude':
|
||||
for pattern in patterns:
|
||||
if not self._exclude_pattern(pattern, anchor=True):
|
||||
logger.warning('no previously-included files '
|
||||
'found matching %r', pattern)
|
||||
|
||||
elif action == 'global-include':
|
||||
for pattern in patterns:
|
||||
if not self._include_pattern(pattern, anchor=False):
|
||||
logger.warning('no files found matching %r '
|
||||
'anywhere in distribution', pattern)
|
||||
|
||||
elif action == 'global-exclude':
|
||||
for pattern in patterns:
|
||||
if not self._exclude_pattern(pattern, anchor=False):
|
||||
logger.warning('no previously-included files '
|
||||
'matching %r found anywhere in '
|
||||
'distribution', pattern)
|
||||
|
||||
elif action == 'recursive-include':
|
||||
for pattern in patterns:
|
||||
if not self._include_pattern(pattern, prefix=thedir):
|
||||
logger.warning('no files found matching %r '
|
||||
'under directory %r', pattern, thedir)
|
||||
|
||||
elif action == 'recursive-exclude':
|
||||
for pattern in patterns:
|
||||
if not self._exclude_pattern(pattern, prefix=thedir):
|
||||
logger.warning('no previously-included files '
|
||||
'matching %r found under directory %r',
|
||||
pattern, thedir)
|
||||
|
||||
elif action == 'graft':
|
||||
if not self._include_pattern(None, prefix=dirpattern):
|
||||
logger.warning('no directories found matching %r',
|
||||
dirpattern)
|
||||
|
||||
elif action == 'prune':
|
||||
if not self._exclude_pattern(None, prefix=dirpattern):
|
||||
logger.warning('no previously-included directories found '
|
||||
'matching %r', dirpattern)
|
||||
else: # pragma: no cover
|
||||
# This should never happen, as it should be caught in
|
||||
# _parse_template_line
|
||||
raise DistlibException(
|
||||
'invalid action %r' % action)
|
||||
|
||||
#
|
||||
# Private API
|
||||
#
|
||||
|
||||
def _parse_directive(self, directive):
|
||||
"""
|
||||
Validate a directive.
|
||||
:param directive: The directive to validate.
|
||||
:return: A tuple of action, patterns, thedir, dir_patterns
|
||||
"""
|
||||
words = directive.split()
|
||||
if len(words) == 1 and words[0] not in ('include', 'exclude',
|
||||
'global-include',
|
||||
'global-exclude',
|
||||
'recursive-include',
|
||||
'recursive-exclude',
|
||||
'graft', 'prune'):
|
||||
# no action given, let's use the default 'include'
|
||||
words.insert(0, 'include')
|
||||
|
||||
action = words[0]
|
||||
patterns = thedir = dir_pattern = None
|
||||
|
||||
if action in ('include', 'exclude',
|
||||
'global-include', 'global-exclude'):
|
||||
if len(words) < 2:
|
||||
raise DistlibException(
|
||||
'%r expects <pattern1> <pattern2> ...' % action)
|
||||
|
||||
patterns = [convert_path(word) for word in words[1:]]
|
||||
|
||||
elif action in ('recursive-include', 'recursive-exclude'):
|
||||
if len(words) < 3:
|
||||
raise DistlibException(
|
||||
'%r expects <dir> <pattern1> <pattern2> ...' % action)
|
||||
|
||||
thedir = convert_path(words[1])
|
||||
patterns = [convert_path(word) for word in words[2:]]
|
||||
|
||||
elif action in ('graft', 'prune'):
|
||||
if len(words) != 2:
|
||||
raise DistlibException(
|
||||
'%r expects a single <dir_pattern>' % action)
|
||||
|
||||
dir_pattern = convert_path(words[1])
|
||||
|
||||
else:
|
||||
raise DistlibException('unknown action %r' % action)
|
||||
|
||||
return action, patterns, thedir, dir_pattern
|
||||
|
||||
def _include_pattern(self, pattern, anchor=True, prefix=None,
|
||||
is_regex=False):
|
||||
"""Select strings (presumably filenames) from 'self.files' that
|
||||
match 'pattern', a Unix-style wildcard (glob) pattern.
|
||||
|
||||
Patterns are not quite the same as implemented by the 'fnmatch'
|
||||
module: '*' and '?' match non-special characters, where "special"
|
||||
is platform-dependent: slash on Unix; colon, slash, and backslash on
|
||||
DOS/Windows; and colon on Mac OS.
|
||||
|
||||
If 'anchor' is true (the default), then the pattern match is more
|
||||
stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
|
||||
'anchor' is false, both of these will match.
|
||||
|
||||
If 'prefix' is supplied, then only filenames starting with 'prefix'
|
||||
(itself a pattern) and ending with 'pattern', with anything in between
|
||||
them, will match. 'anchor' is ignored in this case.
|
||||
|
||||
If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and
|
||||
'pattern' is assumed to be either a string containing a regex or a
|
||||
regex object -- no translation is done, the regex is just compiled
|
||||
and used as-is.
|
||||
|
||||
Selected strings will be added to self.files.
|
||||
|
||||
Return True if files are found.
|
||||
"""
|
||||
# XXX docstring lying about what the special chars are?
|
||||
found = False
|
||||
pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
|
||||
|
||||
# delayed loading of allfiles list
|
||||
if self.allfiles is None:
|
||||
self.findall()
|
||||
|
||||
for name in self.allfiles:
|
||||
if pattern_re.search(name):
|
||||
self.files.add(name)
|
||||
found = True
|
||||
return found
|
||||
|
||||
def _exclude_pattern(self, pattern, anchor=True, prefix=None,
|
||||
is_regex=False):
|
||||
"""Remove strings (presumably filenames) from 'files' that match
|
||||
'pattern'.
|
||||
|
||||
Other parameters are the same as for 'include_pattern()', above.
|
||||
The list 'self.files' is modified in place. Return True if files are
|
||||
found.
|
||||
|
||||
This API is public to allow e.g. exclusion of SCM subdirs, e.g. when
|
||||
packaging source distributions
|
||||
"""
|
||||
found = False
|
||||
pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
|
||||
for f in list(self.files):
|
||||
if pattern_re.search(f):
|
||||
self.files.remove(f)
|
||||
found = True
|
||||
return found
|
||||
|
||||
def _translate_pattern(self, pattern, anchor=True, prefix=None,
|
||||
is_regex=False):
|
||||
"""Translate a shell-like wildcard pattern to a compiled regular
|
||||
expression.
|
||||
|
||||
Return the compiled regex. If 'is_regex' true,
|
||||
then 'pattern' is directly compiled to a regex (if it's a string)
|
||||
or just returned as-is (assumes it's a regex object).
|
||||
"""
|
||||
if is_regex:
|
||||
if isinstance(pattern, str):
|
||||
return re.compile(pattern)
|
||||
else:
|
||||
return pattern
|
||||
|
||||
if pattern:
|
||||
pattern_re = self._glob_to_re(pattern)
|
||||
else:
|
||||
pattern_re = ''
|
||||
|
||||
base = re.escape(os.path.join(self.base, ''))
|
||||
if prefix is not None:
|
||||
# ditch end of pattern character
|
||||
empty_pattern = self._glob_to_re('')
|
||||
prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)]
|
||||
sep = os.sep
|
||||
if os.sep == '\\':
|
||||
sep = r'\\'
|
||||
pattern_re = '^' + base + sep.join((prefix_re,
|
||||
'.*' + pattern_re))
|
||||
else: # no prefix -- respect anchor flag
|
||||
if anchor:
|
||||
pattern_re = '^' + base + pattern_re
|
||||
|
||||
return re.compile(pattern_re)
|
||||
|
||||
def _glob_to_re(self, pattern):
|
||||
"""Translate a shell-like glob pattern to a regular expression.
|
||||
|
||||
Return a string containing the regex. Differs from
|
||||
'fnmatch.translate()' in that '*' does not match "special characters"
|
||||
(which are platform-specific).
|
||||
"""
|
||||
pattern_re = fnmatch.translate(pattern)
|
||||
|
||||
# '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
|
||||
# IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
|
||||
# and by extension they shouldn't match such "special characters" under
|
||||
# any OS. So change all non-escaped dots in the RE to match any
|
||||
# character except the special characters (currently: just os.sep).
|
||||
sep = os.sep
|
||||
if os.sep == '\\':
|
||||
# we're using a regex to manipulate a regex, so we need
|
||||
# to escape the backslash twice
|
||||
sep = r'\\\\'
|
||||
escaped = r'\1[^%s]' % sep
|
||||
pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
|
||||
return pattern_re
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Parser for the environment markers micro-language defined in PEP 345."""
|
||||
|
||||
import ast
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
|
||||
from .compat import python_implementation, string_types
|
||||
from .util import in_venv
|
||||
|
||||
__all__ = ['interpret']
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
"""
|
||||
A limited evaluator for Python expressions.
|
||||
"""
|
||||
|
||||
operators = {
|
||||
'eq': lambda x, y: x == y,
|
||||
'gt': lambda x, y: x > y,
|
||||
'gte': lambda x, y: x >= y,
|
||||
'in': lambda x, y: x in y,
|
||||
'lt': lambda x, y: x < y,
|
||||
'lte': lambda x, y: x <= y,
|
||||
'not': lambda x: not x,
|
||||
'noteq': lambda x, y: x != y,
|
||||
'notin': lambda x, y: x not in y,
|
||||
}
|
||||
|
||||
allowed_values = {
|
||||
'sys_platform': sys.platform,
|
||||
'python_version': '%s.%s' % sys.version_info[:2],
|
||||
# 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_in_venv': str(in_venv()),
|
||||
'platform_release': platform.release(),
|
||||
'platform_version': platform.version(),
|
||||
'platform_machine': platform.machine(),
|
||||
'platform_python_implementation': python_implementation(),
|
||||
}
|
||||
|
||||
def __init__(self, context=None):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param context: If specified, names are looked up in this mapping.
|
||||
"""
|
||||
self.context = context or {}
|
||||
self.source = None
|
||||
|
||||
def get_fragment(self, offset):
|
||||
"""
|
||||
Get the part of the source which is causing a problem.
|
||||
"""
|
||||
fragment_len = 10
|
||||
s = '%r' % (self.source[offset:offset + fragment_len])
|
||||
if offset + fragment_len < len(self.source):
|
||||
s += '...'
|
||||
return s
|
||||
|
||||
def get_handler(self, node_type):
|
||||
"""
|
||||
Get a handler for the specified AST node type.
|
||||
"""
|
||||
return getattr(self, 'do_%s' % node_type, None)
|
||||
|
||||
def evaluate(self, node, filename=None):
|
||||
"""
|
||||
Evaluate a source string or node, using ``filename`` when
|
||||
displaying errors.
|
||||
"""
|
||||
if isinstance(node, string_types):
|
||||
self.source = node
|
||||
kwargs = {'mode': 'eval'}
|
||||
if filename:
|
||||
kwargs['filename'] = filename
|
||||
try:
|
||||
node = ast.parse(node, **kwargs)
|
||||
except SyntaxError as e:
|
||||
s = self.get_fragment(e.offset)
|
||||
raise SyntaxError('syntax error %s' % s)
|
||||
node_type = node.__class__.__name__.lower()
|
||||
handler = self.get_handler(node_type)
|
||||
if handler is None:
|
||||
if self.source is None:
|
||||
s = '(source not available)'
|
||||
else:
|
||||
s = self.get_fragment(node.col_offset)
|
||||
raise SyntaxError("don't know how to evaluate %r %s" % (
|
||||
node_type, s))
|
||||
return handler(node)
|
||||
|
||||
def get_attr_key(self, node):
|
||||
assert isinstance(node, ast.Attribute), 'attribute node expected'
|
||||
return '%s.%s' % (node.value.id, node.attr)
|
||||
|
||||
def do_attribute(self, node):
|
||||
if not isinstance(node.value, ast.Name):
|
||||
valid = False
|
||||
else:
|
||||
key = self.get_attr_key(node)
|
||||
valid = key in self.context or key in self.allowed_values
|
||||
if not valid:
|
||||
raise SyntaxError('invalid expression: %s' % key)
|
||||
if key in self.context:
|
||||
result = self.context[key]
|
||||
else:
|
||||
result = self.allowed_values[key]
|
||||
return result
|
||||
|
||||
def do_boolop(self, node):
|
||||
result = self.evaluate(node.values[0])
|
||||
is_or = node.op.__class__ is ast.Or
|
||||
is_and = node.op.__class__ is ast.And
|
||||
assert is_or or is_and
|
||||
if (is_and and result) or (is_or and not result):
|
||||
for n in node.values[1:]:
|
||||
result = self.evaluate(n)
|
||||
if (is_or and result) or (is_and and not result):
|
||||
break
|
||||
return result
|
||||
|
||||
def do_compare(self, node):
|
||||
def sanity_check(lhsnode, rhsnode):
|
||||
valid = True
|
||||
if isinstance(lhsnode, ast.Str) and isinstance(rhsnode, ast.Str):
|
||||
valid = False
|
||||
#elif (isinstance(lhsnode, ast.Attribute)
|
||||
# and isinstance(rhsnode, ast.Attribute)):
|
||||
# klhs = self.get_attr_key(lhsnode)
|
||||
# krhs = self.get_attr_key(rhsnode)
|
||||
# valid = klhs != krhs
|
||||
if not valid:
|
||||
s = self.get_fragment(node.col_offset)
|
||||
raise SyntaxError('Invalid comparison: %s' % s)
|
||||
|
||||
lhsnode = node.left
|
||||
lhs = self.evaluate(lhsnode)
|
||||
result = True
|
||||
for op, rhsnode in zip(node.ops, node.comparators):
|
||||
sanity_check(lhsnode, rhsnode)
|
||||
op = op.__class__.__name__.lower()
|
||||
if op not in self.operators:
|
||||
raise SyntaxError('unsupported operation: %r' % op)
|
||||
rhs = self.evaluate(rhsnode)
|
||||
result = self.operators[op](lhs, rhs)
|
||||
if not result:
|
||||
break
|
||||
lhs = rhs
|
||||
lhsnode = rhsnode
|
||||
return result
|
||||
|
||||
def do_expression(self, node):
|
||||
return self.evaluate(node.body)
|
||||
|
||||
def do_name(self, node):
|
||||
valid = False
|
||||
if node.id in self.context:
|
||||
valid = True
|
||||
result = self.context[node.id]
|
||||
elif node.id in self.allowed_values:
|
||||
valid = True
|
||||
result = self.allowed_values[node.id]
|
||||
if not valid:
|
||||
raise SyntaxError('invalid expression: %s' % node.id)
|
||||
return result
|
||||
|
||||
def do_str(self, node):
|
||||
return node.s
|
||||
|
||||
|
||||
def interpret(marker, execution_context=None):
|
||||
"""
|
||||
Interpret a marker and return a result depending on environment.
|
||||
|
||||
:param marker: The marker to interpret.
|
||||
:type marker: str
|
||||
:param execution_context: The context used for name lookup.
|
||||
:type execution_context: mapping
|
||||
"""
|
||||
return Evaluator(execution_context).evaluate(marker.strip())
|
||||
+1015
File diff suppressed because it is too large
Load Diff
+351
@@ -0,0 +1,351 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import bisect
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import shutil
|
||||
import sys
|
||||
import types
|
||||
import zipimport
|
||||
|
||||
from . import DistlibException
|
||||
from .util import cached_property, get_cache_base, path_to_cache_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""
|
||||
A class implementing a cache for resources that need to live in the file system
|
||||
e.g. shared libraries.
|
||||
"""
|
||||
|
||||
def __init__(self, base=None):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param base: The base directory where the cache should be located. If
|
||||
not specified, this will be the ``resource-cache``
|
||||
directory under whatever :func:`get_cache_base` returns.
|
||||
"""
|
||||
if base is None:
|
||||
# Use native string to avoid issues on 2.x: see Python #20140.
|
||||
base = os.path.join(get_cache_base(), str('resource-cache'))
|
||||
# we use 'isdir' instead of 'exists', because we want to
|
||||
# fail if there's a file with that name
|
||||
if not os.path.isdir(base):
|
||||
os.makedirs(base)
|
||||
self.base = os.path.abspath(os.path.normpath(base))
|
||||
|
||||
def prefix_to_dir(self, prefix):
|
||||
"""
|
||||
Converts a resource prefix to a directory name in the cache.
|
||||
"""
|
||||
return path_to_cache_dir(prefix)
|
||||
|
||||
def is_stale(self, resource, path):
|
||||
"""
|
||||
Is the cache stale for the given resource?
|
||||
|
||||
:param resource: The :class:`Resource` being cached.
|
||||
:param path: The path of the resource in the cache.
|
||||
:return: True if the cache is stale.
|
||||
"""
|
||||
# Cache invalidation is a hard problem :-)
|
||||
return True
|
||||
|
||||
def get(self, resource):
|
||||
"""
|
||||
Get a resource into the cache,
|
||||
|
||||
:param resource: A :class:`Resource` instance.
|
||||
:return: The pathname of the resource in the cache.
|
||||
"""
|
||||
prefix, path = resource.finder.get_cache_info(resource)
|
||||
if prefix is None:
|
||||
result = path
|
||||
else:
|
||||
result = os.path.join(self.base, self.prefix_to_dir(prefix), path)
|
||||
dirname = os.path.dirname(result)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
if not os.path.exists(result):
|
||||
stale = True
|
||||
else:
|
||||
stale = self.is_stale(resource, path)
|
||||
if stale:
|
||||
# write the bytes of the resource to the cache location
|
||||
with open(result, 'wb') as f:
|
||||
f.write(resource.bytes)
|
||||
return result
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the cache.
|
||||
"""
|
||||
not_removed = []
|
||||
for fn in os.listdir(self.base):
|
||||
fn = os.path.join(self.base, fn)
|
||||
try:
|
||||
if os.path.islink(fn) or os.path.isfile(fn):
|
||||
os.remove(fn)
|
||||
elif os.path.isdir(fn):
|
||||
shutil.rmtree(fn)
|
||||
except Exception:
|
||||
not_removed.append(fn)
|
||||
return not_removed
|
||||
|
||||
cache = Cache()
|
||||
|
||||
|
||||
class ResourceBase(object):
|
||||
def __init__(self, finder, name):
|
||||
self.finder = finder
|
||||
self.name = name
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""
|
||||
A class representing an in-package resource, such as a data file. This is
|
||||
not normally instantiated by user code, but rather by a
|
||||
:class:`ResourceFinder` which manages the resource.
|
||||
"""
|
||||
is_container = False # Backwards compatibility
|
||||
|
||||
def as_stream(self):
|
||||
"""
|
||||
Get the resource as a stream.
|
||||
|
||||
This is not a property to make it obvious that it returns a new stream
|
||||
each time.
|
||||
"""
|
||||
return self.finder.get_stream(self)
|
||||
|
||||
@cached_property
|
||||
def file_path(self):
|
||||
return cache.get(self)
|
||||
|
||||
@cached_property
|
||||
def bytes(self):
|
||||
return self.finder.get_bytes(self)
|
||||
|
||||
@cached_property
|
||||
def size(self):
|
||||
return self.finder.get_size(self)
|
||||
|
||||
|
||||
class ResourceContainer(ResourceBase):
|
||||
is_container = True # Backwards compatibility
|
||||
|
||||
@cached_property
|
||||
def resources(self):
|
||||
return self.finder.get_resources(self)
|
||||
|
||||
|
||||
class ResourceFinder(object):
|
||||
"""
|
||||
Resource finder for file system resources.
|
||||
"""
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.loader = getattr(module, '__loader__', None)
|
||||
self.base = os.path.dirname(getattr(module, '__file__', ''))
|
||||
|
||||
def _adjust_path(self, path):
|
||||
return os.path.realpath(path)
|
||||
|
||||
def _make_path(self, resource_name):
|
||||
parts = resource_name.split('/')
|
||||
parts.insert(0, self.base)
|
||||
result = os.path.join(*parts)
|
||||
return self._adjust_path(result)
|
||||
|
||||
def _find(self, path):
|
||||
return os.path.exists(path)
|
||||
|
||||
def get_cache_info(self, resource):
|
||||
return None, resource.path
|
||||
|
||||
def find(self, resource_name):
|
||||
path = self._make_path(resource_name)
|
||||
if not self._find(path):
|
||||
result = None
|
||||
else:
|
||||
if self._is_directory(path):
|
||||
result = ResourceContainer(self, resource_name)
|
||||
else:
|
||||
result = Resource(self, resource_name)
|
||||
result.path = path
|
||||
return result
|
||||
|
||||
def get_stream(self, resource):
|
||||
return open(resource.path, 'rb')
|
||||
|
||||
def get_bytes(self, resource):
|
||||
with open(resource.path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
def get_size(self, resource):
|
||||
return os.path.getsize(resource.path)
|
||||
|
||||
def get_resources(self, resource):
|
||||
def allowed(f):
|
||||
return f != '__pycache__' and not f.endswith(('.pyc', '.pyo'))
|
||||
return set([f for f in os.listdir(resource.path) if allowed(f)])
|
||||
|
||||
def is_container(self, resource):
|
||||
return self._is_directory(resource.path)
|
||||
|
||||
_is_directory = staticmethod(os.path.isdir)
|
||||
|
||||
|
||||
class ZipResourceFinder(ResourceFinder):
|
||||
"""
|
||||
Resource finder for resources in .zip files.
|
||||
"""
|
||||
def __init__(self, module):
|
||||
super(ZipResourceFinder, self).__init__(module)
|
||||
archive = self.loader.archive
|
||||
self.prefix_len = 1 + len(archive)
|
||||
# PyPy doesn't have a _files attr on zipimporter, and you can't set one
|
||||
if hasattr(self.loader, '_files'):
|
||||
self._files = self.loader._files
|
||||
else:
|
||||
self._files = zipimport._zip_directory_cache[archive]
|
||||
self.index = sorted(self._files)
|
||||
|
||||
def _adjust_path(self, path):
|
||||
return path
|
||||
|
||||
def _find(self, path):
|
||||
path = path[self.prefix_len:]
|
||||
if path in self._files:
|
||||
result = True
|
||||
else:
|
||||
if path and path[-1] != os.sep:
|
||||
path = path + os.sep
|
||||
i = bisect.bisect(self.index, path)
|
||||
try:
|
||||
result = self.index[i].startswith(path)
|
||||
except IndexError:
|
||||
result = False
|
||||
if not result:
|
||||
logger.debug('_find failed: %r %r', path, self.loader.prefix)
|
||||
else:
|
||||
logger.debug('_find worked: %r %r', path, self.loader.prefix)
|
||||
return result
|
||||
|
||||
def get_cache_info(self, resource):
|
||||
prefix = self.loader.archive
|
||||
path = resource.path[1 + len(prefix):]
|
||||
return prefix, path
|
||||
|
||||
def get_bytes(self, resource):
|
||||
return self.loader.get_data(resource.path)
|
||||
|
||||
def get_stream(self, resource):
|
||||
return io.BytesIO(self.get_bytes(resource))
|
||||
|
||||
def get_size(self, resource):
|
||||
path = resource.path[self.prefix_len:]
|
||||
return self._files[path][3]
|
||||
|
||||
def get_resources(self, resource):
|
||||
path = resource.path[self.prefix_len:]
|
||||
if path and path[-1] != os.sep:
|
||||
path += os.sep
|
||||
plen = len(path)
|
||||
result = set()
|
||||
i = bisect.bisect(self.index, path)
|
||||
while i < len(self.index):
|
||||
if not self.index[i].startswith(path):
|
||||
break
|
||||
s = self.index[i][plen:]
|
||||
result.add(s.split(os.sep, 1)[0]) # only immediate children
|
||||
i += 1
|
||||
return result
|
||||
|
||||
def _is_directory(self, path):
|
||||
path = path[self.prefix_len:]
|
||||
if path and path[-1] != os.sep:
|
||||
path += os.sep
|
||||
i = bisect.bisect(self.index, path)
|
||||
try:
|
||||
result = self.index[i].startswith(path)
|
||||
except IndexError:
|
||||
result = False
|
||||
return result
|
||||
|
||||
_finder_registry = {
|
||||
type(None): ResourceFinder,
|
||||
zipimport.zipimporter: ZipResourceFinder
|
||||
}
|
||||
|
||||
try:
|
||||
import _frozen_importlib
|
||||
_finder_registry[_frozen_importlib.SourceFileLoader] = ResourceFinder
|
||||
_finder_registry[_frozen_importlib.FileFinder] = ResourceFinder
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
def register_finder(loader, finder_maker):
|
||||
_finder_registry[type(loader)] = finder_maker
|
||||
|
||||
_finder_cache = {}
|
||||
|
||||
|
||||
def finder(package):
|
||||
"""
|
||||
Return a resource finder for a package.
|
||||
:param package: The name of the package.
|
||||
:return: A :class:`ResourceFinder` instance for the package.
|
||||
"""
|
||||
if package in _finder_cache:
|
||||
result = _finder_cache[package]
|
||||
else:
|
||||
if package not in sys.modules:
|
||||
__import__(package)
|
||||
module = sys.modules[package]
|
||||
path = getattr(module, '__path__', None)
|
||||
if path is None:
|
||||
raise DistlibException('You cannot get a finder for a module, '
|
||||
'only for a package')
|
||||
loader = getattr(module, '__loader__', None)
|
||||
finder_maker = _finder_registry.get(type(loader))
|
||||
if finder_maker is None:
|
||||
raise DistlibException('Unable to locate finder for %r' % package)
|
||||
result = finder_maker(module)
|
||||
_finder_cache[package] = result
|
||||
return result
|
||||
|
||||
|
||||
_dummy_module = types.ModuleType(str('__dummy__'))
|
||||
|
||||
|
||||
def finder_for_path(path):
|
||||
"""
|
||||
Return a resource finder for a path, which should represent a container.
|
||||
|
||||
:param path: The path.
|
||||
:return: A :class:`ResourceFinder` instance for the path.
|
||||
"""
|
||||
result = None
|
||||
# calls any path hooks, gets importer into cache
|
||||
pkgutil.get_importer(path)
|
||||
loader = sys.path_importer_cache.get(path)
|
||||
finder = _finder_registry.get(type(loader))
|
||||
if finder:
|
||||
module = _dummy_module
|
||||
module.__file__ = os.path.join(path, '')
|
||||
module.__loader__ = loader
|
||||
result = finder(module)
|
||||
return result
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
from io import BytesIO
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from .compat import sysconfig, fsencode, detect_encoding, ZipFile
|
||||
from .resources import finder
|
||||
from .util import (FileOperator, get_export_entry, convert_path,
|
||||
get_executable, in_venv)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_DEFAULT_MANIFEST = '''
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0"
|
||||
processorArchitecture="X86"
|
||||
name="%s"
|
||||
type="win32"/>
|
||||
|
||||
<!-- Identify the application security requirements. -->
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>'''.strip()
|
||||
|
||||
# check if Python is called on the first line with this expression
|
||||
FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$')
|
||||
SCRIPT_TEMPLATE = '''# -*- coding: utf-8 -*-
|
||||
if __name__ == '__main__':
|
||||
import sys, re
|
||||
|
||||
def _resolve(module, func):
|
||||
__import__(module)
|
||||
mod = sys.modules[module]
|
||||
parts = func.split('.')
|
||||
result = getattr(mod, parts.pop(0))
|
||||
for p in parts:
|
||||
result = getattr(result, p)
|
||||
return result
|
||||
|
||||
try:
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
|
||||
func = _resolve('%(module)s', '%(func)s')
|
||||
rc = func() # None interpreted as 0
|
||||
except Exception as e: # only supporting Python >= 2.6
|
||||
sys.stderr.write('%%s\\n' %% e)
|
||||
rc = 1
|
||||
sys.exit(rc)
|
||||
'''
|
||||
|
||||
|
||||
class ScriptMaker(object):
|
||||
"""
|
||||
A class to copy or create scripts from source scripts or callable
|
||||
specifications.
|
||||
"""
|
||||
script_template = SCRIPT_TEMPLATE
|
||||
|
||||
executable = None # for shebangs
|
||||
|
||||
def __init__(self, source_dir, target_dir, add_launchers=True,
|
||||
dry_run=False, fileop=None):
|
||||
self.source_dir = source_dir
|
||||
self.target_dir = target_dir
|
||||
self.add_launchers = add_launchers
|
||||
self.force = False
|
||||
self.clobber = False
|
||||
# It only makes sense to set mode bits on POSIX.
|
||||
self.set_mode = (os.name == 'posix')
|
||||
self.variants = set(('', 'X.Y'))
|
||||
self._fileop = fileop or FileOperator(dry_run)
|
||||
|
||||
def _get_alternate_executable(self, executable, options):
|
||||
if options.get('gui', False) and os.name == 'nt':
|
||||
dn, fn = os.path.split(executable)
|
||||
fn = fn.replace('python', 'pythonw')
|
||||
executable = os.path.join(dn, fn)
|
||||
return executable
|
||||
|
||||
def _get_shebang(self, encoding, post_interp=b'', options=None):
|
||||
if self.executable:
|
||||
executable = self.executable
|
||||
elif not sysconfig.is_python_build():
|
||||
executable = get_executable()
|
||||
elif in_venv():
|
||||
executable = os.path.join(sysconfig.get_path('scripts'),
|
||||
'python%s' % sysconfig.get_config_var('EXE'))
|
||||
else:
|
||||
executable = os.path.join(
|
||||
sysconfig.get_config_var('BINDIR'),
|
||||
'python%s%s' % (sysconfig.get_config_var('VERSION'),
|
||||
sysconfig.get_config_var('EXE')))
|
||||
if options:
|
||||
executable = self._get_alternate_executable(executable, options)
|
||||
|
||||
executable = fsencode(executable)
|
||||
shebang = b'#!' + executable + post_interp + b'\n'
|
||||
# Python parser starts to read a script using UTF-8 until
|
||||
# it gets a #coding:xxx cookie. The shebang has to be the
|
||||
# first line of a file, the #coding:xxx cookie cannot be
|
||||
# written before. So the shebang has to be decodable from
|
||||
# UTF-8.
|
||||
try:
|
||||
shebang.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
raise ValueError(
|
||||
'The shebang (%r) is not decodable from utf-8' % shebang)
|
||||
# If the script is encoded to a custom encoding (use a
|
||||
# #coding:xxx cookie), the shebang has to be decodable from
|
||||
# the script encoding too.
|
||||
if encoding != 'utf-8':
|
||||
try:
|
||||
shebang.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
raise ValueError(
|
||||
'The shebang (%r) is not decodable '
|
||||
'from the script encoding (%r)' % (shebang, encoding))
|
||||
return shebang
|
||||
|
||||
def _get_script_text(self, entry):
|
||||
return self.script_template % dict(module=entry.prefix,
|
||||
func=entry.suffix)
|
||||
|
||||
manifest = _DEFAULT_MANIFEST
|
||||
|
||||
def get_manifest(self, exename):
|
||||
base = os.path.basename(exename)
|
||||
return self.manifest % base
|
||||
|
||||
def _write_script(self, names, shebang, script_bytes, filenames, ext):
|
||||
use_launcher = self.add_launchers and os.name == 'nt'
|
||||
linesep = os.linesep.encode('utf-8')
|
||||
if not use_launcher:
|
||||
script_bytes = shebang + linesep + script_bytes
|
||||
else:
|
||||
if ext == 'py':
|
||||
launcher = self._get_launcher('t')
|
||||
else:
|
||||
launcher = self._get_launcher('w')
|
||||
stream = BytesIO()
|
||||
with ZipFile(stream, 'w') as zf:
|
||||
zf.writestr('__main__.py', script_bytes)
|
||||
zip_data = stream.getvalue()
|
||||
script_bytes = launcher + shebang + linesep + zip_data
|
||||
for name in names:
|
||||
outname = os.path.join(self.target_dir, name)
|
||||
if use_launcher:
|
||||
n, e = os.path.splitext(outname)
|
||||
if e.startswith('.py'):
|
||||
outname = n
|
||||
outname = '%s.exe' % outname
|
||||
try:
|
||||
self._fileop.write_binary_file(outname, script_bytes)
|
||||
except Exception:
|
||||
# Failed writing an executable - it might be in use.
|
||||
logger.warning('Failed to write executable - trying to '
|
||||
'use .deleteme logic')
|
||||
dfname = '%s.deleteme' % outname
|
||||
if os.path.exists(dfname):
|
||||
os.remove(dfname) # Not allowed to fail here
|
||||
os.rename(outname, dfname) # nor here
|
||||
self._fileop.write_binary_file(outname, script_bytes)
|
||||
logger.debug('Able to replace executable using '
|
||||
'.deleteme logic')
|
||||
try:
|
||||
os.remove(dfname)
|
||||
except Exception:
|
||||
pass # still in use - ignore error
|
||||
else:
|
||||
if os.name == 'nt' and not outname.endswith('.' + ext):
|
||||
outname = '%s.%s' % (outname, ext)
|
||||
if os.path.exists(outname) and not self.clobber:
|
||||
logger.warning('Skipping existing file %s', outname)
|
||||
continue
|
||||
self._fileop.write_binary_file(outname, script_bytes)
|
||||
if self.set_mode:
|
||||
self._fileop.set_executable_mode([outname])
|
||||
filenames.append(outname)
|
||||
|
||||
def _make_script(self, entry, filenames, options=None):
|
||||
shebang = self._get_shebang('utf-8', options=options)
|
||||
script = self._get_script_text(entry).encode('utf-8')
|
||||
name = entry.name
|
||||
scriptnames = set()
|
||||
if '' in self.variants:
|
||||
scriptnames.add(name)
|
||||
if 'X' in self.variants:
|
||||
scriptnames.add('%s%s' % (name, sys.version[0]))
|
||||
if 'X.Y' in self.variants:
|
||||
scriptnames.add('%s-%s' % (name, sys.version[:3]))
|
||||
if options and options.get('gui', False):
|
||||
ext = 'pyw'
|
||||
else:
|
||||
ext = 'py'
|
||||
self._write_script(scriptnames, shebang, script, filenames, ext)
|
||||
|
||||
def _copy_script(self, script, filenames):
|
||||
adjust = False
|
||||
script = os.path.join(self.source_dir, convert_path(script))
|
||||
outname = os.path.join(self.target_dir, os.path.basename(script))
|
||||
if not self.force and not self._fileop.newer(script, outname):
|
||||
logger.debug('not copying %s (up-to-date)', script)
|
||||
return
|
||||
|
||||
# Always open the file, but ignore failures in dry-run mode --
|
||||
# that way, we'll get accurate feedback if we can read the
|
||||
# script.
|
||||
try:
|
||||
f = open(script, 'rb')
|
||||
except IOError:
|
||||
if not self.dry_run:
|
||||
raise
|
||||
f = None
|
||||
else:
|
||||
encoding, lines = detect_encoding(f.readline)
|
||||
f.seek(0)
|
||||
first_line = f.readline()
|
||||
if not first_line:
|
||||
logger.warning('%s: %s is an empty file (skipping)',
|
||||
self.get_command_name(), script)
|
||||
return
|
||||
|
||||
match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n'))
|
||||
if match:
|
||||
adjust = True
|
||||
post_interp = match.group(1) or b''
|
||||
|
||||
if not adjust:
|
||||
if f:
|
||||
f.close()
|
||||
self._fileop.copy_file(script, outname)
|
||||
if self.set_mode:
|
||||
self._fileop.set_executable_mode([outname])
|
||||
filenames.append(outname)
|
||||
else:
|
||||
logger.info('copying and adjusting %s -> %s', script,
|
||||
self.target_dir)
|
||||
if not self._fileop.dry_run:
|
||||
shebang = self._get_shebang(encoding, post_interp)
|
||||
if b'pythonw' in first_line:
|
||||
ext = 'pyw'
|
||||
else:
|
||||
ext = 'py'
|
||||
n = os.path.basename(outname)
|
||||
self._write_script([n], shebang, f.read(), filenames, ext)
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
@property
|
||||
def dry_run(self):
|
||||
return self._fileop.dry_run
|
||||
|
||||
@dry_run.setter
|
||||
def dry_run(self, value):
|
||||
self._fileop.dry_run = value
|
||||
|
||||
if os.name == 'nt':
|
||||
# Executable launcher support.
|
||||
# Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/
|
||||
|
||||
def _get_launcher(self, kind):
|
||||
if struct.calcsize('P') == 8: # 64-bit
|
||||
bits = '64'
|
||||
else:
|
||||
bits = '32'
|
||||
name = '%s%s.exe' % (kind, bits)
|
||||
# Issue 31: don't hardcode an absolute package name, but
|
||||
# determine it relative to the current package
|
||||
distlib_package = __name__.rsplit('.', 1)[0]
|
||||
result = finder(distlib_package).find(name).bytes
|
||||
return result
|
||||
|
||||
# Public API follows
|
||||
|
||||
def make(self, specification, options=None):
|
||||
"""
|
||||
Make a script.
|
||||
|
||||
:param specification: The specification, which is either a valid export
|
||||
entry specification (to make a script from a
|
||||
callable) or a filename (to make a script by
|
||||
copying from a source location).
|
||||
:param options: A dictionary of options controlling script generation.
|
||||
:return: A list of all absolute pathnames written to.
|
||||
"""
|
||||
filenames = []
|
||||
entry = get_export_entry(specification)
|
||||
if entry is None:
|
||||
self._copy_script(specification, filenames)
|
||||
else:
|
||||
self._make_script(entry, filenames, options=options)
|
||||
return filenames
|
||||
|
||||
def make_multiple(self, specifications, options=None):
|
||||
"""
|
||||
Take a list of specifications and make scripts from them,
|
||||
:param specifications: A list of specifications.
|
||||
:return: A list of all absolute pathnames written to,
|
||||
"""
|
||||
filenames = []
|
||||
for specification in specifications:
|
||||
filenames.extend(self.make(specification, options))
|
||||
return filenames
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+1532
File diff suppressed because it is too large
Load Diff
+698
@@ -0,0 +1,698 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""
|
||||
Implementation of a flexible versioning scheme providing support for PEP-386,
|
||||
distribute-compatible and semantic versioning.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from .compat import string_types
|
||||
|
||||
__all__ = ['NormalizedVersion', 'NormalizedMatcher',
|
||||
'LegacyVersion', 'LegacyMatcher',
|
||||
'SemanticVersion', 'SemanticMatcher',
|
||||
'UnsupportedVersionError', 'get_scheme']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UnsupportedVersionError(ValueError):
|
||||
"""This is an unsupported version."""
|
||||
pass
|
||||
|
||||
|
||||
class Version(object):
|
||||
def __init__(self, s):
|
||||
self._string = s = s.strip()
|
||||
self._parts = parts = self.parse(s)
|
||||
assert isinstance(parts, tuple)
|
||||
assert len(parts) > 0
|
||||
|
||||
def parse(self, s):
|
||||
raise NotImplementedError('please implement in a subclass')
|
||||
|
||||
def _check_compatible(self, other):
|
||||
if type(self) != type(other):
|
||||
raise TypeError('cannot compare %r and %r' % (self, other))
|
||||
|
||||
def __eq__(self, other):
|
||||
self._check_compatible(other)
|
||||
return self._parts == other._parts
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
self._check_compatible(other)
|
||||
return self._parts < other._parts
|
||||
|
||||
def __gt__(self, other):
|
||||
return not (self.__lt__(other) or self.__eq__(other))
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__lt__(other) or self.__eq__(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__gt__(other) or self.__eq__(other)
|
||||
|
||||
# See http://docs.python.org/reference/datamodel#object.__hash__
|
||||
def __hash__(self):
|
||||
return hash(self._parts)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s('%s')" % (self.__class__.__name__, self._string)
|
||||
|
||||
def __str__(self):
|
||||
return self._string
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
raise NotImplementedError('Please implement in subclasses.')
|
||||
|
||||
|
||||
class Matcher(object):
|
||||
version_class = None
|
||||
|
||||
dist_re = re.compile(r"^(\w[\s\w'.-]*)(\((.*)\))?")
|
||||
comp_re = re.compile(r'^(<=|>=|<|>|!=|==|~=)?\s*([^\s,]+)$')
|
||||
num_re = re.compile(r'^\d+(\.\d+)*$')
|
||||
|
||||
# value is either a callable or the name of a method
|
||||
_operators = {
|
||||
'<': lambda v, c, p: v < c,
|
||||
'>': lambda v, c, p: v > c,
|
||||
'<=': lambda v, c, p: v == c or v < c,
|
||||
'>=': lambda v, c, p: v == c or v > c,
|
||||
'==': lambda v, c, p: v == c,
|
||||
# by default, compatible => >=.
|
||||
'~=': lambda v, c, p: v == c or v > c,
|
||||
'!=': lambda v, c, p: v != c,
|
||||
}
|
||||
|
||||
def __init__(self, s):
|
||||
if self.version_class is None:
|
||||
raise ValueError('Please specify a version class')
|
||||
self._string = s = s.strip()
|
||||
m = self.dist_re.match(s)
|
||||
if not m:
|
||||
raise ValueError('Not valid: %r' % s)
|
||||
groups = m.groups('')
|
||||
self.name = groups[0].strip()
|
||||
self.key = self.name.lower() # for case-insensitive comparisons
|
||||
clist = []
|
||||
if groups[2]:
|
||||
constraints = [c.strip() for c in groups[2].split(',')]
|
||||
for c in constraints:
|
||||
m = self.comp_re.match(c)
|
||||
if not m:
|
||||
raise ValueError('Invalid %r in %r' % (c, s))
|
||||
groups = m.groups()
|
||||
op = groups[0] or '~='
|
||||
s = groups[1]
|
||||
if s.endswith('.*'):
|
||||
if op not in ('==', '!='):
|
||||
raise ValueError('\'.*\' not allowed for '
|
||||
'%r constraints' % op)
|
||||
# Could be a partial version (e.g. for '2.*') which
|
||||
# won't parse as a version, so keep it as a string
|
||||
vn, prefix = s[:-2], True
|
||||
if not self.num_re.match(vn):
|
||||
# Just to check that vn is a valid version
|
||||
self.version_class(vn)
|
||||
else:
|
||||
# Should parse as a version, so we can create an
|
||||
# instance for the comparison
|
||||
vn, prefix = self.version_class(s), False
|
||||
clist.append((op, vn, prefix))
|
||||
self._parts = tuple(clist)
|
||||
|
||||
def match(self, version):
|
||||
"""
|
||||
Check if the provided version matches the constraints.
|
||||
|
||||
:param version: The version to match against this instance.
|
||||
:type version: Strring or :class:`Version` instance.
|
||||
"""
|
||||
if isinstance(version, string_types):
|
||||
version = self.version_class(version)
|
||||
for operator, constraint, prefix in self._parts:
|
||||
f = self._operators.get(operator)
|
||||
if isinstance(f, string_types):
|
||||
f = getattr(self, f)
|
||||
if not f:
|
||||
msg = ('%r not implemented '
|
||||
'for %s' % (operator, self.__class__.__name__))
|
||||
raise NotImplementedError(msg)
|
||||
if not f(version, constraint, prefix):
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def exact_version(self):
|
||||
result = None
|
||||
if len(self._parts) == 1 and self._parts[0][0] == '==':
|
||||
result = self._parts[0][1]
|
||||
return result
|
||||
|
||||
def _check_compatible(self, other):
|
||||
if type(self) != type(other) or self.name != other.name:
|
||||
raise TypeError('cannot compare %s and %s' % (self, other))
|
||||
|
||||
def __eq__(self, other):
|
||||
self._check_compatible(other)
|
||||
return self.key == other.key and self._parts == other._parts
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
# See http://docs.python.org/reference/datamodel#object.__hash__
|
||||
def __hash__(self):
|
||||
return hash(self.key) + hash(self._parts)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r)" % (self.__class__.__name__, self._string)
|
||||
|
||||
def __str__(self):
|
||||
return self._string
|
||||
|
||||
|
||||
PEP426_VERSION_RE = re.compile(r'^(\d+\.\d+(\.\d+)*)((a|b|c|rc)(\d+))?'
|
||||
r'(\.(post)(\d+))?(\.(dev)(\d+))?'
|
||||
r'(-(\d+(\.\d+)?))?$')
|
||||
|
||||
|
||||
def _pep426_key(s):
|
||||
s = s.strip()
|
||||
m = PEP426_VERSION_RE.match(s)
|
||||
if not m:
|
||||
raise UnsupportedVersionError('Not a valid version: %s' % s)
|
||||
groups = m.groups()
|
||||
nums = tuple(int(v) for v in groups[0].split('.'))
|
||||
while len(nums) > 1 and nums[-1] == 0:
|
||||
nums = nums[:-1]
|
||||
|
||||
pre = groups[3:5]
|
||||
post = groups[6:8]
|
||||
dev = groups[9:11]
|
||||
local = groups[12]
|
||||
if pre == (None, None):
|
||||
pre = ()
|
||||
else:
|
||||
pre = pre[0], int(pre[1])
|
||||
if post == (None, None):
|
||||
post = ()
|
||||
else:
|
||||
post = post[0], int(post[1])
|
||||
if dev == (None, None):
|
||||
dev = ()
|
||||
else:
|
||||
dev = dev[0], int(dev[1])
|
||||
if local is None:
|
||||
local = ()
|
||||
else:
|
||||
local = tuple([int(s) for s in local.split('.')])
|
||||
if not pre:
|
||||
# either before pre-release, or final release and after
|
||||
if not post and dev:
|
||||
# before pre-release
|
||||
pre = ('a', -1) # to sort before a0
|
||||
else:
|
||||
pre = ('z',) # to sort after all pre-releases
|
||||
# now look at the state of post and dev.
|
||||
if not post:
|
||||
post = ('_',) # sort before 'a'
|
||||
if not dev:
|
||||
dev = ('final',)
|
||||
|
||||
#print('%s -> %s' % (s, m.groups()))
|
||||
return nums, pre, post, dev, local
|
||||
|
||||
|
||||
_normalized_key = _pep426_key
|
||||
|
||||
|
||||
class NormalizedVersion(Version):
|
||||
"""A rational version.
|
||||
|
||||
Good:
|
||||
1.2 # equivalent to "1.2.0"
|
||||
1.2.0
|
||||
1.2a1
|
||||
1.2.3a2
|
||||
1.2.3b1
|
||||
1.2.3c1
|
||||
1.2.3.4
|
||||
TODO: fill this out
|
||||
|
||||
Bad:
|
||||
1 # mininum two numbers
|
||||
1.2a # release level must have a release serial
|
||||
1.2.3b
|
||||
"""
|
||||
def parse(self, s):
|
||||
result = _normalized_key(s)
|
||||
# _normalized_key loses trailing zeroes in the release
|
||||
# clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0
|
||||
# However, PEP 440 prefix matching needs it: for example,
|
||||
# (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0).
|
||||
m = PEP426_VERSION_RE.match(s) # must succeed
|
||||
groups = m.groups()
|
||||
self._release_clause = tuple(int(v) for v in groups[0].split('.'))
|
||||
return result
|
||||
|
||||
PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev'])
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return any(t[0] in self.PREREL_TAGS for t in self._parts if t)
|
||||
|
||||
|
||||
def _match_prefix(x, y):
|
||||
x = str(x)
|
||||
y = str(y)
|
||||
if x == y:
|
||||
return True
|
||||
if not x.startswith(y):
|
||||
return False
|
||||
n = len(y)
|
||||
return x[n] == '.'
|
||||
|
||||
|
||||
class NormalizedMatcher(Matcher):
|
||||
version_class = NormalizedVersion
|
||||
|
||||
# value is either a callable or the name of a method
|
||||
_operators = {
|
||||
'~=': '_match_compatible',
|
||||
'<': '_match_lt',
|
||||
'>': '_match_gt',
|
||||
'<=': '_match_le',
|
||||
'>=': '_match_ge',
|
||||
'==': '_match_eq',
|
||||
'!=': '_match_ne',
|
||||
}
|
||||
|
||||
def _match_lt(self, version, constraint, prefix):
|
||||
if version >= constraint:
|
||||
return False
|
||||
release_clause = constraint._release_clause
|
||||
pfx = '.'.join([str(i) for i in release_clause])
|
||||
return not _match_prefix(version, pfx)
|
||||
|
||||
def _match_gt(self, version, constraint, prefix):
|
||||
if version <= constraint:
|
||||
return False
|
||||
release_clause = constraint._release_clause
|
||||
pfx = '.'.join([str(i) for i in release_clause])
|
||||
return not _match_prefix(version, pfx)
|
||||
|
||||
def _match_le(self, version, constraint, prefix):
|
||||
return version <= constraint
|
||||
|
||||
def _match_ge(self, version, constraint, prefix):
|
||||
return version >= constraint
|
||||
|
||||
def _match_eq(self, version, constraint, prefix):
|
||||
if not prefix:
|
||||
result = (version == constraint)
|
||||
else:
|
||||
result = _match_prefix(version, constraint)
|
||||
return result
|
||||
|
||||
def _match_ne(self, version, constraint, prefix):
|
||||
if not prefix:
|
||||
result = (version != constraint)
|
||||
else:
|
||||
result = not _match_prefix(version, constraint)
|
||||
return result
|
||||
|
||||
def _match_compatible(self, version, constraint, prefix):
|
||||
if version == constraint:
|
||||
return True
|
||||
if version < constraint:
|
||||
return False
|
||||
release_clause = constraint._release_clause
|
||||
if len(release_clause) > 1:
|
||||
release_clause = release_clause[:-1]
|
||||
pfx = '.'.join([str(i) for i in release_clause])
|
||||
return _match_prefix(version, pfx)
|
||||
|
||||
_REPLACEMENTS = (
|
||||
(re.compile('[.+-]$'), ''), # remove trailing puncts
|
||||
(re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start
|
||||
(re.compile('^[.-]'), ''), # remove leading puncts
|
||||
(re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses
|
||||
(re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion)
|
||||
(re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion)
|
||||
(re.compile('[.]{2,}'), '.'), # multiple runs of '.'
|
||||
(re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha
|
||||
(re.compile(r'\b(pre-alpha|prealpha)\b'),
|
||||
'pre.alpha'), # standardise
|
||||
(re.compile(r'\(beta\)$'), 'beta'), # remove parentheses
|
||||
)
|
||||
|
||||
_SUFFIX_REPLACEMENTS = (
|
||||
(re.compile('^[:~._+-]+'), ''), # remove leading puncts
|
||||
(re.compile('[,*")([\]]'), ''), # remove unwanted chars
|
||||
(re.compile('[~:+_ -]'), '.'), # replace illegal chars
|
||||
(re.compile('[.]{2,}'), '.'), # multiple runs of '.'
|
||||
(re.compile(r'\.$'), ''), # trailing '.'
|
||||
)
|
||||
|
||||
_NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)')
|
||||
|
||||
|
||||
def _suggest_semantic_version(s):
|
||||
"""
|
||||
Try to suggest a semantic form for a version for which
|
||||
_suggest_normalized_version couldn't come up with anything.
|
||||
"""
|
||||
result = s.strip().lower()
|
||||
for pat, repl in _REPLACEMENTS:
|
||||
result = pat.sub(repl, result)
|
||||
if not result:
|
||||
result = '0.0.0'
|
||||
|
||||
# Now look for numeric prefix, and separate it out from
|
||||
# the rest.
|
||||
#import pdb; pdb.set_trace()
|
||||
m = _NUMERIC_PREFIX.match(result)
|
||||
if not m:
|
||||
prefix = '0.0.0'
|
||||
suffix = result
|
||||
else:
|
||||
prefix = m.groups()[0].split('.')
|
||||
prefix = [int(i) for i in prefix]
|
||||
while len(prefix) < 3:
|
||||
prefix.append(0)
|
||||
if len(prefix) == 3:
|
||||
suffix = result[m.end():]
|
||||
else:
|
||||
suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():]
|
||||
prefix = prefix[:3]
|
||||
prefix = '.'.join([str(i) for i in prefix])
|
||||
suffix = suffix.strip()
|
||||
if suffix:
|
||||
#import pdb; pdb.set_trace()
|
||||
# massage the suffix.
|
||||
for pat, repl in _SUFFIX_REPLACEMENTS:
|
||||
suffix = pat.sub(repl, suffix)
|
||||
|
||||
if not suffix:
|
||||
result = prefix
|
||||
else:
|
||||
sep = '-' if 'dev' in suffix else '+'
|
||||
result = prefix + sep + suffix
|
||||
if not is_semver(result):
|
||||
result = None
|
||||
return result
|
||||
|
||||
|
||||
def _suggest_normalized_version(s):
|
||||
"""Suggest a normalized version close to the given version string.
|
||||
|
||||
If you have a version string that isn't rational (i.e. NormalizedVersion
|
||||
doesn't like it) then you might be able to get an equivalent (or close)
|
||||
rational version from this function.
|
||||
|
||||
This does a number of simple normalizations to the given string, based
|
||||
on observation of versions currently in use on PyPI. Given a dump of
|
||||
those version during PyCon 2009, 4287 of them:
|
||||
- 2312 (53.93%) match NormalizedVersion without change
|
||||
with the automatic suggestion
|
||||
- 3474 (81.04%) match when using this suggestion method
|
||||
|
||||
@param s {str} An irrational version string.
|
||||
@returns A rational version string, or None, if couldn't determine one.
|
||||
"""
|
||||
try:
|
||||
_normalized_key(s)
|
||||
return s # already rational
|
||||
except UnsupportedVersionError:
|
||||
pass
|
||||
|
||||
rs = s.lower()
|
||||
|
||||
# part of this could use maketrans
|
||||
for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'),
|
||||
('beta', 'b'), ('rc', 'c'), ('-final', ''),
|
||||
('-pre', 'c'),
|
||||
('-release', ''), ('.release', ''), ('-stable', ''),
|
||||
('+', '.'), ('_', '.'), (' ', ''), ('.final', ''),
|
||||
('final', '')):
|
||||
rs = rs.replace(orig, repl)
|
||||
|
||||
# if something ends with dev or pre, we add a 0
|
||||
rs = re.sub(r"pre$", r"pre0", rs)
|
||||
rs = re.sub(r"dev$", r"dev0", rs)
|
||||
|
||||
# if we have something like "b-2" or "a.2" at the end of the
|
||||
# version, that is pobably beta, alpha, etc
|
||||
# let's remove the dash or dot
|
||||
rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs)
|
||||
|
||||
# 1.0-dev-r371 -> 1.0.dev371
|
||||
# 0.1-dev-r79 -> 0.1.dev79
|
||||
rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs)
|
||||
|
||||
# Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1
|
||||
rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs)
|
||||
|
||||
# Clean: v0.3, v1.0
|
||||
if rs.startswith('v'):
|
||||
rs = rs[1:]
|
||||
|
||||
# Clean leading '0's on numbers.
|
||||
#TODO: unintended side-effect on, e.g., "2003.05.09"
|
||||
# PyPI stats: 77 (~2%) better
|
||||
rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
|
||||
|
||||
# Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers
|
||||
# zero.
|
||||
# PyPI stats: 245 (7.56%) better
|
||||
rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs)
|
||||
|
||||
# the 'dev-rNNN' tag is a dev tag
|
||||
rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs)
|
||||
|
||||
# clean the - when used as a pre delimiter
|
||||
rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs)
|
||||
|
||||
# a terminal "dev" or "devel" can be changed into ".dev0"
|
||||
rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs)
|
||||
|
||||
# a terminal "dev" can be changed into ".dev0"
|
||||
rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs)
|
||||
|
||||
# a terminal "final" or "stable" can be removed
|
||||
rs = re.sub(r"(final|stable)$", "", rs)
|
||||
|
||||
# The 'r' and the '-' tags are post release tags
|
||||
# 0.4a1.r10 -> 0.4a1.post10
|
||||
# 0.9.33-17222 -> 0.9.33.post17222
|
||||
# 0.9.33-r17222 -> 0.9.33.post17222
|
||||
rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs)
|
||||
|
||||
# Clean 'r' instead of 'dev' usage:
|
||||
# 0.9.33+r17222 -> 0.9.33.dev17222
|
||||
# 1.0dev123 -> 1.0.dev123
|
||||
# 1.0.git123 -> 1.0.dev123
|
||||
# 1.0.bzr123 -> 1.0.dev123
|
||||
# 0.1a0dev.123 -> 0.1a0.dev123
|
||||
# PyPI stats: ~150 (~4%) better
|
||||
rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs)
|
||||
|
||||
# Clean '.pre' (normalized from '-pre' above) instead of 'c' usage:
|
||||
# 0.2.pre1 -> 0.2c1
|
||||
# 0.2-c1 -> 0.2c1
|
||||
# 1.0preview123 -> 1.0c123
|
||||
# PyPI stats: ~21 (0.62%) better
|
||||
rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
|
||||
|
||||
# Tcl/Tk uses "px" for their post release markers
|
||||
rs = re.sub(r"p(\d+)$", r".post\1", rs)
|
||||
|
||||
try:
|
||||
_normalized_key(rs)
|
||||
except UnsupportedVersionError:
|
||||
rs = None
|
||||
return rs
|
||||
|
||||
#
|
||||
# Legacy version processing (distribute-compatible)
|
||||
#
|
||||
|
||||
_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I)
|
||||
_VERSION_REPLACE = {
|
||||
'pre': 'c',
|
||||
'preview': 'c',
|
||||
'-': 'final-',
|
||||
'rc': 'c',
|
||||
'dev': '@',
|
||||
'': None,
|
||||
'.': None,
|
||||
}
|
||||
|
||||
|
||||
def _legacy_key(s):
|
||||
def get_parts(s):
|
||||
result = []
|
||||
for p in _VERSION_PART.split(s.lower()):
|
||||
p = _VERSION_REPLACE.get(p, p)
|
||||
if p:
|
||||
if '0' <= p[:1] <= '9':
|
||||
p = p.zfill(8)
|
||||
else:
|
||||
p = '*' + p
|
||||
result.append(p)
|
||||
result.append('*final')
|
||||
return result
|
||||
|
||||
result = []
|
||||
for p in get_parts(s):
|
||||
if p.startswith('*'):
|
||||
if p < '*final':
|
||||
while result and result[-1] == '*final-':
|
||||
result.pop()
|
||||
while result and result[-1] == '00000000':
|
||||
result.pop()
|
||||
result.append(p)
|
||||
return tuple(result)
|
||||
|
||||
|
||||
class LegacyVersion(Version):
|
||||
def parse(self, s):
|
||||
return _legacy_key(s)
|
||||
|
||||
PREREL_TAGS = set(
|
||||
['*a', '*alpha', '*b', '*beta', '*c', '*rc', '*r', '*@', '*pre']
|
||||
)
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return any(x in self.PREREL_TAGS for x in self._parts)
|
||||
|
||||
|
||||
class LegacyMatcher(Matcher):
|
||||
version_class = LegacyVersion
|
||||
|
||||
_operators = dict(Matcher._operators)
|
||||
_operators['~='] = '_match_compatible'
|
||||
|
||||
numeric_re = re.compile('^(\d+(\.\d+)*)')
|
||||
|
||||
def _match_compatible(self, version, constraint, prefix):
|
||||
if version < constraint:
|
||||
return False
|
||||
m = self.numeric_re.match(str(constraint))
|
||||
if not m:
|
||||
logger.warning('Cannot compute compatible match for version %s '
|
||||
' and constraint %s', version, constraint)
|
||||
return True
|
||||
s = m.groups()[0]
|
||||
if '.' in s:
|
||||
s = s.rsplit('.', 1)[0]
|
||||
return _match_prefix(version, s)
|
||||
|
||||
#
|
||||
# Semantic versioning
|
||||
#
|
||||
|
||||
_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)'
|
||||
r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?'
|
||||
r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I)
|
||||
|
||||
|
||||
def is_semver(s):
|
||||
return _SEMVER_RE.match(s)
|
||||
|
||||
|
||||
def _semantic_key(s):
|
||||
def make_tuple(s, absent):
|
||||
if s is None:
|
||||
result = (absent,)
|
||||
else:
|
||||
parts = s[1:].split('.')
|
||||
# We can't compare ints and strings on Python 3, so fudge it
|
||||
# by zero-filling numeric values so simulate a numeric comparison
|
||||
result = tuple([p.zfill(8) if p.isdigit() else p for p in parts])
|
||||
return result
|
||||
|
||||
m = is_semver(s)
|
||||
if not m:
|
||||
raise UnsupportedVersionError(s)
|
||||
groups = m.groups()
|
||||
major, minor, patch = [int(i) for i in groups[:3]]
|
||||
# choose the '|' and '*' so that versions sort correctly
|
||||
pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*')
|
||||
return (major, minor, patch), pre, build
|
||||
|
||||
|
||||
class SemanticVersion(Version):
|
||||
def parse(self, s):
|
||||
return _semantic_key(s)
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return self._parts[1][0] != '|'
|
||||
|
||||
|
||||
class SemanticMatcher(Matcher):
|
||||
version_class = SemanticVersion
|
||||
|
||||
|
||||
class VersionScheme(object):
|
||||
def __init__(self, key, matcher, suggester=None):
|
||||
self.key = key
|
||||
self.matcher = matcher
|
||||
self.suggester = suggester
|
||||
|
||||
def is_valid_version(self, s):
|
||||
try:
|
||||
self.matcher.version_class(s)
|
||||
result = True
|
||||
except UnsupportedVersionError:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def is_valid_matcher(self, s):
|
||||
try:
|
||||
self.matcher(s)
|
||||
result = True
|
||||
except UnsupportedVersionError:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def is_valid_constraint_list(self, s):
|
||||
"""
|
||||
Used for processing some metadata fields
|
||||
"""
|
||||
return self.is_valid_matcher('dummy_name (%s)' % s)
|
||||
|
||||
def suggest(self, s):
|
||||
if self.suggester is None:
|
||||
result = None
|
||||
else:
|
||||
result = self.suggester(s)
|
||||
return result
|
||||
|
||||
_SCHEMES = {
|
||||
'normalized': VersionScheme(_normalized_key, NormalizedMatcher,
|
||||
_suggest_normalized_version),
|
||||
'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s),
|
||||
'semantic': VersionScheme(_semantic_key, SemanticMatcher,
|
||||
_suggest_semantic_version),
|
||||
}
|
||||
|
||||
_SCHEMES['default'] = _SCHEMES['normalized']
|
||||
|
||||
|
||||
def get_scheme(name):
|
||||
if name not in _SCHEMES:
|
||||
raise ValueError('unknown scheme name: %r' % name)
|
||||
return _SCHEMES[name]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+723
@@ -0,0 +1,723 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import datetime
|
||||
import distutils.util
|
||||
from email import message_from_file
|
||||
import hashlib
|
||||
import imp
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from . import __version__, DistlibException
|
||||
from .compat import sysconfig, ZipFile, fsdecode, text_type, filter
|
||||
from .database import InstalledDistribution
|
||||
from .metadata import Metadata, METADATA_FILENAME
|
||||
from .util import (FileOperator, convert_path, CSVReader, CSVWriter,
|
||||
cached_property, get_cache_base, read_exports)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
IMP_PREFIX = 'pp'
|
||||
elif sys.platform.startswith('java'):
|
||||
IMP_PREFIX = 'jy'
|
||||
elif sys.platform == 'cli':
|
||||
IMP_PREFIX = 'ip'
|
||||
else:
|
||||
IMP_PREFIX = 'cp'
|
||||
|
||||
VER_SUFFIX = sysconfig.get_config_var('py_version_nodot')
|
||||
if not VER_SUFFIX: # pragma: no cover
|
||||
VER_SUFFIX = '%s%s' % sys.version_info[:2]
|
||||
PYVER = 'py' + VER_SUFFIX
|
||||
IMPVER = IMP_PREFIX + VER_SUFFIX
|
||||
|
||||
ARCH = distutils.util.get_platform().replace('-', '_').replace('.', '_')
|
||||
|
||||
ABI = sysconfig.get_config_var('SOABI')
|
||||
if ABI and ABI.startswith('cpython-'):
|
||||
ABI = ABI.replace('cpython-', 'cp')
|
||||
else:
|
||||
ABI = 'none'
|
||||
|
||||
FILENAME_RE = re.compile(r'''
|
||||
(?P<nm>[^-]+)
|
||||
-(?P<vn>\d+[^-]*)
|
||||
(-(?P<bn>\d+[^-]*))?
|
||||
-(?P<py>\w+\d+(\.\w+\d+)*)
|
||||
-(?P<bi>\w+)
|
||||
-(?P<ar>\w+)
|
||||
\.whl$
|
||||
''', re.IGNORECASE | re.VERBOSE)
|
||||
|
||||
NAME_VERSION_RE = re.compile(r'''
|
||||
(?P<nm>[^-]+)
|
||||
-(?P<vn>\d+[^-]*)
|
||||
(-(?P<bn>\d+[^-]*))?$
|
||||
''', re.IGNORECASE | re.VERBOSE)
|
||||
|
||||
SHEBANG_RE = re.compile(br'\s*#![^\r\n]*')
|
||||
|
||||
if os.sep == '/':
|
||||
to_posix = lambda o: o
|
||||
else:
|
||||
to_posix = lambda o: o.replace(os.sep, '/')
|
||||
|
||||
|
||||
class Mounter(object):
|
||||
def __init__(self):
|
||||
self.impure_wheels = {}
|
||||
self.libs = {}
|
||||
|
||||
def add(self, pathname, extensions):
|
||||
self.impure_wheels[pathname] = extensions
|
||||
self.libs.update(extensions)
|
||||
|
||||
def remove(self, pathname):
|
||||
extensions = self.impure_wheels.pop(pathname)
|
||||
for k, v in extensions:
|
||||
if k in self.libs:
|
||||
del self.libs[k]
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname in self.libs:
|
||||
result = self
|
||||
else:
|
||||
result = None
|
||||
return result
|
||||
|
||||
def load_module(self, fullname):
|
||||
if fullname in sys.modules:
|
||||
result = sys.modules[fullname]
|
||||
else:
|
||||
if fullname not in self.libs:
|
||||
raise ImportError('unable to find extension for %s' % fullname)
|
||||
result = imp.load_dynamic(fullname, self.libs[fullname])
|
||||
result.__loader__ = self
|
||||
parts = fullname.rsplit('.', 1)
|
||||
if len(parts) > 1:
|
||||
result.__package__ = parts[0]
|
||||
return result
|
||||
|
||||
_hook = Mounter()
|
||||
|
||||
|
||||
class Wheel(object):
|
||||
"""
|
||||
Class to build and install from Wheel files (PEP 427).
|
||||
"""
|
||||
|
||||
wheel_version = (1, 1)
|
||||
hash_kind = 'sha256'
|
||||
|
||||
def __init__(self, filename=None, sign=False, verify=False):
|
||||
"""
|
||||
Initialise an instance using a (valid) filename.
|
||||
"""
|
||||
self.sign = sign
|
||||
self.verify = verify
|
||||
self.buildver = ''
|
||||
self.pyver = [PYVER]
|
||||
self.abi = ['none']
|
||||
self.arch = ['any']
|
||||
self.dirname = os.getcwd()
|
||||
if filename is None:
|
||||
self.name = 'dummy'
|
||||
self.version = '0.1'
|
||||
self._filename = self.filename
|
||||
else:
|
||||
m = NAME_VERSION_RE.match(filename)
|
||||
if m:
|
||||
info = m.groupdict('')
|
||||
self.name = info['nm']
|
||||
self.version = info['vn']
|
||||
self.buildver = info['bn']
|
||||
self._filename = self.filename
|
||||
else:
|
||||
dirname, filename = os.path.split(filename)
|
||||
m = FILENAME_RE.match(filename)
|
||||
if not m:
|
||||
raise DistlibException('Invalid name or '
|
||||
'filename: %r' % filename)
|
||||
if dirname:
|
||||
self.dirname = os.path.abspath(dirname)
|
||||
self._filename = filename
|
||||
info = m.groupdict('')
|
||||
self.name = info['nm']
|
||||
self.version = info['vn']
|
||||
self.buildver = info['bn']
|
||||
self.pyver = info['py'].split('.')
|
||||
self.abi = info['bi'].split('.')
|
||||
self.arch = info['ar'].split('.')
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
"""
|
||||
Build and return a filename from the various components.
|
||||
"""
|
||||
if self.buildver:
|
||||
buildver = '-' + self.buildver
|
||||
else:
|
||||
buildver = ''
|
||||
pyver = '.'.join(self.pyver)
|
||||
abi = '.'.join(self.abi)
|
||||
arch = '.'.join(self.arch)
|
||||
return '%s-%s%s-%s-%s-%s.whl' % (self.name, self.version, buildver,
|
||||
pyver, abi, arch)
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
for pyver in self.pyver:
|
||||
for abi in self.abi:
|
||||
for arch in self.arch:
|
||||
yield pyver, abi, arch
|
||||
|
||||
@cached_property
|
||||
def metadata(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
metadata_filename = posixpath.join(info_dir, METADATA_FILENAME)
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
try:
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = wrapper(bf)
|
||||
result = Metadata(fileobj=wf)
|
||||
except KeyError:
|
||||
raise ValueError('Invalid wheel, because %s is '
|
||||
'missing' % METADATA_FILENAME)
|
||||
return result
|
||||
|
||||
@cached_property
|
||||
def info(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
metadata_filename = posixpath.join(info_dir, 'WHEEL')
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = wrapper(bf)
|
||||
message = message_from_file(wf)
|
||||
result = dict(message)
|
||||
return result
|
||||
|
||||
def process_shebang(self, data):
|
||||
m = SHEBANG_RE.match(data)
|
||||
if m:
|
||||
data = b'#!python' + data[m.end():]
|
||||
else:
|
||||
cr = data.find(b'\r')
|
||||
lf = data.find(b'\n')
|
||||
if cr < 0 or cr > lf:
|
||||
term = b'\n'
|
||||
else:
|
||||
if data[cr:cr + 2] == b'\r\n':
|
||||
term = b'\r\n'
|
||||
else:
|
||||
term = b'\r'
|
||||
data = b'#!python' + term + data
|
||||
return data
|
||||
|
||||
def get_hash(self, data, hash_kind=None):
|
||||
if hash_kind is None:
|
||||
hash_kind = self.hash_kind
|
||||
try:
|
||||
hasher = getattr(hashlib, hash_kind)
|
||||
except AttributeError:
|
||||
raise DistlibException('Unsupported hash algorithm: %r' % hash_kind)
|
||||
result = hasher(data).digest()
|
||||
result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii')
|
||||
return hash_kind, result
|
||||
|
||||
def write_record(self, records, record_path, base):
|
||||
with CSVWriter(record_path) as writer:
|
||||
for row in records:
|
||||
writer.writerow(row)
|
||||
p = to_posix(os.path.relpath(record_path, base))
|
||||
writer.writerow((p, '', ''))
|
||||
|
||||
def build(self, paths, tags=None, wheel_version=None):
|
||||
"""
|
||||
Build a wheel from files in specified paths, and use any specified tags
|
||||
when determining the name of the wheel.
|
||||
"""
|
||||
if tags is None:
|
||||
tags = {}
|
||||
|
||||
libkey = list(filter(lambda o: o in paths, ('purelib', 'platlib')))[0]
|
||||
if libkey == 'platlib':
|
||||
is_pure = 'false'
|
||||
default_pyver = [IMPVER]
|
||||
default_abi = [ABI]
|
||||
default_arch = [ARCH]
|
||||
else:
|
||||
is_pure = 'true'
|
||||
default_pyver = [PYVER]
|
||||
default_abi = ['none']
|
||||
default_arch = ['any']
|
||||
|
||||
self.pyver = tags.get('pyver', default_pyver)
|
||||
self.abi = tags.get('abi', default_abi)
|
||||
self.arch = tags.get('arch', default_arch)
|
||||
|
||||
libdir = paths[libkey]
|
||||
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
data_dir = '%s.data' % name_ver
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
|
||||
archive_paths = []
|
||||
|
||||
# First, stuff which is not in site-packages
|
||||
for key in ('data', 'headers', 'scripts'):
|
||||
if key not in paths:
|
||||
continue
|
||||
path = paths[key]
|
||||
if os.path.isdir(path):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for fn in files:
|
||||
p = fsdecode(os.path.join(root, fn))
|
||||
rp = os.path.relpath(p, path)
|
||||
ap = to_posix(os.path.join(data_dir, key, rp))
|
||||
archive_paths.append((ap, p))
|
||||
if key == 'scripts' and not p.endswith('.exe'):
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read()
|
||||
data = self.process_shebang(data)
|
||||
with open(p, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
# Now, stuff which is in site-packages, other than the
|
||||
# distinfo stuff.
|
||||
path = libdir
|
||||
distinfo = None
|
||||
for root, dirs, files in os.walk(path):
|
||||
if root == path:
|
||||
# At the top level only, save distinfo for later
|
||||
# and skip it for now
|
||||
for i, dn in enumerate(dirs):
|
||||
dn = fsdecode(dn)
|
||||
if dn.endswith('.dist-info'):
|
||||
distinfo = os.path.join(root, dn)
|
||||
del dirs[i]
|
||||
break
|
||||
assert distinfo, '.dist-info directory expected, not found'
|
||||
|
||||
for fn in files:
|
||||
# comment out next suite to leave .pyc files in
|
||||
if fsdecode(fn).endswith(('.pyc', '.pyo')):
|
||||
continue
|
||||
p = os.path.join(root, fn)
|
||||
rp = to_posix(os.path.relpath(p, path))
|
||||
archive_paths.append((rp, p))
|
||||
|
||||
# Now distinfo. Assumed to be flat, i.e. os.listdir is enough.
|
||||
files = os.listdir(distinfo)
|
||||
for fn in files:
|
||||
if fn not in ('RECORD', 'INSTALLER', 'SHARED'):
|
||||
p = fsdecode(os.path.join(distinfo, fn))
|
||||
ap = to_posix(os.path.join(info_dir, fn))
|
||||
archive_paths.append((ap, p))
|
||||
|
||||
wheel_metadata = [
|
||||
'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version),
|
||||
'Generator: distlib %s' % __version__,
|
||||
'Root-Is-Purelib: %s' % is_pure,
|
||||
]
|
||||
for pyver, abi, arch in self.tags:
|
||||
wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch))
|
||||
p = os.path.join(distinfo, 'WHEEL')
|
||||
with open(p, 'w') as f:
|
||||
f.write('\n'.join(wheel_metadata))
|
||||
ap = to_posix(os.path.join(info_dir, 'WHEEL'))
|
||||
archive_paths.append((ap, p))
|
||||
|
||||
# Now, at last, RECORD.
|
||||
# Paths in here are archive paths - nothing else makes sense.
|
||||
records = []
|
||||
hasher = getattr(hashlib, self.hash_kind)
|
||||
for ap, p in archive_paths:
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read()
|
||||
digest = '%s=%s' % self.get_hash(data)
|
||||
size = os.path.getsize(p)
|
||||
records.append((ap, digest, size))
|
||||
|
||||
p = os.path.join(distinfo, 'RECORD')
|
||||
self.write_record(records, p, libdir)
|
||||
ap = to_posix(os.path.join(info_dir, 'RECORD'))
|
||||
archive_paths.append((ap, p))
|
||||
# Now, ready to build the zip file
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for ap, p in archive_paths:
|
||||
logger.debug('Wrote %s to %s in wheel', p, ap)
|
||||
zf.write(p, ap)
|
||||
return pathname
|
||||
|
||||
def install(self, paths, maker, **kwargs):
|
||||
"""
|
||||
Install a wheel to the specified paths. If kwarg ``warner`` is
|
||||
specified, it should be a callable, which will be called with two
|
||||
tuples indicating the wheel version of this software and the wheel
|
||||
version in the file, if there is a discrepancy in the versions.
|
||||
This can be used to issue any warnings to raise any exceptions.
|
||||
If kwarg ``lib_only`` is True, only the purelib/platlib files are
|
||||
installed, and the headers, scripts, data and dist-info metadata are
|
||||
not written.
|
||||
|
||||
The return value is a :class:`InstalledDistribution` instance unless
|
||||
``options.lib_only`` is True, in which case the return value is ``None``.
|
||||
"""
|
||||
|
||||
dry_run = maker.dry_run
|
||||
warner = kwargs.get('warner')
|
||||
lib_only = kwargs.get('lib_only', False)
|
||||
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
data_dir = '%s.data' % name_ver
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
|
||||
metadata_name = posixpath.join(info_dir, METADATA_FILENAME)
|
||||
wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
|
||||
record_name = posixpath.join(info_dir, 'RECORD')
|
||||
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
with zf.open(wheel_metadata_name) as bwf:
|
||||
wf = wrapper(bwf)
|
||||
message = message_from_file(wf)
|
||||
wv = message['Wheel-Version'].split('.', 1)
|
||||
file_version = tuple([int(i) for i in wv])
|
||||
if (file_version != self.wheel_version) and warner:
|
||||
warner(self.wheel_version, file_version)
|
||||
|
||||
if message['Root-Is-Purelib'] == 'true':
|
||||
libdir = paths['purelib']
|
||||
else:
|
||||
libdir = paths['platlib']
|
||||
|
||||
records = {}
|
||||
with zf.open(record_name) as bf:
|
||||
with CSVReader(stream=bf) as reader:
|
||||
for row in reader:
|
||||
p = row[0]
|
||||
records[p] = row
|
||||
|
||||
data_pfx = posixpath.join(data_dir, '')
|
||||
info_pfx = posixpath.join(info_dir, '')
|
||||
script_pfx = posixpath.join(data_dir, 'scripts', '')
|
||||
|
||||
# make a new instance rather than a copy of maker's,
|
||||
# as we mutate it
|
||||
fileop = FileOperator(dry_run=dry_run)
|
||||
fileop.record = True # so we can rollback if needed
|
||||
|
||||
bc = not sys.dont_write_bytecode # Double negatives. Lovely!
|
||||
|
||||
outfiles = [] # for RECORD writing
|
||||
|
||||
# for script copying/shebang processing
|
||||
workdir = tempfile.mkdtemp()
|
||||
# set target dir later
|
||||
# we default add_launchers to False, as the
|
||||
# Python Launcher should be used instead
|
||||
maker.source_dir = workdir
|
||||
maker.target_dir = None
|
||||
try:
|
||||
for zinfo in zf.infolist():
|
||||
arcname = zinfo.filename
|
||||
if isinstance(arcname, text_type):
|
||||
u_arcname = arcname
|
||||
else:
|
||||
u_arcname = arcname.decode('utf-8')
|
||||
# The signature file won't be in RECORD,
|
||||
# and we don't currently don't do anything with it
|
||||
if u_arcname.endswith('/RECORD.jws'):
|
||||
continue
|
||||
row = records[u_arcname]
|
||||
if row[2] and str(zinfo.file_size) != row[2]:
|
||||
raise DistlibException('size mismatch for '
|
||||
'%s' % u_arcname)
|
||||
if row[1]:
|
||||
kind, value = row[1].split('=', 1)
|
||||
with zf.open(arcname) as bf:
|
||||
data = bf.read()
|
||||
_, digest = self.get_hash(data, kind)
|
||||
if digest != value:
|
||||
raise DistlibException('digest mismatch for '
|
||||
'%s' % arcname)
|
||||
|
||||
if lib_only and u_arcname.startswith((info_pfx, data_pfx)):
|
||||
logger.debug('lib_only: skipping %s', u_arcname)
|
||||
continue
|
||||
is_script = (u_arcname.startswith(script_pfx)
|
||||
and not u_arcname.endswith('.exe'))
|
||||
|
||||
if u_arcname.startswith(data_pfx):
|
||||
_, where, rp = u_arcname.split('/', 2)
|
||||
outfile = os.path.join(paths[where], convert_path(rp))
|
||||
else:
|
||||
# meant for site-packages.
|
||||
if u_arcname in (wheel_metadata_name, record_name):
|
||||
continue
|
||||
outfile = os.path.join(libdir, convert_path(u_arcname))
|
||||
if not is_script:
|
||||
with zf.open(arcname) as bf:
|
||||
fileop.copy_stream(bf, outfile)
|
||||
outfiles.append(outfile)
|
||||
# Double check the digest of the written file
|
||||
if not dry_run and row[1]:
|
||||
with open(outfile, 'rb') as bf:
|
||||
data = bf.read()
|
||||
_, newdigest = self.get_hash(data, kind)
|
||||
if newdigest != digest:
|
||||
raise DistlibException('digest mismatch '
|
||||
'on write for '
|
||||
'%s' % outfile)
|
||||
if bc and outfile.endswith('.py'):
|
||||
try:
|
||||
pyc = fileop.byte_compile(outfile)
|
||||
outfiles.append(pyc)
|
||||
except Exception:
|
||||
# Don't give up if byte-compilation fails,
|
||||
# but log it and perhaps warn the user
|
||||
logger.warning('Byte-compilation failed',
|
||||
exc_info=True)
|
||||
else:
|
||||
fn = os.path.basename(convert_path(arcname))
|
||||
workname = os.path.join(workdir, fn)
|
||||
with zf.open(arcname) as bf:
|
||||
fileop.copy_stream(bf, workname)
|
||||
|
||||
dn, fn = os.path.split(outfile)
|
||||
maker.target_dir = dn
|
||||
filenames = maker.make(fn)
|
||||
fileop.set_executable_mode(filenames)
|
||||
outfiles.extend(filenames)
|
||||
|
||||
if lib_only:
|
||||
logger.debug('lib_only: returning None')
|
||||
dist = None
|
||||
else:
|
||||
# Generate scripts
|
||||
|
||||
# Try to get pydist.json so we can see if there are
|
||||
# any commands to generate. If this fails (e.g. because
|
||||
# of a legacy wheel), log a warning but don't give up.
|
||||
commands = None
|
||||
file_version = self.info['Wheel-Version']
|
||||
if file_version == '1.0':
|
||||
# Use legacy info
|
||||
ep = posixpath.join(info_dir, 'entry_points.txt')
|
||||
try:
|
||||
with zf.open(ep) as bwf:
|
||||
epdata = read_exports(bwf)
|
||||
commands = {}
|
||||
for key in ('console', 'gui'):
|
||||
k = '%s_scripts' % key
|
||||
if k in epdata:
|
||||
commands['wrap_%s' % key] = d = {}
|
||||
for v in epdata[k].values():
|
||||
s = '%s:%s' % (v.prefix, v.suffix)
|
||||
if v.flags:
|
||||
s += ' %s' % v.flags
|
||||
d[v.name] = s
|
||||
except Exception:
|
||||
logger.warning('Unable to read legacy script '
|
||||
'metadata, so cannot generate '
|
||||
'scripts')
|
||||
else:
|
||||
try:
|
||||
with zf.open(metadata_name) as bwf:
|
||||
wf = wrapper(bwf)
|
||||
commands = json.load(wf).get('commands')
|
||||
except Exception:
|
||||
logger.warning('Unable to read JSON metadata, so '
|
||||
'cannot generate scripts')
|
||||
if commands:
|
||||
console_scripts = commands.get('wrap_console', {})
|
||||
gui_scripts = commands.get('wrap_gui', {})
|
||||
if console_scripts or gui_scripts:
|
||||
script_dir = paths.get('scripts', '')
|
||||
if not os.path.isdir(script_dir):
|
||||
raise ValueError('Valid script path not '
|
||||
'specified')
|
||||
maker.target_dir = script_dir
|
||||
for k, v in console_scripts.items():
|
||||
script = '%s = %s' % (k, v)
|
||||
filenames = maker.make(script)
|
||||
fileop.set_executable_mode(filenames)
|
||||
|
||||
if gui_scripts:
|
||||
options = {'gui': True }
|
||||
for k, v in gui_scripts.items():
|
||||
script = '%s = %s' % (k, v)
|
||||
filenames = maker.make(script, options)
|
||||
fileop.set_executable_mode(filenames)
|
||||
|
||||
p = os.path.join(libdir, info_dir)
|
||||
dist = InstalledDistribution(p)
|
||||
|
||||
# Write SHARED
|
||||
paths = dict(paths) # don't change passed in dict
|
||||
del paths['purelib']
|
||||
del paths['platlib']
|
||||
paths['lib'] = libdir
|
||||
p = dist.write_shared_locations(paths, dry_run)
|
||||
if p:
|
||||
outfiles.append(p)
|
||||
|
||||
# Write RECORD
|
||||
dist.write_installed_files(outfiles, paths['prefix'],
|
||||
dry_run)
|
||||
return dist
|
||||
except Exception: # pragma: no cover
|
||||
logger.exception('installation failed.')
|
||||
fileop.rollback()
|
||||
raise
|
||||
finally:
|
||||
shutil.rmtree(workdir)
|
||||
|
||||
def _get_dylib_cache(self):
|
||||
# Use native string to avoid issues on 2.x: see Python #20140.
|
||||
result = os.path.join(get_cache_base(), str('dylib-cache'), sys.version[:3])
|
||||
if not os.path.isdir(result):
|
||||
os.makedirs(result)
|
||||
return result
|
||||
|
||||
def _get_extensions(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
arcname = posixpath.join(info_dir, 'EXTENSIONS')
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
result = []
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
try:
|
||||
with zf.open(arcname) as bf:
|
||||
wf = wrapper(bf)
|
||||
extensions = json.load(wf)
|
||||
cache_base = self._get_dylib_cache()
|
||||
for name, relpath in extensions.items():
|
||||
dest = os.path.join(cache_base, convert_path(relpath))
|
||||
if not os.path.exists(dest):
|
||||
extract = True
|
||||
else:
|
||||
file_time = os.stat(dest).st_mtime
|
||||
file_time = datetime.datetime.fromtimestamp(file_time)
|
||||
info = zf.getinfo(relpath)
|
||||
wheel_time = datetime.datetime(*info.date_time)
|
||||
extract = wheel_time > file_time
|
||||
if extract:
|
||||
zf.extract(relpath, cache_base)
|
||||
result.append((name, dest))
|
||||
except KeyError:
|
||||
pass
|
||||
return result
|
||||
|
||||
def mount(self, append=False):
|
||||
pathname = os.path.abspath(os.path.join(self.dirname, self.filename))
|
||||
if not is_compatible(self):
|
||||
msg = 'Wheel %s not mountable in this Python.' % pathname
|
||||
raise DistlibException(msg)
|
||||
if pathname in sys.path:
|
||||
logger.debug('%s already in path', pathname)
|
||||
else:
|
||||
if append:
|
||||
sys.path.append(pathname)
|
||||
else:
|
||||
sys.path.insert(0, pathname)
|
||||
extensions = self._get_extensions()
|
||||
if extensions:
|
||||
if _hook not in sys.meta_path:
|
||||
sys.meta_path.append(_hook)
|
||||
_hook.add(pathname, extensions)
|
||||
|
||||
def unmount(self):
|
||||
pathname = os.path.abspath(os.path.join(self.dirname, self.filename))
|
||||
if pathname not in sys.path:
|
||||
logger.debug('%s not in path', pathname)
|
||||
else:
|
||||
sys.path.remove(pathname)
|
||||
if pathname in _hook.impure_wheels:
|
||||
_hook.remove(pathname)
|
||||
if not _hook.impure_wheels:
|
||||
if _hook in sys.meta_path:
|
||||
sys.meta_path.remove(_hook)
|
||||
|
||||
|
||||
def compatible_tags():
|
||||
"""
|
||||
Return (pyver, abi, arch) tuples compatible with this Python.
|
||||
"""
|
||||
versions = [VER_SUFFIX]
|
||||
major = VER_SUFFIX[0]
|
||||
for minor in range(sys.version_info[1] - 1, - 1, -1):
|
||||
versions.append(''.join([major, str(minor)]))
|
||||
|
||||
abis = []
|
||||
for suffix, _, _ in imp.get_suffixes():
|
||||
if suffix.startswith('.abi'):
|
||||
abis.append(suffix.split('.', 2)[1])
|
||||
abis.sort()
|
||||
if ABI != 'none':
|
||||
abis.insert(0, ABI)
|
||||
abis.append('none')
|
||||
result = []
|
||||
|
||||
# Most specific - our Python version, ABI and arch
|
||||
for abi in abis:
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi, ARCH))
|
||||
|
||||
# where no ABI / arch dependency, but IMP_PREFIX dependency
|
||||
for i, version in enumerate(versions):
|
||||
result.append((''.join((IMP_PREFIX, version)), 'none', 'any'))
|
||||
if i == 0:
|
||||
result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any'))
|
||||
|
||||
# no IMP_PREFIX, ABI or arch dependency
|
||||
for i, version in enumerate(versions):
|
||||
result.append((''.join(('py', version)), 'none', 'any'))
|
||||
if i == 0:
|
||||
result.append((''.join(('py', version[0])), 'none', 'any'))
|
||||
return result
|
||||
|
||||
|
||||
COMPATIBLE_TAGS = compatible_tags()
|
||||
|
||||
del compatible_tags
|
||||
|
||||
|
||||
def is_compatible(wheel, tags=None):
|
||||
if not isinstance(wheel, Wheel):
|
||||
wheel = Wheel(wheel) # assume it's a filename
|
||||
result = False
|
||||
if tags is None:
|
||||
tags = COMPATIBLE_TAGS
|
||||
for ver, abi, arch in tags:
|
||||
if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch:
|
||||
result = True
|
||||
break
|
||||
return result
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
HTML parsing library based on the WHATWG "HTML5"
|
||||
specification. The parser is designed to be compatible with existing
|
||||
HTML found in the wild and implements well-defined error recovery that
|
||||
is largely compatible with modern desktop web browsers.
|
||||
|
||||
Example usage:
|
||||
|
||||
import html5lib
|
||||
f = open("my_document.html")
|
||||
tree = html5lib.parse(f)
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
from .html5parser import HTMLParser, parse, parseFragment
|
||||
from .treebuilders import getTreeBuilder
|
||||
from .treewalkers import getTreeWalker
|
||||
from .serializer import serialize
|
||||
|
||||
__all__ = ["HTMLParser", "parse", "parseFragment", "getTreeBuilder",
|
||||
"getTreeWalker", "serialize"]
|
||||
__version__ = "1.0b1"
|
||||
+3086
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user