Skip to content

Commit b0b0573

Browse files
committed
Merge pull request #1787 from qwcode/wheel_editables
build wheels for dependencies of editables
2 parents 64244a9 + 52299a7 commit b0b0573

File tree

10 files changed

+102
-67
lines changed

10 files changed

+102
-67
lines changed

CHANGES.txt

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
* Fixed :issue:`1769`. `pip wheel` now returns an error code if any wheels
3535
fail to build.
3636

37+
* Fixed :issue:`1775`. `pip wheel` wasn't building wheels for dependencies of
38+
editable requirements.
39+
3740

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

pip/cmdoptions.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"""
1010
import copy
1111
from optparse import OptionGroup, SUPPRESS_HELP, Option
12-
from pip.locations import build_prefix, default_log_file
12+
from pip.locations import build_prefix, default_log_file, src_prefix
1313

1414

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

289+
editable = OptionMaker(
290+
'-e', '--editable',
291+
dest='editables',
292+
action='append',
293+
default=[],
294+
metavar='path/url',
295+
help=('Install a project in editable mode (i.e. setuptools '
296+
'"develop mode") from a local project path or a VCS url.'),
297+
)
298+
299+
src = OptionMaker(
300+
'--src', '--source', '--source-dir', '--source-directory',
301+
dest='src_dir',
302+
metavar='dir',
303+
default=src_prefix,
304+
help='Directory to check out editable projects into. '
305+
'The default in a virtualenv is "<venv path>/src". '
306+
'The default for global installs is "<current dir>/src".'
307+
)
308+
289309
use_wheel = OptionMaker(
290310
'--use-wheel',
291311
dest='use_wheel',

pip/commands/install.py

+3-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from pip.req import InstallRequirement, RequirementSet, parse_requirements
66
from pip.log import logger
7-
from pip.locations import (src_prefix, virtualenv_no_global, distutils_scheme,
7+
from pip.locations import (virtualenv_no_global, distutils_scheme,
88
build_prefix)
99
from pip.basecommand import Command
1010
from pip.index import PackageFinder
@@ -42,16 +42,7 @@ def __init__(self, *args, **kw):
4242

4343
cmd_opts = self.cmd_opts
4444

45-
cmd_opts.add_option(
46-
'-e', '--editable',
47-
dest='editables',
48-
action='append',
49-
default=[],
50-
metavar='path/url',
51-
help=('Install a project in editable mode (i.e. setuptools '
52-
'"develop mode") from a local project path or a VCS url.'),
53-
)
54-
45+
cmd_opts.add_option(cmdoptions.editable.make())
5546
cmd_opts.add_option(cmdoptions.requirements.make())
5647
cmd_opts.add_option(cmdoptions.build_dir.make())
5748

@@ -72,15 +63,7 @@ def __init__(self, *args, **kw):
7263
)
7364

7465
cmd_opts.add_option(cmdoptions.download_cache.make())
75-
76-
cmd_opts.add_option(
77-
'--src', '--source', '--source-dir', '--source-directory',
78-
dest='src_dir',
79-
metavar='dir',
80-
default=src_prefix,
81-
help='Directory to check out editable projects into. '
82-
'The default in a virtualenv is "<venv path>/src". '
83-
'The default for global installs is "<current dir>/src".')
66+
cmd_opts.add_option(cmdoptions.src.make())
8467

8568
cmd_opts.add_option(
8669
'-U', '--upgrade',

pip/commands/wheel.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class WheelCommand(Command):
3434
usage = """
3535
%prog [options] <requirement specifier> ...
3636
%prog [options] -r <requirements file> ...
37-
%prog [options] <vcs project url> ...
38-
%prog [options] <local project path> ...
37+
%prog [options] [-e] <vcs project url> ...
38+
%prog [options] [-e] <local project path> ...
3939
%prog [options] <archive url/path> ..."""
4040

4141
summary = 'Build wheels from your requirements.'
@@ -61,8 +61,10 @@ def __init__(self, *args, **kw):
6161
metavar='options',
6262
action='append',
6363
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.")
64+
cmd_opts.add_option(cmdoptions.editable.make())
6465
cmd_opts.add_option(cmdoptions.requirements.make())
6566
cmd_opts.add_option(cmdoptions.download_cache.make())
67+
cmd_opts.add_option(cmdoptions.src.make())
6668
cmd_opts.add_option(cmdoptions.no_deps.make())
6769
cmd_opts.add_option(cmdoptions.build_dir.make())
6870

@@ -158,7 +160,7 @@ def run(self, options, args):
158160
options.build_dir = os.path.abspath(options.build_dir)
159161
requirement_set = RequirementSet(
160162
build_dir=options.build_dir,
161-
src_dir=None,
163+
src_dir=options.src_dir,
162164
download_dir=None,
163165
download_cache=options.download_cache,
164166
ignore_dependencies=options.ignore_dependencies,
@@ -175,16 +177,19 @@ def run(self, options, args):
175177
for name in args:
176178
requirement_set.add_requirement(
177179
InstallRequirement.from_line(name, None))
178-
180+
for name in options.editables:
181+
requirement_set.add_requirement(
182+
InstallRequirement.from_editable(
183+
name,
184+
default_vcs=options.default_vcs
185+
)
186+
)
179187
for filename in options.requirements:
180188
for req in parse_requirements(
181189
filename,
182190
finder=finder,
183191
options=options,
184192
session=session):
185-
if req.editable:
186-
logger.notify("ignoring %s" % req.url)
187-
continue
188193
requirement_set.add_requirement(req)
189194

190195
# fail if no requirements

pip/wheel.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,16 @@ def build(self):
546546

547547
reqset = self.requirement_set.requirements.values()
548548

549-
buildset = [req for req in reqset if not req.is_wheel]
549+
buildset = []
550+
for req in reqset:
551+
if req.is_wheel:
552+
logger.notify(
553+
'Skipping %s, due to already being wheel.' % req.name)
554+
elif req.editable:
555+
logger.notify(
556+
'Skipping %s, due to being editable' % req.name)
557+
else:
558+
buildset.append(req)
550559

551560
if not buildset:
552561
return True
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(name='requires_simple',
4+
version='0.1',
5+
install_requires=['simple==1.0']
6+
)

tests/functional/test_wheel.py

+16-37
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
"""'pip wheel' tests"""
22
import os
3-
import sys
4-
import textwrap
53

64
from os.path import exists
75

8-
from pip.download import path_to_url as path_to_url_d
96
from pip.locations import write_delete_marker_file
107
from pip.status_codes import PREVIOUS_BUILD_DIR_ERROR
11-
from tests.lib import pyversion, path_to_url
8+
from tests.lib import pyversion
129

1310

1411
def test_pip_wheel_fails_without_wheel(script, data):
@@ -50,6 +47,20 @@ def test_pip_wheel_downloads_wheels(script, data):
5047
assert "Saved" in result.stdout, result.stdout
5148

5249

50+
def test_pip_wheel_builds_editable_deps(script, data):
51+
"""
52+
Test 'pip wheel' finds and builds dependencies of editables
53+
"""
54+
script.pip('install', 'wheel')
55+
editable_path = os.path.join(data.src, 'requires_simple')
56+
result = script.pip(
57+
'wheel', '--no-index', '-f', data.find_links, '-e', editable_path
58+
)
59+
wheel_file_name = 'simple-1.0-py%s-none-any.whl' % pyversion[0]
60+
wheel_file_path = script.scratch / 'wheelhouse' / wheel_file_name
61+
assert wheel_file_path in result.files_created, result.stdout
62+
63+
5364
def test_pip_wheel_fail(script, data):
5465
"""
5566
Test 'pip wheel' failure.
@@ -70,38 +81,6 @@ def test_pip_wheel_fail(script, data):
7081
assert result.returncode != 0
7182

