Skip to content

Commit b1ec957

Browse files
committed
Cache git dependencies as wheels
Currently, poetry install will clone, build and install every git dependency when it's not present in the environment. This is OK for developer's machines, but not OK for CI - there environment is always fresh, and installing git dependencies takes significant time on each CI run, especially if the dependency has C extensions that need to be built. This commit builds a wheel for every git dependency that has precise reference hash in lock file and is not required to be in editable mode, stores that wheel in a cache dir and will install from it instead of cloning the repository again.
1 parent 53cdce0 commit b1ec957

File tree

2 files changed

+26
-3
lines changed

2 files changed

+26
-3
lines changed

src/poetry/installation/chef.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ def get_cache_directory_for_link(self, link: Link) -> Path:
251251
self._env.marker_env["interpreter_version"].split(".")[:2]
252252
)
253253

254+
return self._get_directory_from_hash(key_parts)
255+
256+
def get_cache_directory_for_git(self, url: str, ref: str) -> Path:
257+
return self._get_directory_from_hash({"url": url, "ref": ref})
258+
259+
def _get_directory_from_hash(self, key_parts: object) -> Path:
254260
key = hashlib.sha256(
255261
json.dumps(
256262
key_parts, sort_keys=True, separators=(",", ":"), ensure_ascii=True

src/poetry/installation/executor.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import itertools
66
import json
77
import os
8+
import shutil
89
import threading
910

1011
from concurrent.futures import ThreadPoolExecutor
@@ -502,7 +503,6 @@ def _install(self, operation: Install | Update) -> int:
502503
cleanup_archive: bool = False
503504
if package.source_type == "git":
504505
archive = self._prepare_git_archive(operation)
505-
cleanup_archive = True
506506
elif package.source_type == "file":
507507
archive = self._prepare_archive(operation)
508508
elif package.source_type == "directory":
@@ -599,14 +599,25 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path:
599599
from poetry.vcs.git import Git
600600

601601
package = operation.package
602+
reference = package.source_resolved_reference or package.source_reference
603+
assert package.source_url is not None
604+
assert reference is not None
605+
606+
cache_dir = self._chef.get_cache_directory_for_git(
607+
package.source_url, reference
608+
)
609+
cache_dir.mkdir(parents=True, exist_ok=True)
610+
cached_archive = next(cache_dir.glob("*.whl"), None)
611+
if cached_archive is not None and not operation.package.develop:
612+
return cached_archive
613+
602614
operation_message = self.get_operation_message(operation)
603615

604616
message = (
605617
f" <fg=blue;options=bold>•</> {operation_message}: <info>Cloning...</info>"
606618
)
607619
self._write(operation, message)
608620

609-
assert package.source_url is not None
610621
source = Git.clone(
611622
url=package.source_url,
612623
source_root=self._env.path / "src",
@@ -621,7 +632,13 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path:
621632

622633
package._source_url = original_url
623634

624-
return archive
635+
if operation.package.develop:
636+
return archive
637+
else:
638+
cached_archive = cache_dir / archive.name
639+
shutil.copy(archive, cached_archive)
640+
641+
return cached_archive
625642

626643
def _install_directory_without_wheel_installer(
627644
self, operation: Install | Update

0 commit comments

Comments
 (0)