Skip to content

Add new option: pip wheel --save-wheel-names #6377

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 3 commits into from
Oct 19, 2019
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
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 @@ -55,7 +55,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 @@ -905,7 +905,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 @@ -921,6 +922,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 @@ -1139,6 +1144,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