Repository: pypa/pipenv

Issue:
`pipenv install --index <my-pypi-registry> <my-package>` generates a Pipfile that `pipenv lock` can't handle
I start from a completely blank slate -- no Pipfile, no Pipfile.lock, no virtual environment. And I execute a command like
```
pipenv install --index <my-pypi-registry> <my-package>
```
where `<my-pypi-registry>` is a private PyPI registry (not a mirror of the public PyPI registry!) that hosts the package `<my-package>`.

The result of the command is:
- a virtual environment is successfully created
- and a Pipfile is generated
- but locking fails.
```
...
Successfully created virtual environment!
Virtualenv location: C:\tmp\tmp-venv\.venv
Creating a Pipfile for this project...
Installing <my-package>...
Adding <my-package> to Pipfile's [packages]...
Installation Succeeded
Pipfile.lock not found, creating...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
 Locking...Building requirements...
Resolving dependencies...
Locking Failed!

CRITICAL:pipenv.patched.notpip._internal.resolution.resolvelib.factory:Could not find a version that satisfies the requirement <my-package> (from versions: none)
[ResolutionFailure]:   File "c:\python391\lib\site-packages\pipenv\resolver.py", line 743, in _main
[ResolutionFailure]:       resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages, dev)
[ResolutionFailure]:   File "c:\python391\lib\site-packages\pipenv\resolver.py", line 704, in resolve_packages
[ResolutionFailure]:       results, resolver = resolve(
[ResolutionFailure]:   File "c:\python391\lib\site-packages\pipenv\resolver.py", line 685, in resolve
[ResolutionFailure]:       return resolve_deps(
[ResolutionFailure]:   File "c:\python391\lib\site-packages\pipenv\utils.py", line 1370, in resolve_deps
[ResolutionFailure]:       results, hashes, markers_lookup, resolver, skipped = actually_resolve_deps(
[ResolutionFailure]:   File "c:\python391\lib\site-packages\pipenv\utils.py", line 1099, in actually_resolve_deps
[ResolutionFailure]:       resolver.resolve()
[ResolutionFailure]:   File "c:\python391\lib\site-packages\pipenv\utils.py", line 877, in resolve
[ResolutionFailure]:       raise ResolutionFailure(message=str(e))
[pipenv.exceptions.ResolutionFailure]: Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
  You can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
  Hint: try $ pipenv lock --pre if it is a pre-release dependency.
ERROR: No matching distribution found for <my-package>
```

The generated Pipfile looks like this:
```
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
<my-package> = {version = "*", index = "<my-pypi-registry>"}
...
```

That looks reasonable, but `pipenv lock` fails on it with the error shown above.

However, if I manually change the Pipfile to look like this, then `pipenv lock` succeeds, even though this seems essentially the same:
```
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[[source]]
url = "<my-pypi-registry>"
verify_ssl = true
name = "mypypi"

[packages]
<my-package> = {version = "*", index = "mypypi"}
...
```
---
If (again starting from a completely blank slate) I replace `--index` with `--extra-index-url`, so my command looks like this:
```
pipenv install --extra-index-url <my-pypi-registry> <my-package>
```
then the command's output looks the same, meaning
- a virtual environment is successfully created
- and a Pipfile is generated
- but locking fails with the same error as above.

However, in this case, the generated Pipfile is clearly wrong, in that it doesn't reference \<my-pypi-registry> at all, so it's not surprising that `pipenv lock` fails on it. It looks like this:
```
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
<my-package> = "*"
...
```
---
In summary, this might be two separate bugs.

1. `pipenv lock` fails on the seemingly correct Pipfile generated by `pipenv install --index ...`.
2. `pipenv install --extra-index-url ...` generates an incorrect Pipfile.
---
I'm using pipenv version 2021.11.15, which I installed using pip.


Discussion:

Resolving patch:
diff --git a/pipenv/core.py b/pipenv/core.py
--- a/pipenv/core.py
+++ b/pipenv/core.py
@@ -2039,8 +2039,6 @@ def do_install(
                     sp.write_err(vistir.compat.fs_str("{}: {}".format(crayons.red("WARNING"), e)))
                     sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Installation Failed"))
                     sys.exit(1)
-                if index_url:
-                    pkg_requirement.index = index_url
                 no_deps = False
                 sp.text = "Installing..."
                 try:
@@ -2110,6 +2108,13 @@ def do_install(
                     )
                 ))
                 # Add the package to the Pipfile.
+                indexes = list(filter(None, [index_url, extra_index_url]))
+                for index in indexes:
+                    index_name = project.add_index_to_pipfile(
+                        index, verify_ssl=index.startswith("https:")
+                    )
+                    if index_url and not extra_index_url:
+                        pkg_requirement.index = index_name
                 try:
                     project.add_package_to_pipfile(pkg_requirement, dev)
                 except ValueError:
diff --git a/pipenv/project.py b/pipenv/project.py
--- a/pipenv/project.py
+++ b/pipenv/project.py
@@ -1005,20 +1005,26 @@ def add_index_to_pipfile(self, index, verify_ssl=True):
         """Adds a given index to the Pipfile."""
         # Read and append Pipfile.
         p = self.parsed_pipfile
+        source = None
         try:
-            self.get_source(url=index)
+            source = self.get_source(url=index)
         except SourceNotFound:
-            source = {"url": index, "verify_ssl": verify_ssl}
-        else:
-            return
+            try:
+                source = self.get_source(name=index)
+            except SourceNotFound:
+                pass
+        if source is not None:
+            return source["name"]
+        source = {"url": index, "verify_ssl": verify_ssl}
         source["name"] = self.src_name_from_url(index)
         # Add the package to the group.
         if "source" not in p:
-            p["source"] = [source]
+            p["source"] = [tomlkit.item(source)]
         else:
-            p["source"].append(source)
+            p["source"].append(tomlkit.item(source))
         # Write Pipfile.
         self.write_toml(p)
+        return source["name"]
 
     def recase_pipfile(self):
         if self.ensure_proper_casing():


Test patch:
diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py
--- a/tests/integration/test_install_uri.py
+++ b/tests/integration/test_install_uri.py
@@ -208,6 +208,33 @@ def test_install_named_index_alias(PipenvInstance_NoPyPI):
         assert c.returncode == 0
 
 
+@pytest.mark.urls
+@pytest.mark.index
+@pytest.mark.install
+@pytest.mark.needs_internet
+def test_install_specifying_index_url(PipenvInstance_NoPyPI):
+    with PipenvInstance_NoPyPI() as p:
+        with open(p.pipfile_path, "w") as f:
+            contents = """
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+six = "*"
+
+[dev-packages]
+            """.strip()
+            f.write(contents)
+        c = p.pipenv("install pipenv-test-private-package --index https://test.pypi.org/simple")
+        assert c.returncode == 0
+        pipfile = p.pipfile
+        assert pipfile["source"][1]["url"] == "https://test.pypi.org/simple"
+        assert pipfile["source"][1]["name"] == "testpypi"
+        assert pipfile["packages"]["pipenv-test-private-package"]["index"] == "testpypi"
+
+
 @pytest.mark.vcs
 @pytest.mark.urls
 @pytest.mark.install