Skip to content

build wheels for dependencies of editables #1787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
* Fixed :issue:`1769`. `pip wheel` now returns an error code if any wheels
fail to build.

* Fixed :issue:`1775`. `pip wheel` wasn't building wheels for dependencies of
editable requirements.


**1.5.5 (2014-05-03)**

Expand Down
22 changes: 21 additions & 1 deletion pip/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"""
import copy
from optparse import OptionGroup, SUPPRESS_HELP, Option
from pip.locations import build_prefix, default_log_file
from pip.locations import build_prefix, default_log_file, src_prefix


def make_option_group(group, parser):
Expand Down Expand Up @@ -286,6 +286,26 @@ def make(self):
help='Install from the given requirements file. '
'This option can be used multiple times.')

editable = OptionMaker(
'-e', '--editable',
dest='editables',
action='append',
default=[],
metavar='path/url',
help=('Install a project in editable mode (i.e. setuptools '
'"develop mode") from a local project path or a VCS url.'),
)

src = OptionMaker(
'--src', '--source', '--source-dir', '--source-directory',
dest='src_dir',
metavar='dir',
default=src_prefix,
help='Directory to check out editable projects into. '
'The default in a virtualenv is "<venv path>/src". '
'The default for global installs is "<current dir>/src".'
)

use_wheel = OptionMaker(
'--use-wheel',
dest='use_wheel',
Expand Down
23 changes: 3 additions & 20 deletions pip/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pip.req import InstallRequirement, RequirementSet, parse_requirements
from pip.log import logger
from pip.locations import (src_prefix, virtualenv_no_global, distutils_scheme,
from pip.locations import (virtualenv_no_global, distutils_scheme,
build_prefix)
from pip.basecommand import Command
from pip.index import PackageFinder
Expand Down Expand Up @@ -43,16 +43,7 @@ def __init__(self, *args, **kw):

cmd_opts = self.cmd_opts

cmd_opts.add_option(
'-e', '--editable',
dest='editables',
action='append',
default=[],
metavar='path/url',
help=('Install a project in editable mode (i.e. setuptools '
'"develop mode") from a local project path or a VCS url.'),
)

cmd_opts.add_option(cmdoptions.editable.make())
cmd_opts.add_option(cmdoptions.requirements.make())
cmd_opts.add_option(cmdoptions.build_dir.make())

Expand All @@ -73,15 +64,7 @@ def __init__(self, *args, **kw):
)

cmd_opts.add_option(cmdoptions.download_cache.make())

cmd_opts.add_option(
'--src', '--source', '--source-dir', '--source-directory',
dest='src_dir',
metavar='dir',
default=src_prefix,
help='Directory to check out editable projects into. '
'The default in a virtualenv is "<venv path>/src". '
'The default for global installs is "<current dir>/src".')
cmd_opts.add_option(cmdoptions.src.make())

cmd_opts.add_option(
'-U', '--upgrade',
Expand Down
19 changes: 12 additions & 7 deletions pip/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class WheelCommand(Command):
usage = """
%prog [options] <requirement specifier> ...
%prog [options] -r <requirements file> ...
%prog [options] <vcs project url> ...
%prog [options] <local project path> ...
%prog [options] [-e] <vcs project url> ...
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""

summary = 'Build wheels from your requirements.'
Expand All @@ -61,8 +61,10 @@ def __init__(self, *args, **kw):
metavar='options',
action='append',
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.")
cmd_opts.add_option(cmdoptions.editable.make())
cmd_opts.add_option(cmdoptions.requirements.make())
cmd_opts.add_option(cmdoptions.download_cache.make())
cmd_opts.add_option(cmdoptions.src.make())
cmd_opts.add_option(cmdoptions.no_deps.make())
cmd_opts.add_option(cmdoptions.build_dir.make())

