Skip to content

Commit

Permalink
Add new option: pip wheel --save-wheel-names (#6377)
Browse files Browse the repository at this point in the history
  • Loading branch information
pradyunsg committed Oct 19, 2019
2 parents f864903 + 211fd55 commit bcad1b1
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 2 deletions.
1 change: 1 addition & 0 deletions news/6340.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new option ``--save-wheel-names <filename>`` to ``pip wheel`` that writes the names of the resulting wheels to the given filename.
40 changes: 40 additions & 0 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ def __init__(self, *args, **kw):
cmd_opts.add_option(cmdoptions.no_clean())
cmd_opts.add_option(cmdoptions.require_hashes())

cmd_opts.add_option(
'--save-wheel-names',
dest='path_to_wheelnames',
action='store',
metavar='path',
help=("Store the filenames of the built or downloaded wheels "
"in a new file of given path. Filenames are separated "
"by new line and file ends with new line"),
)

index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
self.parser,
Expand All @@ -110,6 +120,28 @@ def __init__(self, *args, **kw):
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)

def save_wheelnames(
self,
links_filenames,
path_to_wheelnames,
wheel_filenames,
):
if path_to_wheelnames is None:
return

entries_to_save = wheel_filenames + links_filenames
entries_to_save = [
filename + '\n' for filename in entries_to_save
if filename.endswith('whl')
]
try:
with open(path_to_wheelnames, 'w') as f:
f.writelines(entries_to_save)
except EnvironmentError as e:
logger.error('Cannot write to the given path: %s\n%s' %
(path_to_wheelnames, e))
raise

