1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

'pip wheel'

This commit is contained in:
Marcus Smith 2012-10-16 15:57:10 -07:00
parent 6e481b1f9e
commit 4bfc92ddba
9 changed files with 368 additions and 83 deletions

View file

@ -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
View 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
View 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()

View file

@ -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()

View file

@ -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]))

View file

@ -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"

Binary file not shown.

View file

@ -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

View file

@ -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