Repository: pdm-project/pdm

Issue:
Editable install shouldn't be cached
- [x] I have searched the issue tracker and believe that this is not a duplicate.

**Make sure you run commands with `-v` flag before pasting the output.**

## Steps to reproduce

There are multiple reproducible issues with editable caching. I hit multiple since the 2.14 release, here's the main cases

### Different projects, same name

Just create 2 different `pdm` self-installable projects (`distribution=true`) with the same name (`test-project` or `demo` are realistic candidates) without using git.

The first project editable self-install will be OK.
The 2nd one will have the same cache key (`demo-0.1.0-0.editable-py3-none-any.whl.cache`) and instead of being self installed, it will be linked to the first one (`import demo` import project 1 instead of 2).

### Same project, multiple clones

Clone twice the same installable project (general case will be `HEAD=main`) and `pdm install` them.
Same result as the previous case, the 2nd one is linked to the first one. If you change branch on the first project, editable install version won't change and 2nd project will silently have the 1st project version.

### Self or dependency

The same cases occur for both self-install or for editable dependencies.

## Actual behavior

<!--A clear and concise description the result of the above steps-->

Editable installs packages are cached leading self-installs or multiple installs in different envs collide and then to be mixed

## Expected behavior

<!--A clear and concise description of what you expected to happen.-->
Editable installs are not cached and can be used across multiple projects without collisions and mixups

## Environment Information

```bash
PDM version:
  2.14.0
Python Interpreter:
  /home/noirbizarre/Workspaces/python.copier/.venv/bin/python (3.11)
Project Root:
  /home/noirbizarre/Workspaces/python.copier
Local Packages:

{
  "implementation_name": "cpython",
  "implementation_version": "3.11.8",
  "os_name": "posix",
  "platform_machine": "x86_64",
  "platform_release": "6.8.5-arch1-1",
  "platform_system": "Linux",
  "platform_version": "#1 SMP PREEMPT_DYNAMIC Thu, 11 Apr 2024 01:47:33 +0000",
  "python_full_version": "3.11.8",
  "platform_python_implementation": "CPython",
  "python_version": "3.11",
  "sys_platform": "linux"
}
```



Discussion:

Patch:
diff --git a/news/2816.bugfix.md b/news/2816.bugfix.md
new file mode 100644
index 0000000000..9123b0531a
--- /dev/null
+++ b/news/2816.bugfix.md
@@ -0,0 +1,1 @@
+Don't cache editable installations.
diff --git a/src/pdm/installers/manager.py b/src/pdm/installers/manager.py
index 1835cfba16..2ad07e54d4 100644
--- a/src/pdm/installers/manager.py
+++ b/src/pdm/installers/manager.py
@@ -33,7 +33,7 @@ def install(self, candidate: Candidate) -> Distribution:
             prepared.build(),
             self.environment,
             direct_url=prepared.direct_url(),
-            install_links=self.use_install_cache,
+            install_links=self.use_install_cache and not candidate.req.editable,
             rename_pth=self.rename_pth,
         )
         return Distribution.at(dist_info)


Test patch:
diff --git a/tests/test_installer.py b/tests/test_installer.py
index 451b6c70a7..60501b11ee 100644
--- a/tests/test_installer.py
+++ b/tests/test_installer.py
@@ -2,6 +2,7 @@
 
 import logging
 import os
+from pathlib import Path
 from typing import Callable
 
 import pytest
@@ -167,6 +168,35 @@ def test_url_requirement_is_not_cached(project):
     assert dist.read_text("direct_url.json")
 
 
+def test_editable_is_not_cached(project, tmp_path_factory):
+    editable_path: Path = tmp_path_factory.mktemp("editable-project")
+
+    editable_setup = editable_path / "setup.py"
+    editable_setup.write_text("""
+from setuptools import setup
+
+setup(name='editable-project',
+      version='0.1.0',
+      description='',
+      py_modules=['module'],
+)
+""")
+    editable_module = editable_path / "module.py"
+    editable_module.write_text("")
+
+    req = parse_requirement(f"file://{editable_path}#egg=editable-project", True)
+    candidate = Candidate(req)
+    installer = InstallManager(project.environment, use_install_cache=True)
+    installer.install(candidate)
+
+    cache_path = project.cache("packages") / "editable_project-0.1.0-0.editable-py3-none-any.whl.cache"
+    assert not cache_path.is_dir()
+    lib_path = Path(project.environment.get_paths()["purelib"])
+    for pth in lib_path.glob("*editable_project*.pth"):
+        assert pth.is_file()
+        assert not pth.is_symlink()
+
+
 @pytest.mark.parametrize("use_install_cache", [False, True])
 def test_install_wheel_with_data_scripts(project, use_install_cache):
     req = parse_requirement("jmespath")