Repository: pypa/pip

Issue:
Add `pip install --dry-run` or similar, to get resolution result
**What's the problem this feature will solve?**

pip currently does not have any mechanism for users to get the result of pip's dependency resolution. This functionality is useful for users to be able to:

- generate something like a "lockfile"
- checking if installing a package would break the existing environment
- checking for dependency conflicts among a set of packages
- (more?)

All these can be performed today, but require installing packages to some environment and introspecting the environment for information. Since all the relevant information is available to `pip install` at run time, it would be useful to avoid hitting issues with this.

**Describe the solution you'd like**

#8032 proposes a `pip install --dry-run` option.
#7819 proposes a `pip resolve` command.
#1345 has more proposals. :)

There have likely more proposals in the issue tracker that I can't find.

**Alternative Solutions**

Let other not-pip tooling in the ecosystem provide this functionality to users. This is sub-optimal given that pip's resolver isn't exposed publicly in any way (pip's internals are not to be used like a library).

The most notable example is [`pip-tools`](https://github.com/jazzband/pip-tools/) project, which is the current best answer for any user who is seeking this functionality.

---

Note: This description has been edited by @pradyunsg in April 2020 (see edit history for details), and some *really old, outdated comments* have been hidden on this issue.

Discussion:
cosmicexplorer: Note that in https://github.com/pypa/pip/pull/10748/commits/a67c59fe8b95e3f6e50d7538b0f6d4cf674756a7 I have refactored #10748 to make its output easier to consume from a `pip install` command.
sbidoul: I have updated this PR to produce hashes in more situations. The only missing part is the integration with the wheel cache (on which I started working in #11042. Also `--dry-run` needs to print/log something, but that should be easy.

All in all, this seems to fit pretty neatly in the pip code base.

The hardest remaining part is to produce PEP 566 compliant metadata. To achieve this we may need to vendor `importlib_metadata` which has the metadata json conversion function. On the other hand, for a MVP it could be sufficient to export limited metadata information such as Name, Version and maybe Requires-Dist.

If there is positive feedback in the short term (especially from people who want to produce lock files with hashes) I'm motivated to push this forward. I may be able free up some time in May so as to land this as an experimental feature in 22.2 (July).

pfmoore: > The hardest remaining part is to produce PEP 566 compliant metadata. To achieve this we may need to vendor importlib_metadata which has the metadata json conversion function.

I have an implementation that converts a `METADATA` file (more accurately, an `email.message.Message` object) to that format in https://github.com/pfmoore/pkg_metadata/blob/main/src/pkg_metadata/metadata.py (I'd recommend extracting that function, rather than vendoring `pkg_metadata` at this point, as I only just uploaded the first draft version of the project yesterday!). The `msg_to_json` function is pretty robust, though - it successfully translates the `METADATA` file for every wheel on PyPI (including some that have things like broken encodings).
sbidoul: Thanks @pfmoore! I extracted your `msg_to_json` and it fits nicely.

The PR is still pretty small but it will grow with tests and docs etc. So my plan is to split it in easier to review chunks

- [ ] pip install --dry-run
- [ ] adding BaseDistribution.json_metadata (msg_to_json)
- [ ] side quest: exposing json metadata in pip list --json
- [ ] adding `download_info: DirectUrl` on InstallRequirement and integrating with the wheel cache
- [ ] pip install --report

In the meantime, this branch is ready for experimenters, and I'm happy to discuss / bikeshed the report schema here.
cosmicexplorer: I am about to propose basing #10748 off of this PR, since I *really* like the formalization of metadata parsing in `_internal/metadata/json.py` and moving the `InstallationReport` itself into `_internal/models`.

I had written a whole treatise trying to convince you to move this to `pip download --report`, but after reading https://github.com/pypa/pip/pull/10748#issuecomment-1007282460 again, it makes a lot more sense to me, and I now agree with you on keeping it in `pip install --report`. Would you consider:
1. renaming `--ignore-installed` to `--empty-env`?
1. renaming `InstallationReport` to `ResolutionReport`?

And as i am about to propose in #10748, I think I can make that PR additionally avoid downloading any dists when `--dry-run` is provided.
sbidoul: > renaming --ignore-installed to --empty-env?

@cosmicexplorer actually the `--ignore-installed` option already exists. So it would mean creating an alias. Perhaps this should be discussed in a separate issue ?

> renaming InstallationReport to ResolutionReport?

I'll look into it. This could make sense if we use it in another context than the install command.
OTOH, this name does not appear in the JSON output so it is easy to change as it is part of the pip internals.
sbidoul: I rebased on top of the other PRs and added integration tests. This is now feature complete.

I keep it draft until the other PRs are merged but this PR (i.e. the last commit) is now simple as it can get.
sbidoul: I've added an `environment` field containing the environment values (as in [PEP 508 environment markers](https://peps.python.org/pep-0508/#environment-markers)).

This is important so tools running in a different python environment than pip can interpret `requires_dist` metadata with environment markers using, `packaging.marker.Marker.evaluate()`
sbidoul: I also added a `requested_extras` field to top level requirements in the report. 

This is important so there is enough information to recreate the dependency graph by walking requires_dist metadata from the top level requirements.
sbidoul: I'm not sure how best to include the output format in the pip documentation.
Shall I write a new page with a free text documentation of the output and an example ? Where would it fit best ?
I also have a JSON schema with field descriptions and types which could be useful.

Any suggestions ?
sbidoul: Wondering if we should support `-` as target for the `--report` option. Useful (only) in combination with `--quiet`.
ofek: > Wondering if we should support `-` (stdout) as target for the `--report` option. Useful (only) in combination with `--quiet`.

Yes please! Hatch only uses output since it supports non-local environments like containers.
sbidoul: @ofek done ! :)
uranusjr: For documentation, I think a separate page describing the format is proably best. It can be put under _Reference_. I would do both free text, example, and a JSON schema, since they serve different audiences to the file.
sbidoul: Question in the bike shedding dpt: what do people think of the `download_info` field name ? Would `resolved_url` sound better ?
sbidoul: I added documentation, so I think this is ready. 
pradyunsg: I haven't reviewed this throughly, but... I have a couple of questions regarding how this format can evolve:

- Should we version this format in some way? I think an integer is enough -- it would be good to not be locked in to this format forever. Better to have it and not need it, than to need it and not have it.
- Should we add, for the initial releases, an `experimental: true` key-value pair, and allow ourselves the ability to evolve this in response to early user feedback?
sbidoul: > Should we version this format in some way? I think an integer is enough -- it would be good to not be locked in to this format forever. Better to have it and not need it, than to need it and not have it.

Maybe. I'm hesitant. If we add a version field, we should also state a versioning policy I guess. What should it be ? Should the version change only when backward incompatible changes are made ? Or should it change also when we add, say, new optional fields providing more information ? And then say we support some kind of semver, similar to [Metadata-Version](https://packaging.python.org/en/latest/specifications/core-metadata/#metadata-version) ?

On the other hand I don't expect this file to be persisted, but only be used in a transient manner by tools for further processing. May be we should make it explicit in the docs that it is intended to be used that way.

Also, I think it would not be acceptable for `--report` to suddenly produce a backward-incompatible format when a new pip version is released (similar discussion for pip list in ##11223). So if we ever need to make backward-incompatible changes I think we'll then need to add a new option such as `--report-version` that defaults to 1 and let users explicitly opt-in to superior versions. In which case they would know what the output format will be, and would not need to read a version field.

Another possibility that comes to mind is to add a `pip_version` field in the output, so users would know which pip version produced the data and know which feature they can expect.

> Should we add, for the initial releases, an experimental: true key-value pair, and allow ourselves the ability to evolve this in response to early user feedback?

An alternative could be to enable the `--report` option with a feature flag, which would let users explicitly and knowingly opt-in into an experimental feature.

Or say that this version is version 0.1 and start with the versioning scheme right away.

Genuine questions / food for thought...
sbidoul: I added `pip_version`, which is useful in any case. Users can use that to know which features to expect in the report, based on the documentation which will mention in what version new features have been added.

I lean towards saying that, in the unlikely case we need to do backward incompatible changes (such as removing fields, or changing semantics), we'll add a `--report-version` option.

Regarding the experimental nature of this, if we think it's necessary, I lean towards gating it behind a feature flag for one release cycle, in order to gather user feedback. I'm neutral on this, let me know your preference, @pradyunsg and @pypa/pip-committers.
pradyunsg: I don't think putting the pip version number is particularly useful. If there's a feature being added, that's gonna be a new key/value and the reader can look for that key/value.

The sort of thing that I'm thinking of is exactly the use case of changing semantics or removing a field. Having a number that we bump to convey that this is incompatible is *much* nicer than trying to communicate that information via documentation or flags.

pradyunsg: Adding report version flag is not sufficient, because we'd be tied into consuming the original format forever, with no graceful mechanism to deprecate it. I think that is suboptimal, and it'd be better to have a `"version" : 0` key for a release or two, before bumping that to version 1 and keeping that around until we need to make a backwards incompatible change. And when we do, we can add the report version flag (or do it now), to allow generating a different version and, like the transitions with the feature flags, to change the default at some point and also drop support for generating an older version at some point.

I'm not sure what you mean by putting this behind a feature flag. The point of the feature flags is to provide opt-in/opt-out from behaviours that we'd like to make the default. That's not the case here -- the report generation is already an opt-in. Having to pass an extra flag achieves very little in terms of functional changes that a version 0 or experimental true key wouldn't convey.
sbidoul: > I'm not sure what you mean by putting this behind a feature flag.

The idea would have been to force users to explicitly state that they wanted to use an experimental feature and would not overlook the experimental status. Probably too strong, let's trust user will read the docs.

Let's go with `"version": "0"`. I added that and documented it.

I kept `pip_version` as I see several potential uses and benefits:
- it affirms that this format is a "pip thing" and not any kind of interoperability standard
- the pip version is an information about the environment and may be useful in some cases
- we can imagine minor change that don't warrant a major version bump but for which tools would still be interested


Patch:
diff --git a/docs/html/cli/pip_install.rst b/docs/html/cli/pip_install.rst
index 408ef25ff..384d393fd 100644
--- a/docs/html/cli/pip_install.rst
+++ b/docs/html/cli/pip_install.rst
@@ -79,6 +79,18 @@ for an exception regarding pre-release versions). Where more than one source of
 the chosen version is available, it is assumed that any source is acceptable
 (as otherwise the versions would differ).
 
+Obtaining information about what was installed
+----------------------------------------------
+
+The install command has a ``--report`` option that will generate a JSON report of what
+pip has installed. In combination with the ``--dry-run`` and ``--ignore-installed`` it
+can be used to *resolve* a set of requirements without actually installing them.
+
+The report can be written to a file, or to standard output (using ``--report -`` in
+combination with ``--quiet``).
+
+The format of the JSON report is described in :doc:`../reference/installation-report`.
+
 Installation Order
 ------------------
 
diff --git a/docs/html/reference/index.md b/docs/html/reference/index.md
index 855dc79b3..749e88096 100644
--- a/docs/html/reference/index.md
+++ b/docs/html/reference/index.md
@@ -9,4 +9,5 @@ interoperability standards that pip utilises/implements.
 build-system/index
 requirement-specifiers
 requirements-file-format
+installation-report
 ```
diff --git a/docs/html/reference/installation-report.md b/docs/html/reference/installation-report.md
new file mode 100644
index 000000000..0be6be753
--- /dev/null
+++ b/docs/html/reference/installation-report.md
@@ -0,0 +1,176 @@
+# Installation Report
+
+The `--report` option of the pip install command produces a detailed JSON report of what
+it did install (or what it would have installed, if used with the `--dry-run` option).
+
+## Specification
+
+The report is a JSON object with the following properties:
+
+- `version`: the string `0`, denoting that the installation report is an experimental
+  feature. This value will change to `1`, when the feature is deemed stable after
+  gathering user feedback (likely in pip 22.3 or 23.0). Backward incompatible changes
+  may be introduced in version `1` without notice. After that, it will change only if
+  and when backward incompatible changes are introduced, such as removing mandatory
+  fields or changing the semantics or data type of existing fields. The introduction of
+  backward incompatible changes will follow the usual pip processes such as the
+  deprecation cycle or feature flags. Tools must check this field to ensure they support
+  the corresponding version.
+
+- `pip_version`: a string with the version of pip used to produce the report.
+
+- `install`: an array of [InstallationReportItem](InstallationReportItem) representing
+  the distribution packages (to be) installed.
+
+- `environment`: an object describing the environment where the installation report was
+  generated. See [PEP 508 environment
+  markers](https://peps.python.org/pep-0508/#environment-markers) for more information.
+  Values have a string type.
+
+(InstallationReportItem)=
+
+An `InstallationReportItem` is an object describing a (to be) installed distribution
+package with the following properties:
+
+- `metadata`: the metadata of the distribution, converted to a JSON object according to
+  the [PEP 566
+  transformation](https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata).
+
+- `is_direct`: `true` if the requirement was provided as, or constrained to, a direct
+  URL reference. `false` if the requirements was provided as a name and version
+  specifier.
+
+- `download_info`: Information about the artifact (to be) downloaded for installation,
+  using the [direct
+  URL](https://packaging.python.org/en/latest/specifications/direct-url/) data
+  structure. When `is_direct` is `true`, this field is the same as the `direct_url.json`
+  metadata, otherwise it represents the URL of the artifact obtained from the index or
+  `--find-links`.
+
+  ```{note}
+  For source archives, `download_info.archive_info.hash` may
+  be absent when the requirement was installed from the wheel cache
+  and the cache entry was populated by an older pip version that did not
+  record the origin URL of the downloaded artifact.
+  ```
+
+- `requested`: `true` if the requirement was explicitly provided by the user, either
+  directely via a command line argument or indirectly via a requirements file. `false`
+  if the requirement was installed as a dependency of another requirement.
+
+- `requested_extras`: extras requested by the user. This field is only present when the
+  `requested` field is true.
+
+## Example
+
+The following command:
+
+```console
+pip install \
+  --ignore-installed --dry-run --quiet \
+  --report - \
+  "pydantic>=1.9" git+https://github.com/pypa/packaging@main
+```
+
+will produce an output similar to this (metadata abriged for brevity):
+
+```json
+{
+  "version": "0",
+  "pip_version": "22.2",
+  "install": [
+    {
+      "download_info": {
+        "url": "https://files.pythonhosted.org/packages/a4/0c/fbaa7319dcb5eecd3484686eb5a5c5702a6445adb566f01aee6de3369bc4/pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
+        "archive_info": {
+          "hash": "sha256=18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"
+        }
+      },
+      "is_direct": false,
+      "requested": true,
+      "metadata": {
+        "name": "pydantic",
+        "version": "1.9.1",
+        "requires_dist": [
+          "typing-extensions (>=3.7.4.3)",
+          "dataclasses (>=0.6) ; python_version < \"3.7\"",
+          "python-dotenv (>=0.10.4) ; extra == 'dotenv'",
+          "email-validator (>=1.0.3) ; extra == 'email'"
+        ],
+        "requires_python": ">=3.6.1",
+        "provides_extra": [
+          "dotenv",
+          "email"
+        ]
+      }
+    },
+    {
+      "download_info": {
+        "url": "https://github.com/pypa/packaging",
+        "vcs_info": {
+          "vcs": "git",
+          "requested_revision": "main",
+          "commit_id": "4f42225e91a0be634625c09e84dd29ea82b85e27"
+        }
+      },
+      "is_direct": true,
+      "requested": true,
+      "metadata": {
+        "name": "packaging",
+        "version": "21.4.dev0",
+        "requires_dist": [
+          "pyparsing (!=3.0.5,>=2.0.2)"
+        ],
+        "requires_python": ">=3.7"
+      }
+    },
+    {
+      "download_info": {
+        "url": "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl",
+        "archive_info": {
+          "hash": "sha256=5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
+        }
+      },
+      "is_direct": false,
+      "requested": false,
+      "metadata": {
+        "name": "pyparsing",
+        "version": "3.0.9",
+        "requires_dist": [
+          "railroad-diagrams ; extra == \"diagrams\"",
+          "jinja2 ; extra == \"diagrams\""
+        ],
+        "requires_python": ">=3.6.8"
+      }
+    },
+    {
+      "download_info": {
+        "url": "https://files.pythonhosted.org/packages/75/e1/932e06004039dd670c9d5e1df0cd606bf46e29a28e65d5bb28e894ea29c9/typing_extensions-4.2.0-py3-none-any.whl",
+        "archive_info": {
+          "hash": "sha256=6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"
+        }
+      },
+      "is_direct": false,
+      "requested": false,
+      "metadata": {
+        "name": "typing_extensions",
+        "version": "4.2.0",
+        "requires_python": ">=3.7"
+      }
+    }
+  ],
+  "environment": {
+    "implementation_name": "cpython",
+    "implementation_version": "3.10.5",
+    "os_name": "posix",
+    "platform_machine": "x86_64",
+    "platform_release": "5.13-generic",
+    "platform_system": "Linux",
+    "platform_version": "...",
+    "python_full_version": "3.10.5",
+    "platform_python_implementation": "CPython",
+    "python_version": "3.10",
+    "sys_platform": "linux"
+  }
+}
+```
diff --git a/docs/html/topics/vcs-support.md b/docs/html/topics/vcs-support.md
index 70bb5beb9..538b4a791 100644
--- a/docs/html/topics/vcs-support.md
+++ b/docs/html/topics/vcs-support.md
@@ -73,16 +73,16 @@ The supported schemes are `svn`, `svn+svn`, `svn+http`, `svn+https` and
 `svn+ssh`. Here are some of the supported forms:
 
 ```none
-MyProject @ svn+https://svn.example.com/MyProject
-MyProject @ svn+ssh://svn.example.com/MyProject
-MyProject @ svn+ssh://user@svn.example.com/MyProject
+MyProject @svn+https://svn.example.com/MyProject
+MyProject @svn+ssh://svn.example.com/MyProject
+MyProject @svn+ssh://user@svn.example.com/MyProject
 ```
 
 You can also give specific revisions to an SVN URL, like so:
 
 ```none
--e svn+http://svn.example.com/svn/MyProject/trunk@2019#egg=MyProject
--e svn+http://svn.example.com/svn/MyProject/trunk@{20080101}#egg=MyProject
+MyProject @ -e svn+http://svn.example.com/svn/MyProject/trunk@2019
+MyProject @ -e svn+http://svn.example.com/svn/MyProject/trunk@{20080101}
 ```
 
 Note that you need to use [Editable VCS installs](#editable-vcs-installs) for
diff --git a/news/10222.bugfix.rst b/news/10222.bugfix.rst
deleted file mode 100644
index 0595e2b87..000000000
--- a/news/10222.bugfix.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Fix ``pip install --pre`` for packages with pre-release build dependencies defined both in ``pyproject.toml``'s ``build-system.requires`` and ``setup.py``'s ``setup_requires``.
diff --git a/news/11225.feature.rst b/news/11225.feature.rst
deleted file mode 100644
index 2da8f5617..000000000
--- a/news/11225.feature.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-pip's deprecation warnings now subclass the built-in ``DeprecationWarning``, and
-can be suppressed by running the Python interpreter with
-``-W ignore::DeprecationWarning``.
diff --git a/news/11241.removal.rst b/news/11241.removal.rst
deleted file mode 100644
index 69fb01866..000000000
--- a/news/11241.removal.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Remove ``--use-deprecated=backtrack-on-build-failures``.
diff --git a/news/11248.bugfix.rst b/news/11248.bugfix.rst
deleted file mode 100644
index c48146973..000000000
--- a/news/11248.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Use ``importlib.resources`` to read the ``vendor.txt`` file in ``pip debug``.
-This makes the command safe for use from a zipapp.
diff --git a/news/53.feature.rst b/news/53.feature.rst
new file mode 100644
index 000000000..a05256024
--- /dev/null
+++ b/news/53.feature.rst
@@ -0,0 +1,3 @@
+Add an experimental ``--report`` option to the install command to generate a JSON report
+of what was installed. In combination with ``--dry-run`` and ``--ignore-installed`` it
+can be used to resolve the requirements.
diff --git a/noxfile.py b/noxfile.py
index b7d3d8253..9de795fe5 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -229,23 +229,15 @@ def vendoring(session: nox.Session) -> None:
 
 @nox.session
 def coverage(session: nox.Session) -> None:
-    # Install source distribution
-    run_with_protected_pip(session, "install", ".")
-
-    # Install test dependencies
-    run_with_protected_pip(session, "install", "-r", REQUIREMENTS["tests"])
-
-    if not os.path.exists(".coverage-output"):
-        os.mkdir(".coverage-output")
+    if not os.path.exists("./.coverage-output"):
+        os.mkdir("./.coverage-output")
     session.run(
         "pytest",
         "--cov=pip",
         "--cov-config=./setup.cfg",
-        *session.posargs,
         env={
             "COVERAGE_OUTPUT_DIR": "./.coverage-output",
-            "COVERAGE_PROCESS_START": os.fsdecode(Path("setup.cfg").resolve()),
-            "SETUPTOOLS_USE_DISTUTILS": "stdlib",
+            "COVERAGE_PROCESS_START": "./setup.cfg",
         },
     )
 
diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py
index 35ae1e68c..df5152a78 100644
--- a/src/pip/_internal/cli/cmdoptions.py
+++ b/src/pip/_internal/cli/cmdoptions.py
@@ -1013,6 +1013,7 @@ use_deprecated_feature: Callable[..., Option] = partial(
     default=[],
     choices=[
         "legacy-resolver",
+        "backtrack-on-build-failures",
         "html5lib",
     ],
     help=("Enable deprecated functionality, that will be removed in the future."),
diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py
index b289426cf..6db058869 100644
--- a/src/pip/_internal/cli/req_command.py
+++ b/src/pip/_internal/cli/req_command.py
@@ -34,6 +34,7 @@ from pip._internal.req.req_file import parse_requirements
 from pip._internal.req.req_install import InstallRequirement
 from pip._internal.resolution.base import BaseResolver
 from pip._internal.self_outdated_check import pip_self_version_check
+from pip._internal.utils.deprecation import deprecated
 from pip._internal.utils.temp_dir import (
     TempDirectory,
     TempDirectoryTypeRegistry,
@@ -269,6 +270,31 @@ class RequirementCommand(IndexGroupCommand):
 
         return "2020-resolver"
 
+    @staticmethod
+    def determine_build_failure_suppression(options: Values) -> bool:
+        """Determines whether build failures should be suppressed and backtracked on."""
+        if "backtrack-on-build-failures" not in options.deprecated_features_enabled:
+            return False
+
+        if "legacy-resolver" in options.deprecated_features_enabled:
+            raise CommandError("Cannot backtrack with legacy resolver.")
+
+        deprecated(
+            reason=(
+                "Backtracking on build failures can mask issues related to how "
+                "a package generates metadata or builds a wheel. This flag will "
+                "be removed in pip 22.2."
+            ),
+            gone_in=None,
+            replacement=(
+                "avoiding known-bad versions by explicitly telling pip to ignore them "
+                "(either directly as requirements, or via a constraints file)"
+            ),
+            feature_flag=None,
+            issue=10655,
+        )
+        return True
+
     @classmethod
     def make_requirement_preparer(
         cls,
@@ -345,6 +371,7 @@ class RequirementCommand(IndexGroupCommand):
             use_pep517=use_pep517,
             config_settings=getattr(options, "config_settings", None),
         )
+        suppress_build_failures = cls.determine_build_failure_suppression(options)
         resolver_variant = cls.determine_resolver_variant(options)
         # The long import name and duplicated invocation is needed to convince
         # Mypy into correctly typechecking. Otherwise it would complain the
@@ -364,6 +391,7 @@ class RequirementCommand(IndexGroupCommand):
                 force_reinstall=force_reinstall,
                 upgrade_strategy=upgrade_strategy,
                 py_version_info=py_version_info,
+                suppress_build_failures=suppress_build_failures,
             )
         import pip._internal.resolution.legacy.resolver
 
diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py
index 6fad1fe89..084d7fa98 100644
--- a/src/pip/_internal/commands/debug.py
+++ b/src/pip/_internal/commands/debug.py
@@ -1,4 +1,3 @@
-import importlib.resources
 import locale
 import logging
 import os
@@ -11,6 +10,7 @@ import pip._vendor
 from pip._vendor.certifi import where
 from pip._vendor.packaging.version import parse as parse_version
 
+from pip import __file__ as pip_location
 from pip._internal.cli import cmdoptions
 from pip._internal.cli.base_command import Command
 from pip._internal.cli.cmdoptions import make_target_python
@@ -35,7 +35,11 @@ def show_sys_implementation() -> None:
 
 
 def create_vendor_txt_map() -> Dict[str, str]:
-    with importlib.resources.open_text("pip._vendor", "vendor.txt") as f:
+    vendor_txt_path = os.path.join(
+        os.path.dirname(pip_location), "_vendor", "vendor.txt"
+    )
+
+    with open(vendor_txt_path) as f:
         # Purge non version specifying lines.
         # Also, remove any space prefix or suffixes (including comments).
         lines = [
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index 9bb99cdf2..29907645c 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -1,4 +1,5 @@
 import errno
+import json
 import operator
 import os
 import shutil
@@ -7,6 +8,7 @@ from optparse import SUPPRESS_HELP, Values
 from typing import Iterable, List, Optional
 
 from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.rich import print_json
 
 from pip._internal.cache import WheelCache
 from pip._internal.cli import cmdoptions
@@ -21,6 +23,7 @@ from pip._internal.exceptions import CommandError, InstallationError
 from pip._internal.locations import get_scheme
 from pip._internal.metadata import get_environment
 from pip._internal.models.format_control import FormatControl
+from pip._internal.models.installation_report import InstallationReport
 from pip._internal.operations.build.build_tracker import get_build_tracker
 from pip._internal.operations.check import ConflictDetails, check_install_conflicts
 from pip._internal.req import install_given_reqs
@@ -250,6 +253,20 @@ class InstallCommand(RequirementCommand):
         self.parser.insert_option_group(0, index_opts)
         self.parser.insert_option_group(0, self.cmd_opts)
 
+        self.cmd_opts.add_option(
+            "--report",
+            dest="json_report_file",
+            metavar="file",
+            default=None,
+            help=(
+                "Generate a JSON file describing what pip did to install "
+                "the provided requirements. "
+                "Can be used in combination with --dry-run and --ignore-installed "
+                "to 'resolve' the requirements. "
+                "When - is used as file name it writes to stdout."
+            ),
+        )
+
     @with_cleanup
     def run(self, options: Values, args: List[str]) -> int:
         if options.use_user_site and options.target_dir is not None:
@@ -353,6 +370,20 @@ class InstallCommand(RequirementCommand):
                 reqs, check_supported_wheels=not options.target_dir
             )
 
+            if options.json_report_file:
+                logger.warning(
+                    "--report is currently an experimental option. "
+                    "The output format may change in a future release "
+                    "without prior warning."
+                )
+
+                report = InstallationReport(requirement_set.requirements_to_install)
+                if options.json_report_file == "-":
+                    print_json(data=report.to_dict())
+                else:
+                    with open(options.json_report_file, "w", encoding="utf-8") as f:
+                        json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
+
             if options.dry_run:
                 would_install_items = sorted(
                     (r.metadata["name"], r.metadata["version"])
diff --git a/src/pip/_internal/models/installation_report.py b/src/pip/_internal/models/installation_report.py
new file mode 100644
index 000000000..965f09523
--- /dev/null
+++ b/src/pip/_internal/models/installation_report.py
@@ -0,0 +1,53 @@
+from typing import Any, Dict, Sequence
+
+from pip._vendor.packaging.markers import default_environment
+
+from pip import __version__
+from pip._internal.req.req_install import InstallRequirement
+
+
+class InstallationReport:
+    def __init__(self, install_requirements: Sequence[InstallRequirement]):
+        self._install_requirements = install_requirements
+
+    @classmethod
+    def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
+        assert ireq.download_info, f"No download_info for {ireq}"
+        res = {
+            # PEP 610 json for the download URL. download_info.archive_info.hash may
+            # be absent when the requirement was installed from the wheel cache
+            # and the cache entry was populated by an older pip version that did not
+            # record origin.json.
+            "download_info": ireq.download_info.to_dict(),
+            # is_direct is true if the requirement was a direct URL reference (which
+            # includes editable requirements), and false if the requirement was
+            # downloaded from a PEP 503 index or --find-links.
+            "is_direct": bool(ireq.original_link),
+            # requested is true if the requirement was specified by the user (aka
+            # top level requirement), and false if it was installed as a dependency of a
+            # requirement. https://peps.python.org/pep-0376/#requested
+            "requested": ireq.user_supplied,
+            # PEP 566 json encoding for metadata
+            # https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
+            "metadata": ireq.get_dist().metadata_dict,
+        }
+        if ireq.user_supplied and ireq.extras:
+            # For top level requirements, the list of requested extras, if any.
+            res["requested_extras"] = list(sorted(ireq.extras))
+        return res
+
+    def to_dict(self) -> Dict[str, Any]:
+        return {
+            "version": "0",
+            "pip_version": __version__,
+            "install": [
+                self._install_req_to_dict(ireq) for ireq in self._install_requirements
+            ],
+            # https://peps.python.org/pep-0508/#environment-markers
+            # TODO: currently, the resolver uses the default environment to evaluate
+            # environment markers, so that is what we report here. In the future, it
+            # should also take into account options such as --python-version or
+            # --platform, perhaps under the form of an environment_override field?
+            # https://github.com/pypa/pip/issues/11198
+            "environment": default_environment(),
+        }
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index a4c24b52a..4569033db 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -27,6 +27,7 @@ from pip._internal.cache import CacheEntry, WheelCache
 from pip._internal.exceptions import (
     DistributionNotFound,
     InstallationError,
+    InstallationSubprocessError,
     MetadataInconsistent,
     UnsupportedPythonVersion,
     UnsupportedWheel,
@@ -96,6 +97,7 @@ class Factory:
         force_reinstall: bool,
         ignore_installed: bool,
         ignore_requires_python: bool,
+        suppress_build_failures: bool,
         py_version_info: Optional[Tuple[int, ...]] = None,
     ) -> None:
         self._finder = finder
@@ -106,6 +108,7 @@ class Factory:
         self._use_user_site = use_user_site
         self._force_reinstall = force_reinstall
         self._ignore_requires_python = ignore_requires_python
+        self._suppress_build_failures = suppress_build_failures
 
         self._build_failures: Cache[InstallationError] = {}
         self._link_candidate_cache: Cache[LinkCandidate] = {}
@@ -198,6 +201,12 @@ class Factory:
                     )
                     self._build_failures[link] = e
                     return None
+                except InstallationSubprocessError as e:
+                    if not self._suppress_build_failures:
+                        raise
+                    logger.warning("Discarding %s due to build failure: %s", link, e)
+                    self._build_failures[link] = e
+                    return None
 
             base: BaseCandidate = self._editable_candidate_cache[link]
         else:
@@ -219,6 +228,12 @@ class Factory:
                     )
                     self._build_failures[link] = e
                     return None
+                except InstallationSubprocessError as e:
+                    if not self._suppress_build_failures:
+                        raise
+                    logger.warning("Discarding %s due to build failure: %s", link, e)
+                    self._build_failures[link] = e
+                    return None
             base = self._link_candidate_cache[link]
 
         if not extras:
diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py
index a605d6c25..32ef7899b 100644
--- a/src/pip/_internal/resolution/resolvelib/resolver.py
+++ b/src/pip/_internal/resolution/resolvelib/resolver.py
@@ -47,6 +47,7 @@ class Resolver(BaseResolver):
         ignore_requires_python: bool,
         force_reinstall: bool,
         upgrade_strategy: str,
+        suppress_build_failures: bool,
         py_version_info: Optional[Tuple[int, ...]] = None,
     ):
         super().__init__()
@@ -61,6 +62,7 @@ class Resolver(BaseResolver):
             force_reinstall=force_reinstall,
             ignore_installed=ignore_installed,
             ignore_requires_python=ignore_requires_python,
+            suppress_build_failures=suppress_build_failures,
             py_version_info=py_version_info,
         )
         self.ignore_dependencies = ignore_dependencies
diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py
index 7964095ff..72bd6f25a 100644
--- a/src/pip/_internal/utils/deprecation.py
+++ b/src/pip/_internal/utils/deprecation.py
@@ -13,7 +13,7 @@ from pip import __version__ as current_version  # NOTE: tests patch this name.
 DEPRECATION_MSG_PREFIX = "DEPRECATION: "
 
 
-class PipDeprecationWarning(DeprecationWarning):
+class PipDeprecationWarning(Warning):
     pass
 
 
diff --git a/src/pip/_internal/utils/distutils_args.py b/src/pip/_internal/utils/distutils_args.py
index 2fd186207..e4aa5b827 100644
--- a/src/pip/_internal/utils/distutils_args.py
+++ b/src/pip/_internal/utils/distutils_args.py
@@ -1,43 +1,42 @@
-from getopt import GetoptError, getopt
+from distutils.errors import DistutilsArgError
+from distutils.fancy_getopt import FancyGetopt
 from typing import Dict, List
 
 _options = [
-    "exec-prefix=",
-    "home=",
-    "install-base=",
-    "install-data=",
-    "install-headers=",
-    "install-lib=",
-    "install-platlib=",
-    "install-purelib=",
-    "install-scripts=",
-    "prefix=",
-    "root=",
-    "user",
+    ("exec-prefix=", None, ""),
+    ("home=", None, ""),
+    ("install-base=", None, ""),
+    ("install-data=", None, ""),
+    ("install-headers=", None, ""),
+    ("install-lib=", None, ""),
+    ("install-platlib=", None, ""),
+    ("install-purelib=", None, ""),
+    ("install-scripts=", None, ""),
+    ("prefix=", None, ""),
+    ("root=", None, ""),
+    ("user", None, ""),
 ]
 
 
+# typeshed doesn't permit Tuple[str, None, str], see python/typeshed#3469.
+_distutils_getopt = FancyGetopt(_options)  # type: ignore
+
+
 def parse_distutils_args(args: List[str]) -> Dict[str, str]:
-    """Parse provided arguments, returning an object that has the matched arguments.
+    """Parse provided arguments, returning an object that has the
+    matched arguments.
 
     Any unknown arguments are ignored.
     """
     result = {}
     for arg in args:
         try:
-            parsed_opt, _ = getopt(args=[arg], shortopts="", longopts=_options)
-        except GetoptError:
+            _, match = _distutils_getopt.getopt(args=[arg])
+        except DistutilsArgError:
             # We don't care about any other options, which here may be
             # considered unrecognized since our option list is not
             # exhaustive.
-            continue
-
-        if not parsed_opt:
-            continue
-
-        option = parsed_opt[0]
-        name_from_parsed = option[0][2:].replace("-", "_")
-        value_from_parsed = option[1] or "true"
-        result[name_from_parsed] = value_from_parsed
-
+            pass
+        else:
+            result.update(match.__dict__)
     return result


Test patch:
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index e74477fe2..ad2b4f5e9 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -733,45 +733,6 @@ def test_editable_install__local_dir_setup_requires_with_pyproject(
     script.pip("install", "--find-links", shared_data.find_links, "-e", local_dir)
 
 
-def test_install_pre__setup_requires_with_pyproject(
-    script: PipTestEnvironment, shared_data: TestData, common_wheels: Path
-) -> None:
-    """
-    Test installing with a pre-release build dependency declared in both
-    setup.py and pyproject.toml.
-
-    https://github.com/pypa/pip/issues/10573
-    """
-    depends_package = "prerelease_dependency"
-    depends_path = create_basic_wheel_for_package(script, depends_package, "1.0.0a1")
-
-    local_dir = script.scratch_path.joinpath("temp")
-    local_dir.mkdir()
-    pyproject_path = local_dir.joinpath("pyproject.toml")
-    pyproject_path.write_text(
-        "[build-system]\n"
-        f'requires = ["setuptools", "wheel", "{depends_package}"]\n'
-        'build-backend = "setuptools.build_meta"\n'
-    )
-    setup_py_path = local_dir.joinpath("setup.py")
-    setup_py_path.write_text(
-        "from setuptools import setup\n"
-        f"setup(name='dummy', setup_requires=['{depends_package}'])\n"
-    )
-
-    script.pip(
-        "install",
-        "--pre",
-        "--no-cache-dir",
-        "--no-index",
-        "--find-links",
-        common_wheels,
-        "--find-links",
-        depends_path.parent,
-        local_dir,
-    )
-
-
 @pytest.mark.network
 def test_upgrade_argparse_shadowed(script: PipTestEnvironment) -> None:
     # If argparse is installed - even if shadowed for imported - we support
diff --git a/tests/functional/test_install_report.py b/tests/functional/test_install_report.py
new file mode 100644
index 000000000..b61fd89c6
--- /dev/null
+++ b/tests/functional/test_install_report.py
@@ -0,0 +1,214 @@
+import json
+from pathlib import Path
+from typing import Any, Dict
+
+import pytest
+from packaging.utils import canonicalize_name
+
+from ..lib import PipTestEnvironment, TestData
+
+
+def _install_dict(report: Dict[str, Any]) -> Dict[str, Any]:
+    return {canonicalize_name(i["metadata"]["name"]): i for i in report["install"]}
+
+
+@pytest.mark.usefixtures("with_wheel")
+def test_install_report_basic(
+    script: PipTestEnvironment, shared_data: TestData, tmp_path: Path
+) -> None:
+    report_path = tmp_path / "report.json"
+    script.pip(
+        "install",
+        "simplewheel",
+        "--dry-run",
+        "--no-index",
+        "--find-links",
+        str(shared_data.root / "packages/"),
+        "--report",
+        str(report_path),
+        allow_stderr_warning=True,
+    )
+    report = json.loads(report_path.read_text())
+    assert "install" in report
+    assert len(report["install"]) == 1
+    simplewheel_report = _install_dict(report)["simplewheel"]
+    assert simplewheel_report["metadata"]["name"] == "simplewheel"
+    assert simplewheel_report["requested"] is True
+    assert simplewheel_report["is_direct"] is False
+    url = simplewheel_report["download_info"]["url"]
+    assert url.startswith("file://")
+    assert url.endswith("/packages/simplewheel-2.0-1-py2.py3-none-any.whl")
+    assert (
+        simplewheel_report["download_info"]["archive_info"]["hash"]
+        == "sha256=191d6520d0570b13580bf7642c97ddfbb46dd04da5dd2cf7bef9f32391dfe716"
+    )
+
+
+@pytest.mark.usefixtures("with_wheel")
+def test_install_report_dep(
+    script: PipTestEnvironment, shared_data: TestData, tmp_path: Path
+) -> None:
+    """Test dependencies are present in the install report with requested=False."""
+    report_path = tmp_path / "report.json"
+    script.pip(
+        "install",
+        "require_simple",
+        "--dry-run",
+        "--no-index",
+        "--find-links",
+        str(shared_data.root / "packages/"),
+        "--report",
+        str(report_path),
+        allow_stderr_warning=True,
+    )
+    report = json.loads(report_path.read_text())
+    assert len(report["install"]) == 2
+    assert _install_dict(report)["require-simple"]["requested"] is True
+    assert _install_dict(report)["simple"]["requested"] is False
+
+
+@pytest.mark.network
+@pytest.mark.usefixtures("with_wheel")
+def test_install_report_index(script: PipTestEnvironment, tmp_path: Path) -> None:
+    """Test report for sdist obtained from index."""
+    report_path = tmp_path / "report.json"
+    script.pip(
+        "install",
+        "--dry-run",
+        "Paste[openid]==1.7.5.1",
+        "--report",
+        str(report_path),
+        allow_stderr_warning=True,
+    )
+    report = json.loads(report_path.read_text())
+    assert len(report["install"]) == 2
+    install_dict = _install_dict(report)
+    assert install_dict["paste"]["requested"] is True
+    assert install_dict["python-openid"]["requested"] is False
+    paste_report = install_dict["paste"]
+    assert paste_report["download_info"]["url"].startswith(
+        "https://files.pythonhosted.org/"
+    )
+    assert paste_report["download_info"]["url"].endswith("/Paste-1.7.5.1.tar.gz")
+    assert (
+        paste_report["download_info"]["archive_info"]["hash"]
+        == "sha256=11645842ba8ec986ae8cfbe4c6cacff5c35f0f4527abf4f5581ae8b4ad49c0b6"
+    )
+    assert paste_report["requested_extras"] == ["openid"]
+    assert "requires_dist" in paste_report["metadata"]
+
+
+@pytest.mark.network
+@pytest.mark.usefixtures("with_wheel")
+def test_install_report_vcs_and_wheel_cache(
+    script: PipTestEnvironment, tmp_path: Path
+) -> None:
+    """Test report for VCS reference, and interactions with the wheel cache."""
+    cache_dir = tmp_path / "cache"
+    report_path = tmp_path / "report.json"
+    script.pip(
+        "install",
+        "git+https://github.com/pypa/pip-test-package"
+        "@5547fa909e83df8bd743d3978d6667497983a4b7",
+        "--cache-dir",
+        str(cache_dir),
+        "--report",
+        str(report_path),
+        allow_stderr_warning=True,
+    )
+    report = json.loads(report_path.read_text())
+    assert len(report["install"]) == 1
+    pip_test_package_report = report["install"][0]
+    assert pip_test_package_report["is_direct"] is True
+    assert pip_test_package_report["requested"] is True
+    assert (
+        pip_test_package_report["download_info"]["url"]
+        == "https://github.com/pypa/pip-test-package"
+    )
+    assert pip_test_package_report["download_info"]["vcs_info"]["vcs"] == "git"
+    assert (
+        pip_test_package_report["download_info"]["vcs_info"]["commit_id"]
+        == "5547fa909e83df8bd743d3978d6667497983a4b7"
+    )
+    # Now do it again to make sure the cache is used and that the report still contains
+    # the original VCS url.
+    report_path.unlink()
+    result = script.pip(
+        "install",
+        "pip-test-package @ git+https://github.com/pypa/pip-test-package"
+        "@5547fa909e83df8bd743d3978d6667497983a4b7",
+        "--ignore-installed",
+        "--cache-dir",
+        str(cache_dir),
+        "--report",
+        str(report_path),
+        allow_stderr_warning=True,
+    )
+    assert "Using cached pip_test_package" in result.stdout
+    report = json.loads(report_path.read_text())
+    assert len(report["install"]) == 1
+    pip_test_package_report = report["install"][0]
+    assert pip_test_package_report["is_direct"] is True
+    assert pip_test_package_report["requested"] is True
+    assert (
+        pip_test_package_report["download_info"]["url"]
+        == "https://github.com/pypa/pip-test-package"
+    )
+    assert pip_test_package_report["download_info"]["vcs_info"]["vcs"] == "git"
+    assert (
+        pip_test_package_report["download_info"]["vcs_info"]["commit_id"]
+        == "5547fa909e83df8bd743d3978d6667497983a4b7"
+    )
+
+
+@pytest.mark.network
+@pytest.mark.usefixtures("with_wheel")
+def test_install_report_vcs_editable(
+    script: PipTestEnvironment, tmp_path: Path
+) -> None:
+    """Test report remote editable."""
+    report_path = tmp_path / "report.json"
+    script.pip(
+        "install",
+        "--editable",
+        "git+https://github.com/pypa/pip-test-package"
+        "@5547fa909e83df8bd743d3978d6667497983a4b7"
+        "#egg=pip-test-package",
+        "--report",
+        str(report_path),
+        allow_stderr_warning=True,
+    )
+    report = json.loads(report_path.read_text())
+    assert len(report["install"]) == 1
+    pip_test_package_report = report["install"][0]
+    assert pip_test_package_report["is_direct"] is True
+    assert pip_test_package_report["download_info"]["url"].startswith("file://")
+    assert pip_test_package_report["download_info"]["url"].endswith(
+        "/src/pip-test-package"
+    )
+    assert pip_test_package_report["download_info"]["dir_info"]["editable"] is True
+
+
+@pytest.mark.usefixtures("with_wheel")
+def test_install_report_to_stdout(
+    script: PipTestEnvironment, shared_data: TestData
+) -> None:
+    result = script.pip(
+        "install",
+        "simplewheel",
+        "--quiet",
+        "--dry-run",
+        "--no-index",
+        "--find-links",
+        str(shared_data.root / "packages/"),
+        "--report",
+        "-",
+        allow_stderr_warning=True,
+    )
+    assert result.stderr == (
+        "WARNING: --report is currently an experimental option. "
+        "The output format may change in a future release without prior warning.\n"
+    )
+    report = json.loads(result.stdout)
+    assert "install" in report
+    assert len(report["install"]) == 1
diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py
index efcae2928..f3acdb9ca 100644
--- a/tests/functional/test_new_resolver.py
+++ b/tests/functional/test_new_resolver.py
@@ -2326,6 +2326,26 @@ def test_new_resolver_do_not_backtrack_on_build_failure(
     assert "egg_info" in result.stderr
 
 
+def test_new_resolver_flag_permits_backtracking_on_build_failure(
+    script: PipTestEnvironment,
+) -> None:
+    create_basic_sdist_for_package(script, "pkg1", "2.0", fails_egg_info=True)
+    create_basic_wheel_for_package(script, "pkg1", "1.0")
+
+    script.pip(
+        "install",
+        "--use-deprecated=backtrack-on-build-failures",
+        "--no-cache-dir",
+        "--no-index",
+        "--find-links",
+        script.scratch_path,
+        "pkg1",
+        allow_stderr_warning=True,
+    )
+
+    script.assert_installed(pkg1="1.0")
+
+
 def test_new_resolver_works_when_failing_package_builds_are_disallowed(
     script: PipTestEnvironment,
 ) -> None:
diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py
index a8889293b..6cef3c17c 100644
--- a/tests/unit/resolution_resolvelib/conftest.py
+++ b/tests/unit/resolution_resolvelib/conftest.py
@@ -63,6 +63,7 @@ def factory(finder: PackageFinder, preparer: RequirementPreparer) -> Iterator[Fa
         force_reinstall=False,
         ignore_installed=False,
         ignore_requires_python=False,
+        suppress_build_failures=False,
         py_version_info=None,
     )
 
diff --git a/tests/unit/resolution_resolvelib/test_resolver.py b/tests/unit/resolution_resolvelib/test_resolver.py
index 87c2b5f35..db71f911a 100644
--- a/tests/unit/resolution_resolvelib/test_resolver.py
+++ b/tests/unit/resolution_resolvelib/test_resolver.py
@@ -29,6 +29,7 @@ def resolver(preparer: RequirementPreparer, finder: PackageFinder) -> Resolver:
         ignore_requires_python=False,
         force_reinstall=False,
         upgrade_strategy="to-satisfy-only",
+        suppress_build_failures=False,
     )
     return resolver
 
diff --git a/tests/unit/test_utils_distutils_args.py b/tests/unit/test_utils_distutils_args.py
index 21f31e926..e63c565a1 100644
--- a/tests/unit/test_utils_distutils_args.py
+++ b/tests/unit/test_utils_distutils_args.py
@@ -60,4 +60,4 @@ def test_all_value_options_work(name: str, value: str) -> None:
 
 def test_user_option_works() -> None:
     result = parse_distutils_args(["--user"])
-    assert result["user"]
+    assert result["user"] == 1