Expand Down Expand Up @@ -158,7 +160,7 @@ def run(self, options, args):
options.build_dir = os.path.abspath(options.build_dir)
requirement_set = RequirementSet(
build_dir=options.build_dir,
src_dir=None,
src_dir=options.src_dir,
download_dir=None,
download_cache=options.download_cache,
ignore_dependencies=options.ignore_dependencies,
Expand All @@ -175,16 +177,19 @@ def run(self, options, args):
for name in args:
requirement_set.add_requirement(
InstallRequirement.from_line(name, None))

for name in options.editables:
requirement_set.add_requirement(
InstallRequirement.from_editable(
name,
default_vcs=options.default_vcs
)
)
for filename in options.requirements:
for req in parse_requirements(
filename,
finder=finder,
options=options,
session=session):
if req.editable:
logger.notify("ignoring %s" % req.url)
continue
requirement_set.add_requirement(req)

# fail if no requirements
Expand Down
11 changes: 10 additions & 1 deletion pip/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,16 @@ def build(self):

reqset = self.requirement_set.requirements.values()

buildset = [req for req in reqset if not req.is_wheel]
buildset = []
for req in reqset:
if req.is_wheel:
logger.notify(
'Skipping %s, due to already being wheel.' % req.name)
elif req.editable:
logger.notify(
'Skipping %s, due to being editable' % req.name)
else:
buildset.append(req)

if not buildset:
return True
Expand Down
1 change: 1 addition & 0 deletions tests/data/src/requires_simple/requires_simple/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#
6 changes: 6 additions & 0 deletions tests/data/src/requires_simple/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from setuptools import setup, find_packages

setup(name='requires_simple',
version='0.1',
install_requires=['simple==1.0']
)
53 changes: 16 additions & 37 deletions tests/functional/test_wheel.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""'pip wheel' tests"""
import os
import sys
import textwrap

from os.path import exists

from pip.download import path_to_url as path_to_url_d
from pip.locations import write_delete_marker_file
from pip.status_codes import PREVIOUS_BUILD_DIR_ERROR
from tests.lib import pyversion, path_to_url
from tests.lib import pyversion


def test_pip_wheel_fails_without_wheel(script, data):
Expand Down Expand Up @@ -50,6 +47,20 @@ def test_pip_wheel_downloads_wheels(script, data):
assert "Saved" in result.stdout, result.stdout


def test_pip_wheel_builds_editable_deps(script, data):
"""
Test 'pip wheel' finds and builds dependencies of editables
"""
script.pip('install', 'wheel')
editable_path = os.path.join(data.src, 'requires_simple')
result = script.pip(
'wheel', '--no-index', '-f', data.find_links, '-e', editable_path
)
wheel_file_name = 'simple-1.0-py%s-none-any.whl' % pyversion[0]
wheel_file_path = script.scratch / 'wheelhouse' / wheel_file_name
assert wheel_file_path in result.files_created, result.stdout


def test_pip_wheel_fail(script, data):
"""
Test 'pip wheel' failure.
Expand All @@ -70,38 +81,6 @@ def test_pip_wheel_fail(script, data):
assert result.returncode != 0


def test_pip_wheel_ignore_wheels_editables(script, data):
"""
Test 'pip wheel' ignores editables
"""
script.pip('install', 'wheel')

local_wheel = '%s/simple.dist-0.1-py2.py3-none-any.whl' % data.find_links
local_editable = data.packages.join("FSPkg")
script.scratch_path.join("reqs.txt").write(textwrap.dedent("""\
%s
-e %s
simple
""" % (local_wheel, local_editable)))
result = script.pip(
'wheel', '--no-index', '-f', data.find_links, '-r',
script.scratch_path / 'reqs.txt',
)
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0]
wheel_file_path = script.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
ignore_editable = "ignoring %s" % path_to_url(local_editable)
# TODO: understand this divergence
if sys.platform == 'win32':
ignore_editable = "ignoring %s" % path_to_url_d(local_editable)
assert ignore_editable in result.stdout, result.stdout


def test_no_clean_option_blocks_cleaning_after_wheel(script, data):
"""
Test --no-clean option blocks cleaning after wheel build
Expand Down Expand Up @@ -153,4 +132,4 @@ def test_pip_wheel_fail_cause_of_previous_build_dir(script, data):
)

# Then I see that the error code is the right one
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR, result
4 changes: 4 additions & 0 deletions tests/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ def packages2(self):
def packages3(self):
return self.root.join("packages3")

@property
def src(self):
return self.root.join("src")

@property
def indexes(self):
return self.root.join("indexes")
Expand Down
27 changes: 26 additions & 1 deletion tests/unit/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os

import pytest
from mock import patch
from mock import patch, Mock

from pip._vendor import pkg_resources
from pip import wheel
Expand Down Expand Up @@ -283,3 +283,28 @@ def test_dist_info_contains_empty_dir(self, data, tmpdir):
self.assert_installed()
assert not os.path.isdir(
os.path.join(self.dest_dist_info, 'empty_dir'))


class TestWheelBuilder(object):

@patch('pip.log.Logger.log')
@patch('pip.wheel.WheelBuilder._build_one')
def test_skip_building_wheels(self, mock_build_one, mock_log):
wheel_req = Mock(is_wheel=True, editable=False)
reqset = Mock(requirements=Mock(values=lambda: [wheel_req]))
wb = wheel.WheelBuilder(reqset, Mock(), '/wheel/dir')
wb.build()
name, args, kwargs = mock_log.mock_calls[0]
assert "due to already being wheel" in args[1]
assert mock_build_one.mock_calls == []

@patch('pip.log.Logger.log')
@patch('pip.wheel.WheelBuilder._build_one')
def test_skip_building_editables(self, mock_build_one, mock_log):
editable_req = Mock(editable=True, is_wheel=False)
reqset = Mock(requirements=Mock(values=lambda: [editable_req]))
wb = wheel.WheelBuilder(reqset, Mock(), '/wheel/dir')
wb.build()
name, args, kwargs = mock_log.mock_calls[0]
assert "due to being editable" in args[1]
assert mock_build_one.mock_calls == []