mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
'pip wheel'
This commit is contained in:
parent
6e481b1f9e
commit
4bfc92ddba
9 changed files with 368 additions and 83 deletions
|
@ -10,6 +10,7 @@ from pip.basecommand import Command
|
|||
from pip.index import PackageFinder
|
||||
from pip.exceptions import InstallationError, CommandError
|
||||
from pip.backwardcompat import home_lib
|
||||
from pip.commands.options import *
|
||||
|
||||
|
||||
class InstallCommand(Command):
|
||||
|
@ -35,60 +36,14 @@ class InstallCommand(Command):
|
|||
'setup.py develop). You can run this on an existing directory/checkout (like '
|
||||
'pip install -e src/mycheckout). This option may be provided multiple times. '
|
||||
'Possible values for VCS are: svn, git, hg and bzr.')
|
||||
self.parser.add_option(
|
||||
'-r', '--requirement',
|
||||
dest='requirements',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='FILENAME',
|
||||
help='Install all the packages listed in the given requirements file. '
|
||||
'This option can be used multiple times.')
|
||||
self.parser.add_option(
|
||||
'-f', '--find-links',
|
||||
dest='find_links',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='URL',
|
||||
help='URL to look for packages at')
|
||||
self.parser.add_option(
|
||||
'-i', '--index-url', '--pypi-url',
|
||||
dest='index_url',
|
||||
metavar='URL',
|
||||
default='http://pypi.python.org/simple/',
|
||||
help='Base URL of Python Package Index (default %default)')
|
||||
self.parser.add_option(
|
||||
'--extra-index-url',
|
||||
dest='extra_index_urls',
|
||||
metavar='URL',
|
||||
action='append',
|
||||
default=[],
|
||||
help='Extra URLs of package indexes to use in addition to --index-url')
|
||||
self.parser.add_option(
|
||||
'--no-index',
|
||||
dest='no_index',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Ignore package index (only looking at --find-links URLs instead)')
|
||||
self.parser.add_option(
|
||||
'-M', '--use-mirrors',
|
||||
dest='use_mirrors',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use the PyPI mirrors as a fallback in case the main index is down.')
|
||||
self.parser.add_option(
|
||||
'--mirrors',
|
||||
dest='mirrors',
|
||||
metavar='URL',
|
||||
action='append',
|
||||
default=[],
|
||||
help='Specific mirror URLs to query when --use-mirrors is used')
|
||||
|
||||
self.parser.add_option(
|
||||
'-b', '--build', '--build-dir', '--build-directory',
|
||||
dest='build_dir',
|
||||
metavar='DIR',
|
||||
default=build_prefix,
|
||||
help='Unpack packages into DIR (default %default) and build from there')
|
||||
self.parser.add_option(REQUIREMENTS)
|
||||
self.parser.add_option(FIND_LINKS)
|
||||
self.parser.add_option(INDEX_URL)
|
||||
self.parser.add_option(EXTRA_INDEX_URLS)
|
||||
self.parser.add_option(NO_INDEX)
|
||||
self.parser.add_option(USE_MIRRORS)
|
||||
self.parser.add_option(MIRRORS)
|
||||
self.parser.add_option(BUILD_DIR)
|
||||
self.parser.add_option(
|
||||
'-t', '--target',
|
||||
dest='target_dir',
|
||||
|
@ -101,19 +56,13 @@ class InstallCommand(Command):
|
|||
metavar='DIR',
|
||||
default=None,
|
||||
help='Download packages into DIR instead of installing them')
|
||||
self.parser.add_option(
|
||||
'--download-cache',
|
||||
dest='download_cache',
|
||||
metavar='DIR',
|
||||
default=None,
|
||||
help='Cache downloaded packages in DIR')
|
||||
self.parser.add_option(DOWNLOAD_CACHE)
|
||||
self.parser.add_option(
|
||||
'--src', '--source', '--source-dir', '--source-directory',
|
||||
dest='src_dir',
|
||||
metavar='DIR',
|
||||
default=src_prefix,
|
||||
help='Check out --editable packages into DIR (default %default)')
|
||||
|
||||
self.parser.add_option(
|
||||
'-U', '--upgrade',
|
||||
dest='upgrade',
|
||||
|
@ -147,23 +96,8 @@ class InstallCommand(Command):
|
|||
action="store_true",
|
||||
help="Don't download any packages, just install the ones already downloaded "
|
||||
"(completes an install run with --no-install)")
|
||||
|
||||
self.parser.add_option(
|
||||
'--install-option',
|
||||
dest='install_options',
|
||||
action='append',
|
||||
help="Extra arguments to be supplied to the setup.py install "
|
||||
"command (use like --install-option=\"--install-scripts=/usr/local/bin\"). "
|
||||
"Use multiple --install-option options to pass multiple options to setup.py install. "
|
||||
"If you are using an option with a directory path, be sure to use absolute path.")
|
||||
|
||||
self.parser.add_option(
|
||||
'--global-option',
|
||||
dest='global_options',
|
||||
action='append',
|
||||
help="Extra global options to be supplied to the setup.py "
|
||||
"call before the install command")
|
||||
|
||||
self.parser.add_option(INSTALL_OPTIONS)
|
||||
self.parser.add_option(GLOBAL_OPTIONS)
|
||||
self.parser.add_option(
|
||||
'--user',
|
||||
dest='use_user_site',
|
||||
|
@ -219,7 +153,6 @@ class InstallCommand(Command):
|
|||
src_dir=options.src_dir,
|
||||
download_dir=options.download_dir,
|
||||
download_cache=options.download_cache,
|
||||
use_wheel=options.use_wheel,
|
||||
upgrade=options.upgrade,
|
||||
as_egg=options.as_egg,
|
||||
ignore_installed=options.ignore_installed,
|
||||
|
|
89
pip/commands/options.py
Normal file
89
pip/commands/options.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
pip command options
|
||||
"""
|
||||
import os
|
||||
from optparse import make_option
|
||||
from pip.locations import build_prefix, src_prefix
|
||||
|
||||
REQUIREMENTS = make_option(
|
||||
'-r', '--requirement',
|
||||
dest='requirements',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='FILENAME',
|
||||
help='Install all the packages listed in the given requirements file. '
|
||||
'This option can be used multiple times.')
|
||||
|
||||
FIND_LINKS = make_option(
|
||||
'-f', '--find-links',
|
||||
dest='find_links',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='URL',
|
||||
help='URL to look for packages at')
|
||||
INDEX_URL = make_option(
|
||||
'-i', '--index-url', '--pypi-url',
|
||||
dest='index_url',
|
||||
metavar='URL',
|
||||
default='http://pypi.python.org/simple/',
|
||||
help='Base URL of Python Package Index (default %default)')
|
||||
|
||||
EXTRA_INDEX_URLS = make_option(
|
||||
'--extra-index-url',
|
||||
dest='extra_index_urls',
|
||||
metavar='URL',
|
||||
action='append',
|
||||
default=[],
|
||||
help='Extra URLs of package indexes to use in addition to --index-url')
|
||||
|
||||
NO_INDEX = make_option(
|
||||
'--no-index',
|
||||
dest='no_index',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Ignore package index (only looking at --find-links URLs instead)')
|
||||
|
||||
USE_MIRRORS = make_option(
|
||||
'-M', '--use-mirrors',
|
||||
dest='use_mirrors',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use the PyPI mirrors as a fallback in case the main index is down.')
|
||||
|
||||
MIRRORS = make_option(
|
||||
'--mirrors',
|
||||
dest='mirrors',
|
||||
metavar='URL',
|
||||
action='append',
|
||||
default=[],
|
||||
help='Specific mirror URLs to query when --use-mirrors is used')
|
||||
|
||||
DOWNLOAD_CACHE = make_option(
|
||||
'--download-cache',
|
||||
dest='download_cache',
|
||||
metavar='DIR',
|
||||
default=None,
|
||||
help='Cache downloaded packages in DIR')
|
||||
|
||||
BUILD_DIR = make_option(
|
||||
'-b', '--build', '--build-dir', '--build-directory',
|
||||
dest='build_dir',
|
||||
metavar='DIR',
|
||||
default=build_prefix,
|
||||
help='Unpack packages into DIR (default %default) and build from there')
|
||||
|
||||
INSTALL_OPTIONS = make_option(
|
||||
'--install-option',
|
||||
dest='install_options',
|
||||
action='append',
|
||||
help="Extra arguments to be supplied to the setup.py install "
|
||||
"command (use like --install-option=\"--install-scripts=/usr/local/bin\"). "
|
||||
"Use multiple --install-option options to pass multiple options to setup.py install. "
|
||||
"If you are using an option with a directory path, be sure to use absolute path.")
|
||||
|
||||
GLOBAL_OPTIONS = make_option(
|
||||
'--global-option',
|
||||
dest='global_options',
|
||||
action='append',
|
||||
help="Extra global options to be supplied to the setup.py "
|
||||
"call before the install command")
|
116
pip/commands/wheel.py
Normal file
116
pip/commands/wheel.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
from pip.req import InstallRequirement, RequirementSet
|
||||
from pip.req import parse_requirements
|
||||
from pip.log import logger
|
||||
from pip.locations import build_prefix, src_prefix, virtualenv_no_global
|
||||
from pip.basecommand import Command
|
||||
from pip.index import PackageFinder
|
||||
from pip.exceptions import InstallationError, CommandError
|
||||
from pip.backwardcompat import home_lib
|
||||
from pip.commands.options import *
|
||||
from pip.wheel import WheelBuilder
|
||||
from pip.util import normalize_path
|
||||
|
||||
DEFAULT_WHEEL_DIR = os.path.join(normalize_path(os.curdir), 'wheelhouse')
|
||||
|
||||
class WheelCommand(Command):
|
||||
name = 'wheel'
|
||||
usage = '%prog [OPTIONS] PACKAGE_NAMES...'
|
||||
summary = 'Build wheels from your requirements'
|
||||
|
||||
def __init__(self):
|
||||
super(WheelCommand, self).__init__()
|
||||
self.parser.add_option(
|
||||
'-w', '--wheel-dir',
|
||||
dest='wheel_dir',
|
||||
metavar='DIR',
|
||||
default=DEFAULT_WHEEL_DIR,
|
||||
help='Build wheels into DIR (default %default)')
|
||||
self.parser.add_option(
|
||||
'--unpack-only',
|
||||
dest='unpack_only',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Only unpack')
|
||||
self.parser.add_option(REQUIREMENTS)
|
||||
self.parser.add_option(FIND_LINKS)
|
||||
self.parser.add_option(INDEX_URL)
|
||||
self.parser.add_option(EXTRA_INDEX_URLS)
|
||||
self.parser.add_option(NO_INDEX)
|
||||
self.parser.add_option(USE_MIRRORS)
|
||||
self.parser.add_option(MIRRORS)
|
||||
self.parser.add_option(DOWNLOAD_CACHE)
|
||||
self.parser.add_option(BUILD_DIR)
|
||||
self.parser.add_option(
|
||||
'--build-option',
|
||||
dest='build_options',
|
||||
action='append',
|
||||
help="Extra arguments to be supplied to setup.py bdist_wheel")
|
||||
self.parser.add_option(GLOBAL_OPTIONS)
|
||||
|
||||
def run(self, options, args):
|
||||
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index:
|
||||
logger.notify('Ignoring indexes: %s' % ','.join(index_urls))
|
||||
index_urls = []
|
||||
|
||||
finder = PackageFinder(find_links=options.find_links,
|
||||
index_urls=index_urls,
|
||||
use_mirrors=options.use_mirrors,
|
||||
mirrors=options.mirrors,
|
||||
use_wheel=False)
|
||||
|
||||
options.build_dir = os.path.abspath(options.build_dir)
|
||||
requirement_set = RequirementSet(
|
||||
build_dir=options.build_dir,
|
||||
src_dir=None,
|
||||
download_dir=None,
|
||||
download_cache=options.download_cache,
|
||||
ignore_installed=True)
|
||||
|
||||
#parse args and/or requirements files
|
||||
for name in args:
|
||||
if name.endswith(".whl"):
|
||||
logger.notify("ignoring %s" % name)
|
||||
continue
|
||||
requirement_set.add_requirement(
|
||||
InstallRequirement.from_line(name, None))
|
||||
for filename in options.requirements:
|
||||
for req in parse_requirements(filename, finder=finder, options=options):
|
||||
if req.editable or (req.name is None and req.url.endswith(".whl")):
|
||||
logger.notify("ignoring %s" % req.url)
|
||||
continue
|
||||
requirement_set.add_requirement(req)
|
||||
|
||||
#fail if no requirements
|
||||
if not requirement_set.has_requirements:
|
||||
opts = {'name': self.name}
|
||||
msg = ('You must give at least one requirement '
|
||||
'to %(name)s (see "pip help %(name)s")' % opts)
|
||||
logger.error(msg)
|
||||
return
|
||||
|
||||
#if unpack-only, just prepare and return
|
||||
#'pip wheel' probably shouldn't be offering this? 'pip unpack'?
|
||||
if options.unpack_only:
|
||||
requirement_set.prepare_files(finder)
|
||||
return
|
||||
|
||||
#build wheels
|
||||
wb = WheelBuilder(
|
||||
requirement_set,
|
||||
finder,
|
||||
options.wheel_dir,
|
||||
build_options = options.build_options or [],
|
||||
global_options = options.global_options or []
|
||||
)
|
||||
wb.build()
|
||||
|
||||
requirement_set.cleanup_files()
|
||||
|
||||
|
||||
WheelCommand()
|
|
@ -822,8 +822,7 @@ class RequirementSet(object):
|
|||
|
||||
def __init__(self, build_dir, src_dir, download_dir, download_cache=None,
|
||||
upgrade=False, ignore_installed=False, as_egg=False,
|
||||
ignore_dependencies=False, force_reinstall=False, use_user_site=False,
|
||||
use_wheel=False):
|
||||
ignore_dependencies=False, force_reinstall=False, use_user_site=False):
|
||||
self.build_dir = build_dir
|
||||
self.src_dir = src_dir
|
||||
self.download_dir = download_dir
|
||||
|
@ -841,7 +840,6 @@ class RequirementSet(object):
|
|||
self.reqs_to_cleanup = []
|
||||
self.as_egg = as_egg
|
||||
self.use_user_site = use_user_site
|
||||
self.use_wheel = use_wheel
|
||||
|
||||
def __str__(self):
|
||||
reqs = [req for req in self.requirements.values()
|
||||
|
|
74
pip/wheel.py
74
pip/wheel.py
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Support functions for installing the "wheel" binary package format.
|
||||
Support functions for installing and building the "wheel" binary package format.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
|
@ -9,6 +9,8 @@ import sys
|
|||
import shutil
|
||||
import functools
|
||||
import hashlib
|
||||
from pip.log import logger
|
||||
from pip.util import call_subprocess, normalize_path
|
||||
|
||||
from base64 import urlsafe_b64encode
|
||||
|
||||
|
@ -181,3 +183,73 @@ def uninstallation_paths(dist):
|
|||
base = fn[:-3]
|
||||
path = os.path.join(dn, base+'.pyc')
|
||||
yield path
|
||||
|
||||
|
||||
|
||||
class WheelBuilder(object):
|
||||
"""Build wheels from a RequirementSet."""
|
||||
|
||||
ignore_list = ['distribute','pip','setuptools']
|
||||
|
||||
def __init__(self, requirement_set, finder, wheel_dir, build_options=[], global_options=[]):
|
||||
self.requirement_set = requirement_set
|
||||
self.finder = finder
|
||||
self.wheel_dir = normalize_path(wheel_dir)
|
||||
self.build_options = build_options
|
||||
self.global_options = global_options
|
||||
|
||||
def _build_one(self, req):
|
||||
"""Build one wheel."""
|
||||
|
||||
try:
|
||||
import wheel
|
||||
except ImportError:
|
||||
logger.error('The wheel package is required; Failed to build a wheel for %s.' %req.name)
|
||||
return False
|
||||
|
||||
base_args = [
|
||||
sys.executable, '-c',
|
||||
"import setuptools;__file__=%r;"\
|
||||
"exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))" % req.setup_py] + \
|
||||
list(self.global_options)
|
||||
|
||||
logger.notify('Running setup.py bdist_wheel for %s' % req.name)
|
||||
logger.notify('Destination directory: %s' % self.wheel_dir)
|
||||
wheel_args = base_args + ['bdist_wheel', '-d', self.wheel_dir] + self.build_options
|
||||
try:
|
||||
call_subprocess(wheel_args, cwd=req.source_dir, show_stdout=False)
|
||||
return True
|
||||
except:
|
||||
logger.error('Failed building wheel for %s' % req.name)
|
||||
return False
|
||||
|
||||
def build(self):
|
||||
"""Build wheels."""
|
||||
|
||||
#unpacks and constructs req set
|
||||
self.requirement_set.prepare_files(self.finder)
|
||||
|
||||
reqset = self.requirement_set.requirements.values()
|
||||
|
||||
#make the wheelhouse
|
||||
if not os.path.exists(self.wheel_dir):
|
||||
os.makedirs(self.wheel_dir)
|
||||
|
||||
#build the wheels
|
||||
logger.notify('Building wheels for collected packages: %s' % ', '.join([req.name for req in reqset]))
|
||||
logger.indent += 2
|
||||
build_success, build_failure = [], []
|
||||
for req in reqset:
|
||||
if req.name.lower() in self.ignore_list:
|
||||
continue
|
||||
if self._build_one(req):
|
||||
build_success.append(req)
|
||||
else:
|
||||
build_failure.append(req)
|
||||
logger.indent -= 2
|
||||
|
||||
#notify sucess/failure
|
||||
if build_success:
|
||||
logger.notify('Successfully built %s' % ' '.join([req.name for req in build_success]))
|
||||
if build_failure:
|
||||
logger.notify('Failed to build %s' % ' '.join([req.name for req in build_failure]))
|
||||
|
|
|
@ -62,6 +62,9 @@ simple-[123].0.tar.gz
|
|||
---------------------
|
||||
contains "simple" package; good for basic testing and version logic.
|
||||
|
||||
wheelbroken-0.1.tar.gz
|
||||
-----------------
|
||||
fails for "setup.py bdist_wheel"
|
||||
|
||||
|
||||
|
||||
|
|
BIN
tests/packages/wheelbroken-0.1.tar.gz
Normal file
BIN
tests/packages/wheelbroken-0.1.tar.gz
Normal file
Binary file not shown.
|
@ -13,6 +13,7 @@ from tests.path import Path, curdir, u
|
|||
from pip.util import rmtree
|
||||
|
||||
pyversion = sys.version[:3]
|
||||
pyversion_nodot = "%d%d" % (sys.version_info[0], sys.version_info[1])
|
||||
|
||||
# the directory containing all the tests
|
||||
here = Path(__file__).abspath.folder
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
"""Tests for wheel binary packages and .dist-info."""
|
||||
|
||||
import imp
|
||||
import os
|
||||
from pip import wheel
|
||||
import textwrap
|
||||
from tests.test_pip import here, reset_env, run_pip, pyversion, pyversion_nodot, write_file
|
||||
from tests.path import Path
|
||||
|
||||
FIND_LINKS = 'file://' + os.path.join(here, 'packages')
|
||||
|
||||
def test_uninstallation_paths():
|
||||
class dist(object):
|
||||
|
@ -28,3 +34,70 @@ def test_uninstallation_paths():
|
|||
paths2 = list(wheel.uninstallation_paths(d))
|
||||
|
||||
assert paths2 == paths
|
||||
|
||||
|
||||
def test_pip_wheel_success():
|
||||
"""
|
||||
Test 'pip wheel' success.
|
||||
"""
|
||||
env = reset_env()
|
||||
run_pip('install', 'wheel')
|
||||
run_pip('install', 'markerlib')
|
||||
result = run_pip('wheel', '--no-index', '-f', FIND_LINKS, 'simple==3.0')
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_path = env.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path in result.files_created, (wheel_file_path, result.files_created)
|
||||
assert "Successfully built simple" in result.stdout, result.stdout
|
||||
|
||||
|
||||
def test_pip_wheel_fail():
|
||||
"""
|
||||
Test 'pip wheel' failure.
|
||||
"""
|
||||
env = reset_env()
|
||||
run_pip('install', 'wheel')
|
||||
run_pip('install', 'markerlib')
|
||||
result = run_pip('wheel', '--no-index', '-f', FIND_LINKS, 'wheelbroken==0.1')
|
||||
wheel_file_name = 'wheelbroken-0.1-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_path = env.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path not in result.files_created, (wheel_file_path, result.files_created)
|
||||
assert "FakeError" in result.stdout, result.stdout
|
||||
assert "Failed to build wheelbroken" in result.stdout, result.stdout
|
||||
|
||||
|
||||
def test_pip_wheel_ignore_wheels_editables():
|
||||
"""
|
||||
Test 'pip wheel' ignores editables and *.whl files in requirements
|
||||
"""
|
||||
env = reset_env()
|
||||
run_pip('install', 'wheel')
|
||||
run_pip('install', 'markerlib')
|
||||
|
||||
local_wheel = '%s/simple.dist-0.1-py2.py3-none-any.whl' % FIND_LINKS
|
||||
local_editable = os.path.abspath(os.path.join(here, 'packages', 'FSPkg'))
|
||||
write_file('reqs.txt', textwrap.dedent("""\
|
||||
%s
|
||||
-e %s
|
||||
simple
|
||||
""" % (local_wheel, local_editable)))
|
||||
result = run_pip('wheel', '--no-index', '-f', FIND_LINKS, '-r', env.scratch_path / 'reqs.txt')
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_path = env.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path in result.files_created, (wheel_file_path, result.files_created)
|
||||
assert "Successfully built simple" in result.stdout, result.stdout
|
||||
assert "Failed to build" not in result.stdout, result.stdout
|
||||
assert "ignoring %s" % local_wheel in result.stdout
|
||||
assert "ignoring file://%s" % local_editable in result.stdout, result.stdout
|
||||
|
||||
|
||||
def test_pip_wheel_unpack_only():
|
||||
"""
|
||||
Test 'pip wheel' unpack only.
|
||||
"""
|
||||
env = reset_env()
|
||||
result = run_pip('wheel', '--unpack-only', '--no-index', '-f', FIND_LINKS, 'simple==3.0')
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_path = env.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path not in result.files_created, (wheel_file_path, result.files_created)
|
||||
assert env.venv/'build'/'simple'/'setup.py' in result.files_created, result.files_created
|
||||
|
||||
|
|
Loading…
Reference in a new issue