7283

73-
def test_pip_wheel_ignore_wheels_editables(script, data):
74-
"""
75-
Test 'pip wheel' ignores editables
76-
"""
77-
script.pip('install', 'wheel')
78-
79-
local_wheel = '%s/simple.dist-0.1-py2.py3-none-any.whl' % data.find_links
80-
local_editable = data.packages.join("FSPkg")
81-
script.scratch_path.join("reqs.txt").write(textwrap.dedent("""\
82-
%s
83-
-e %s
84-
simple
85-
""" % (local_wheel, local_editable)))
86-
result = script.pip(
87-
'wheel', '--no-index', '-f', data.find_links, '-r',
88-
script.scratch_path / 'reqs.txt',
89-
)
90-
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0]
91-
wheel_file_path = script.scratch / 'wheelhouse' / wheel_file_name
92-
assert wheel_file_path in result.files_created, (
93-
wheel_file_path,
94-
result.files_created,
95-
)
96-
assert "Successfully built simple" in result.stdout, result.stdout
97-
assert "Failed to build" not in result.stdout, result.stdout
98-
ignore_editable = "ignoring %s" % path_to_url(local_editable)
99-
# TODO: understand this divergence
100-
if sys.platform == 'win32':
101-
ignore_editable = "ignoring %s" % path_to_url_d(local_editable)
102-
assert ignore_editable in result.stdout, result.stdout
103-
104-
10584
def test_no_clean_option_blocks_cleaning_after_wheel(script, data):
10685
"""
10786
Test --no-clean option blocks cleaning after wheel build
@@ -153,4 +132,4 @@ def test_pip_wheel_fail_cause_of_previous_build_dir(script, data):
153132
)
154133

155134
# Then I see that the error code is the right one
156-
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR
135+
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR, result

tests/lib/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ def packages2(self):
7070
def packages3(self):
7171
return self.root.join("packages3")
7272

73+
@property
74+
def src(self):
75+
return self.root.join("src")
76+
7377
@property
7478
def indexes(self):
7579
return self.root.join("indexes")

tests/unit/test_wheel.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33

44
import pytest
5-
from mock import patch
5+
from mock import patch, Mock
66

77
from pip._vendor import pkg_resources
88
from pip import wheel
@@ -283,3 +283,28 @@ def test_dist_info_contains_empty_dir(self, data, tmpdir):
283283
self.assert_installed()
284284
assert not os.path.isdir(
285285
os.path.join(self.dest_dist_info, 'empty_dir'))
286+
287+
288+
class TestWheelBuilder(object):
289+
290+
@patch('pip.log.Logger.log')
291+
@patch('pip.wheel.WheelBuilder._build_one')
292+
def test_skip_building_wheels(self, mock_build_one, mock_log):
293+
wheel_req = Mock(is_wheel=True, editable=False)
294+
reqset = Mock(requirements=Mock(values=lambda: [wheel_req]))
295+
wb = wheel.WheelBuilder(reqset, Mock(), '/wheel/dir')
296+
wb.build()
297+
name, args, kwargs = mock_log.mock_calls[0]
298+
assert "due to already being wheel" in args[1]
299+
assert mock_build_one.mock_calls == []
300+
301+
@patch('pip.log.Logger.log')
302+
@patch('pip.wheel.WheelBuilder._build_one')
303+
def test_skip_building_editables(self, mock_build_one, mock_log):
304+
editable_req = Mock(editable=True, is_wheel=False)
305+
reqset = Mock(requirements=Mock(values=lambda: [editable_req]))
306+
wb = wheel.WheelBuilder(reqset, Mock(), '/wheel/dir')
307+
wb.build()
308+
name, args, kwargs = mock_log.mock_calls[0]
309+
assert "due to being editable" in args[1]
310+
assert mock_build_one.mock_calls == []

0 commit comments

Comments
 (0)