diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 9e4fec70b..0f39b90d8 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -74,6 +74,10 @@ def build_wheels( builder, # type: WheelBuilder pep517_requirements, # type: List[InstallRequirement] legacy_requirements, # type: List[InstallRequirement] + wheel_cache, # type: WheelCache + build_options, # type: List[str] + global_options, # type: List[str] + check_binary_allowed, # type: BinaryAllowedPredicate ): # type: (...) -> List[InstallRequirement] """ @@ -86,6 +90,10 @@ def build_wheels( _, build_failures = builder.build( pep517_requirements, should_unpack=True, + wheel_cache=wheel_cache, + build_options=build_options, + global_options=global_options, + check_binary_allowed=check_binary_allowed, ) if should_build_legacy: @@ -95,6 +103,10 @@ def build_wheels( builder.build( legacy_requirements, should_unpack=True, + wheel_cache=wheel_cache, + build_options=build_options, + global_options=global_options, + check_binary_allowed=check_binary_allowed, ) return build_failures @@ -396,16 +408,15 @@ class InstallCommand(RequirementCommand): else: legacy_requirements.append(req) - wheel_builder = WheelBuilder( - preparer, wheel_cache, - build_options=[], global_options=[], - check_binary_allowed=check_binary_allowed, - ) - + wheel_builder = WheelBuilder(preparer) build_failures = build_wheels( builder=wheel_builder, pep517_requirements=pep517_requirements, legacy_requirements=legacy_requirements, + wheel_cache=wheel_cache, + build_options=[], + global_options=[], + check_binary_allowed=check_binary_allowed, ) # If we're using PEP 517, we cannot do a direct install diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index d3fe34308..edf006714 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -158,14 +158,13 @@ class WheelCommand(RequirementCommand): resolver.resolve(requirement_set) # build wheels - wb = WheelBuilder( - preparer, wheel_cache, - build_options=options.build_options or [], - global_options=options.global_options or [], - ) + wb = WheelBuilder(preparer) build_successes, build_failures = wb.build( requirement_set.requirements.values(), should_unpack=False, + wheel_cache=wheel_cache, + build_options=options.build_options or [], + global_options=options.global_options or [], ) for req in build_successes: assert req.link and req.link.is_wheel diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 1e425ad9b..b992bd676 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -160,117 +160,115 @@ def _always_true(_): return True +def _build_one( + req, # type: InstallRequirement + output_dir, # type: str + build_options, # type: List[str] + global_options, # type: List[str] +): + # type: (...) -> Optional[str] + """Build one wheel. + + :return: The filename of the built wheel, or None if the build failed. + """ + try: + ensure_dir(output_dir) + except OSError as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, e, + ) + return None + + # Install build deps into temporary directory (PEP 518) + with req.build_env: + return _build_one_inside_env( + req, output_dir, build_options, global_options + ) + + +def _build_one_inside_env( + req, # type: InstallRequirement + output_dir, # type: str + build_options, # type: List[str] + global_options, # type: List[str] +): + # type: (...) -> Optional[str] + with TempDirectory(kind="wheel") as temp_dir: + if req.use_pep517: + wheel_path = build_wheel_pep517( + name=req.name, + backend=req.pep517_backend, + metadata_directory=req.metadata_directory, + build_options=build_options, + tempd=temp_dir.path, + ) + else: + wheel_path = build_wheel_legacy( + name=req.name, + setup_py_path=req.setup_py_path, + source_dir=req.unpacked_source_directory, + global_options=global_options, + build_options=build_options, + tempd=temp_dir.path, + ) + + if wheel_path is not None: + wheel_name = os.path.basename(wheel_path) + dest_path = os.path.join(output_dir, wheel_name) + try: + wheel_hash, length = hash_file(wheel_path) + shutil.move(wheel_path, dest_path) + logger.info('Created wheel for %s: ' + 'filename=%s size=%d sha256=%s', + req.name, wheel_name, length, + wheel_hash.hexdigest()) + logger.info('Stored in directory: %s', output_dir) + return dest_path + except Exception as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, e, + ) + # Ignore return, we can't do anything else useful. + _clean_one(req, global_options) + return None + + +def _clean_one(req, global_options): + # type: (InstallRequirement, List[str]) -> bool + clean_args = make_setuptools_clean_args( + req.setup_py_path, + global_options=global_options, + ) + + logger.info('Running setup.py clean for %s', req.name) + try: + call_subprocess(clean_args, cwd=req.source_dir) + return True + except Exception: + logger.error('Failed cleaning build dir for %s', req.name) + return False + + class WheelBuilder(object): """Build wheels from a RequirementSet.""" def __init__( self, preparer, # type: RequirementPreparer - wheel_cache, # type: WheelCache - build_options=None, # type: Optional[List[str]] - global_options=None, # type: Optional[List[str]] - check_binary_allowed=None, # type: Optional[BinaryAllowedPredicate] ): # type: (...) -> None - if check_binary_allowed is None: - # Binaries allowed by default. - check_binary_allowed = _always_true - self.preparer = preparer - self.wheel_cache = wheel_cache - - self.build_options = build_options or [] - self.global_options = global_options or [] - self.check_binary_allowed = check_binary_allowed - - def _build_one( - self, - req, # type: InstallRequirement - output_dir, # type: str - ): - # type: (...) -> Optional[str] - """Build one wheel. - - :return: The filename of the built wheel, or None if the build failed. - """ - try: - ensure_dir(output_dir) - except OSError as e: - logger.warning( - "Building wheel for %s failed: %s", - req.name, e, - ) - return None - - # Install build deps into temporary directory (PEP 518) - with req.build_env: - return self._build_one_inside_env(req, output_dir) - - def _build_one_inside_env( - self, - req, # type: InstallRequirement - output_dir, # type: str - ): - # type: (...) -> Optional[str] - with TempDirectory(kind="wheel") as temp_dir: - if req.use_pep517: - wheel_path = build_wheel_pep517( - name=req.name, - backend=req.pep517_backend, - metadata_directory=req.metadata_directory, - build_options=self.build_options, - tempd=temp_dir.path, - ) - else: - wheel_path = build_wheel_legacy( - name=req.name, - setup_py_path=req.setup_py_path, - source_dir=req.unpacked_source_directory, - global_options=self.global_options, - build_options=self.build_options, - tempd=temp_dir.path, - ) - - if wheel_path is not None: - wheel_name = os.path.basename(wheel_path) - dest_path = os.path.join(output_dir, wheel_name) - try: - wheel_hash, length = hash_file(wheel_path) - shutil.move(wheel_path, dest_path) - logger.info('Created wheel for %s: ' - 'filename=%s size=%d sha256=%s', - req.name, wheel_name, length, - wheel_hash.hexdigest()) - logger.info('Stored in directory: %s', output_dir) - return dest_path - except Exception as e: - logger.warning( - "Building wheel for %s failed: %s", - req.name, e, - ) - # Ignore return, we can't do anything else useful. - self._clean_one(req) - return None - - def _clean_one(self, req): - # type: (InstallRequirement) -> bool - clean_args = make_setuptools_clean_args( - req.setup_py_path, - global_options=self.global_options, - ) - - logger.info('Running setup.py clean for %s', req.name) - try: - call_subprocess(clean_args, cwd=req.source_dir) - return True - except Exception: - logger.error('Failed cleaning build dir for %s', req.name) - return False def build( self, requirements, # type: Iterable[InstallRequirement] should_unpack, # type: bool + wheel_cache, # type: WheelCache + build_options, # type: List[str] + global_options, # type: List[str] + check_binary_allowed=None, # type: Optional[BinaryAllowedPredicate] ): # type: (...) -> BuildResult """Build wheels. @@ -281,10 +279,14 @@ class WheelBuilder(object): :return: The list of InstallRequirement that succeeded to build and the list of InstallRequirement that failed to build. """ + if check_binary_allowed is None: + # Binaries allowed by default. + check_binary_allowed = _always_true + buildset = _collect_buildset( requirements, - wheel_cache=self.wheel_cache, - check_binary_allowed=self.check_binary_allowed, + wheel_cache=wheel_cache, + check_binary_allowed=check_binary_allowed, need_wheel=not should_unpack, ) if not buildset: @@ -302,7 +304,9 @@ class WheelBuilder(object): with indent_log(): build_successes, build_failures = [], [] for req, cache_dir in buildset: - wheel_file = self._build_one(req, cache_dir) + wheel_file = _build_one( + req, cache_dir, build_options, global_options + ) if wheel_file: # Update the link for this. req.link = Link(path_to_url(wheel_file)) diff --git a/tests/unit/test_command_install.py b/tests/unit/test_command_install.py index 9d862792b..36b3ca73f 100644 --- a/tests/unit/test_command_install.py +++ b/tests/unit/test_command_install.py @@ -1,7 +1,7 @@ import errno import pytest -from mock import Mock, call, patch +from mock import Mock, patch from pip._vendor.packaging.requirements import Requirement from pip._internal.commands.install import ( @@ -24,8 +24,11 @@ class TestWheelCache: """ Return: (mock_calls, return_value). """ + built_reqs = [] + def build(reqs, **kwargs): # Fail the first requirement. + built_reqs.append(reqs) return ([], [reqs[0]]) builder = Mock() @@ -35,24 +38,25 @@ class TestWheelCache: builder=builder, pep517_requirements=pep517_requirements, legacy_requirements=legacy_requirements, + wheel_cache=Mock(cache_dir=None), + build_options=[], + global_options=[], + check_binary_allowed=None, ) - return (builder.build.mock_calls, build_failures) + return (built_reqs, build_failures) @patch('pip._internal.commands.install.is_wheel_installed') def test_build_wheels__wheel_installed(self, is_wheel_installed): is_wheel_installed.return_value = True - mock_calls, build_failures = self.check_build_wheels( + built_reqs, build_failures = self.check_build_wheels( pep517_requirements=['a', 'b'], legacy_requirements=['c', 'd'], ) # Legacy requirements were built. - assert mock_calls == [ - call(['a', 'b'], should_unpack=True), - call(['c', 'd'], should_unpack=True), - ] + assert built_reqs == [['a', 'b'], ['c', 'd']] # Legacy build failures are not included in the return value. assert build_failures == ['a'] @@ -61,15 +65,13 @@ class TestWheelCache: def test_build_wheels__wheel_not_installed(self, is_wheel_installed): is_wheel_installed.return_value = False - mock_calls, build_failures = self.check_build_wheels( + built_reqs, build_failures = self.check_build_wheels( pep517_requirements=['a', 'b'], legacy_requirements=['c', 'd'], ) # Legacy requirements were not built. - assert mock_calls == [ - call(['a', 'b'], should_unpack=True), - ] + assert built_reqs == [['a', 'b']] assert build_failures == ['a'] diff --git a/tests/unit/test_wheel_builder.py b/tests/unit/test_wheel_builder.py index 9d0953518..9d2c80303 100644 --- a/tests/unit/test_wheel_builder.py +++ b/tests/unit/test_wheel_builder.py @@ -186,15 +186,18 @@ def test_format_command_result__empty_output(caplog, log_level): class TestWheelBuilder(object): def test_skip_building_wheels(self, caplog): - wb = wheel_builder.WheelBuilder( - preparer=Mock(), - wheel_cache=Mock(cache_dir=None), - ) + wb = wheel_builder.WheelBuilder(preparer=Mock()) wb._build_one = mock_build_one = Mock() wheel_req = Mock(is_wheel=True, editable=False, constraint=False) with caplog.at_level(logging.INFO): - wb.build([wheel_req], should_unpack=False) + wb.build( + [wheel_req], + should_unpack=False, + wheel_cache=Mock(cache_dir=None), + build_options=[], + global_options=[], + ) assert "due to already being wheel" in caplog.text assert mock_build_one.mock_calls == []