def run(self, options, args):
# type: (Values, List[Any]) -> None
cmdoptions.check_install_build_global(options)
Expand Down Expand Up @@ -163,10 +195,18 @@ def run(self, options, args):
build_options=options.build_options or [],
global_options=options.global_options or [],
no_clean=options.no_clean,
path_to_wheelnames=options.path_to_wheelnames
)
build_failures = wb.build(
requirement_set.requirements.values(),
)
self.save_wheelnames(
[req.link.filename for req in
requirement_set.successfully_downloaded
if req.link is not None],
wb.path_to_wheelnames,
wb.wheel_filenames,
)
if len(build_failures) != 0:
raise CommandError(
"Failed to build one or more wheels"
Expand Down
12 changes: 10 additions & 2 deletions src/pip/_internal/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
if MYPY_CHECK_RUNNING:
from typing import (
Dict, List, Optional, Sequence, Mapping, Tuple, IO, Text, Any,
Iterable, Callable, Set,
Iterable, Callable, Set, Union,
)
from pip._vendor.packaging.requirements import Requirement
from pip._internal.req.req_install import InstallRequirement
Expand Down Expand Up @@ -908,7 +908,8 @@ def __init__(
build_options=None, # type: Optional[List[str]]
global_options=None, # type: Optional[List[str]]
check_binary_allowed=None, # type: Optional[BinaryAllowedPredicate]
no_clean=False # type: bool
no_clean=False, # type: bool
path_to_wheelnames=None, # type: Optional[Union[bytes, Text]]
):
# type: (...) -> None
if check_binary_allowed is None:
Expand All @@ -924,6 +925,10 @@ def __init__(
self.global_options = global_options or []
self.check_binary_allowed = check_binary_allowed
self.no_clean = no_clean
# path where to save built names of built wheels
self.path_to_wheelnames = path_to_wheelnames
# file names of built wheel names
self.wheel_filenames = [] # type: List[Union[bytes, Text]]

def _build_one(self, req, output_dir, python_tag=None):
"""Build one wheel.
Expand Down Expand Up @@ -1134,6 +1139,9 @@ def build(
)
if wheel_file:
build_success.append(req)
self.wheel_filenames.append(
os.path.relpath(wheel_file, output_dir)
)
if should_unpack:
# XXX: This is mildly duplicative with prepare_files,
# but not close enough to pull out to a single common
Expand Down
63 changes: 63 additions & 0 deletions tests/functional/test_wheel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""'pip wheel' tests"""
import os
import re
import stat
from os.path import exists

import pytest
Expand Down Expand Up @@ -255,3 +256,65 @@ def test_legacy_wheels_are_not_confused_with_other_files(script, tmpdir, data):
wheel_file_name = 'simplewheel-1.0-py%s-none-any.whl' % pyversion[0]
wheel_file_path = script.scratch / wheel_file_name
assert wheel_file_path in result.files_created, result.stdout


def test_pip_option_save_wheel_name(script, data):
"""Check if the option saves the filenames of built wheels
"""
script.pip(
'wheel', '--no-index', '-f', data.find_links,
'require_simple==1.0',
'--save-wheel-name', 'wheelnames',
)

wheel_file_names = [
'require_simple-1.0-py%s-none-any.whl' % pyversion[0],
'simple-3.0-py%s-none-any.whl' % pyversion[0],
]
wheelnames_path = script.scratch_path / 'wheelnames'
with open(wheelnames_path, 'r') as wheelnames_file:
wheelnames_entries = (wheelnames_file.read()).splitlines()
assert wheel_file_names == wheelnames_entries


def test_pip_option_save_wheel_name_Permission_error(script, data):

temp_file = script.base_path / 'scratch' / 'wheelnames'

wheel_file_names = [
'require_simple-1.0-py%s-none-any.whl' % pyversion[0],
'simple-3.0-py%s-none-any.whl' % pyversion[0],
]

script.pip(
'wheel', '--no-index', '-f', data.find_links,
'require_simple==1.0',
'--save-wheel-name', 'wheelnames',
)
os.chmod(temp_file, stat.S_IREAD)
result = script.pip(
'wheel', '--no-index', '-f', data.find_links,
'require_simple==1.0',
'--save-wheel-name', 'wheelnames', expect_error=True,
)
os.chmod(temp_file, stat.S_IREAD | stat.S_IWRITE)

assert "ERROR: Cannot write to the given path: wheelnames\n" \
"[Errno 13] Permission denied: 'wheelnames'\n" in result.stderr

with open(temp_file) as f:
result = f.read().splitlines()
# check that file stays same
assert result == wheel_file_names


def test_pip_option_save_wheel_name_error_during_build(script, data):
script.pip(
'wheel', '--no-index', '--save-wheel-name', 'wheelnames',
'-f', data.find_links, 'wheelbroken==0.1',
expect_error=True,
)
wheelnames_path = script.base_path / 'scratch' / 'wheelnames'
with open(wheelnames_path) as f:
wheelnames = f.read().splitlines()
assert wheelnames == []
28 changes: 28 additions & 0 deletions tests/unit/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pip._vendor.packaging.requirements import Requirement

from pip._internal import pep425tags, wheel
from pip._internal.commands.wheel import WheelCommand
from pip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel
from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
Expand Down Expand Up @@ -848,3 +849,30 @@ def test_rehash(self, tmpdir):
h, length = wheel.rehash(self.test_file)
assert length == str(self.test_file_len)
assert h == self.test_file_hash_encoded


class TestWheelCommand(object):

def test_save_wheelnames(self, tmpdir):
wheel_filenames = ['Flask-1.1.dev0-py2.py3-none-any.whl']
links_filenames = [
'flask',
'Werkzeug-0.15.4-py2.py3-none-any.whl',
'Jinja2-2.10.1-py2.py3-none-any.whl',
'itsdangerous-1.1.0-py2.py3-none-any.whl',
'Click-7.0-py2.py3-none-any.whl'
]

expected = wheel_filenames + links_filenames[1:]
expected = [filename + '\n' for filename in expected]
temp_file = tmpdir.joinpath('wheelfiles')

WheelCommand('name', 'summary').save_wheelnames(
links_filenames,
temp_file,
wheel_filenames
)

with open(temp_file, 'r') as f:
test_content = f.readlines()
assert test_content == expected

0 comments on commit bcad1b1

Please sign in to comment.