diff --git a/news/6852.feature b/news/6852.feature new file mode 100644 index 00000000000..30097e82a8f --- /dev/null +++ b/news/6852.feature @@ -0,0 +1,5 @@ +Cache wheels that ``pip wheel`` built locally, matching what +``pip install`` does. This particularly helps performance in workflows where +``pip wheel`` is used for `building before installing +`_. +Users desiring the original behavior can use ``pip wheel --no-cache-dir``. diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py index 71d9765ce42..ac4bf029f14 100644 --- a/src/pip/_internal/wheel.py +++ b/src/pip/_internal/wheel.py @@ -1102,7 +1102,7 @@ def build( :param should_unpack: If True, after building the wheel, unpack it and replace the sdist with the unpacked version in preparation for installation. - :return: True if all the wheels built correctly. + :return: The list of InstallRequirement that failed to build. """ # pip install uses should_unpack=True. # pip install never provides a _wheel_dir. @@ -1124,19 +1124,15 @@ def build( ): continue - # Determine where the wheel should go. - if should_unpack: - if ( - cache_available and - should_cache(req, self.check_binary_allowed) - ): - output_dir = self.wheel_cache.get_path_for_link(req.link) - else: - output_dir = self.wheel_cache.get_ephem_path_for_link( - req.link - ) + if ( + cache_available and + should_cache(req, self.check_binary_allowed) + ): + output_dir = self.wheel_cache.get_path_for_link(req.link) else: - output_dir = self._wheel_dir + output_dir = self.wheel_cache.get_ephem_path_for_link( + req.link + ) buildset.append((req, output_dir)) @@ -1174,10 +1170,6 @@ def build( python_tag=python_tag, ) 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 @@ -1202,6 +1194,25 @@ def build( assert req.link.is_wheel # extract the wheel into the dir unpack_file(req.link.file_path, req.source_dir) + else: + # copy from cache to target directory + try: + ensure_dir(self._wheel_dir) + shutil.copy( + os.path.join(output_dir, wheel_file), + self._wheel_dir, + ) + except OSError as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, e, + ) + build_failure.append(req) + continue + self.wheel_filenames.append( + os.path.relpath(wheel_file, output_dir) + ) + build_success.append(req) else: build_failure.append(req) diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 92f2c9ef479..b123d0693cd 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -62,6 +62,30 @@ def test_pip_wheel_success(script, data): assert "Successfully built simple" in result.stdout, result.stdout +def test_pip_wheel_build_cache(script, data): + """ + Test 'pip wheel' builds and caches. + """ + result = script.pip( + 'wheel', '--no-index', '-f', data.find_links, + 'simple==3.0', + ) + wheel_file_name = 'simple-3.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 + assert "Successfully built simple" in result.stdout, result.stdout + # remove target file + (script.scratch_path / wheel_file_name).unlink() + # pip wheel again and test that no build occurs since + # we get the wheel from cache + result = script.pip( + 'wheel', '--no-index', '-f', data.find_links, + 'simple==3.0', + ) + assert wheel_file_path in result.files_created, result.stdout + assert "Successfully built simple" not in result.stdout, result.stdout + + def test_basic_pip_wheel_downloads_wheels(script, data): """ Test 'pip wheel' downloads wheels