Created
January 7, 2022 13:36
-
-
Save cosmicexplorer/008bafbde6dbdb0e01a322196a2568de to your computer and use it in GitHub Desktop.
attempt to make pip install --report work the same as pip download --report
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py | |
index eedb1ff5d..804bc11ff 100644 | |
--- a/src/pip/_internal/commands/install.py | |
+++ b/src/pip/_internal/commands/install.py | |
@@ -17,6 +17,7 @@ from pip._internal.cli.req_command import ( | |
with_cleanup, | |
) | |
from pip._internal.cli.status_codes import ERROR, SUCCESS | |
+from pip._internal.commands.download import ResolutionResult, ResolvedCandidate | |
from pip._internal.exceptions import CommandError, InstallationError | |
from pip._internal.locations import get_scheme | |
from pip._internal.metadata import get_environment | |
@@ -45,6 +46,16 @@ from pip._internal.wheel_builder import ( | |
build, | |
should_build_for_install_command, | |
) | |
+from pip._internal.resolution.resolvelib.candidates import ( | |
+ LinkCandidate, | |
+ RequiresPythonCandidate, | |
+) | |
+from pip._internal.resolution.resolvelib.requirements import ( | |
+ ExplicitRequirement, | |
+ RequiresPythonRequirement, | |
+) | |
+from pip._internal.req.req_install import produce_exact_version_specifier | |
+from pip._internal.models.link import LinkWithSource, URLDownloadInfo | |
logger = getLogger(__name__) | |
@@ -223,6 +234,28 @@ class InstallCommand(RequirementCommand): | |
help="Do not warn about broken dependencies", | |
) | |
+ self.cmd_opts.add_option( | |
+ "--dry-run", | |
+ dest="dry_run", | |
+ action="store_true", | |
+ help=( | |
+ "Avoid actually downloading wheels or sdists. " | |
+ "Intended to be used with --report." | |
+ ), | |
+ ) | |
+ | |
+ self.cmd_opts.add_option( | |
+ "--report", | |
+ "--resolution-report", | |
+ dest="json_report_file", | |
+ metavar="file", | |
+ default=None, | |
+ help=( | |
+ "Print a JSON object representing the resolve into <file>. " | |
+ "Often used with --dry-run." | |
+ ), | |
+ ) | |
+ | |
self.cmd_opts.add_option(cmdoptions.no_binary()) | |
self.cmd_opts.add_option(cmdoptions.only_binary()) | |
self.cmd_opts.add_option(cmdoptions.prefer_binary()) | |
@@ -339,6 +372,101 @@ class InstallCommand(RequirementCommand): | |
reqs, check_supported_wheels=not options.target_dir | |
) | |
+ # Reconstruct the input requirements provided to the resolve. | |
+ input_requirements: List[str] = [] | |
+ for ireq in reqs: | |
+ if ireq.req: | |
+ # If the initial requirement string contained a url (retained in | |
+ # InstallRequirement.link), add it back to the requirement string | |
+ # included in the JSON report. | |
+ if ireq.link: | |
+ req_string = f"{ireq.req}@{ireq.link.url}" | |
+ else: | |
+ req_string = str(ireq.req) | |
+ else: | |
+ assert ireq.link | |
+ req_string = ireq.link.url | |
+ | |
+ input_requirements.append(req_string) | |
+ | |
+ # Scan all the elements of the resulting `RequirementSet` and map it back to all | |
+ # the install candidates preserved by `RequirementSetWithCandidates`. | |
+ resolution_result = ResolutionResult( | |
+ input_requirements=tuple(input_requirements) | |
+ ) | |
+ for candidate in requirement_set.candidates.mapping.values(): | |
+ # This will occur for the python version requirement, for example. | |
+ if candidate.name not in requirement_set.requirements: | |
+ if isinstance(candidate, RequiresPythonCandidate): | |
+ assert resolution_result.python_version is None | |
+ resolution_result.python_version = produce_exact_version_specifier( | |
+ str(candidate.version) | |
+ ) | |
+ continue | |
+ raise TypeError( | |
+ f"unknown candidate not found in requirement set: {candidate}" | |
+ ) | |
+ | |
+ req = requirement_set.requirements[candidate.name] | |
+ assert req.name is not None | |
+ assert req.link is not None | |
+ assert req.name not in resolution_result.candidates | |
+ | |
+ # Scan the dependencies of the installation candidates, which cover both | |
+ # normal dependencies as well as Requires-Python information. | |
+ requires_python: Optional[SpecifierSet] = None | |
+ dependencies: List[Requirement] = [] | |
+ for maybe_dep in candidate.iter_dependencies(with_requires=True): | |
+ # It's unclear why `.iter_dependencies()` may occasionally yield `None`. | |
+ if maybe_dep is None: | |
+ continue | |
+ # There will only ever be one of these for each candidate, if any. We | |
+ # extract the version specifier. | |
+ if isinstance(maybe_dep, RequiresPythonRequirement): | |
+ requires_python = maybe_dep.specifier | |
+ continue | |
+ | |
+ # Convert the 2020 resolver-internal Requirement subclass instance into | |
+ # a `packaging.requirements.Requirement` instance. | |
+ maybe_req = maybe_dep.as_serializable_requirement() | |
+ if maybe_req is None: | |
+ continue | |
+ | |
+ # For `ExplicitRequirement`s only, we want to make sure we propagate any | |
+ # source URL into a dependency's `packaging.requirements.Requirement` | |
+ # instance. | |
+ if isinstance(maybe_dep, ExplicitRequirement): | |
+ dep_candidate = maybe_dep.candidate | |
+ if maybe_req.url is None and isinstance( | |
+ dep_candidate, LinkCandidate | |
+ ): | |
+ assert dep_candidate.source_link is not None | |
+ maybe_req = Requirement( | |
+ f"{maybe_req}@{dep_candidate.source_link.url}" | |
+ ) | |
+ | |
+ dependencies.append(maybe_req) | |
+ | |
+ # Mutate the candidates dictionary to add this candidate after processing | |
+ # any dependencies and python version requirement. | |
+ resolution_result.candidates[req.name] = ResolvedCandidate( | |
+ req=candidate.as_serializable_requirement(), | |
+ download_info=URLDownloadInfo.from_link_with_source( | |
+ LinkWithSource( | |
+ req.link, | |
+ source_dir=req.source_dir, | |
+ link_is_in_wheel_cache=req.original_link_is_in_wheel_cache, | |
+ ) | |
+ ), | |
+ dependencies=tuple(dependencies), | |
+ requires_python=requires_python, | |
+ ) | |
+ | |
+ # Write a simplified representation of the resolution to stdout. | |
+ write_output(resolution_result.as_basic_log(options.json_report_file)) | |
+ with open(options.json_report_file, "w") as f: | |
+ json.dump(resolution_result.as_json(), f, indent=4) | |
+ | |
try: | |
pip_req = requirement_set.get_requirement("pip") | |
except KeyError: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment