Merge pull request #20 from mbeutelspacher/feat/persistent_cache

feat: add the option to store and load sha256 cache in/from a file
This commit is contained in:
Michal Sojka 2025-04-22 21:39:26 +02:00 committed by GitHub
commit 63c76d6aac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 13 deletions

View file

@ -116,7 +116,7 @@ usage: ros2nix [-h]
[--output-dir OUTPUT_DIR] [--fetch] [--use-per-package-src] [--output-dir OUTPUT_DIR] [--fetch] [--use-per-package-src]
[--patches | --no-patches] [--distro DISTRO] [--patches | --no-patches] [--distro DISTRO]
[--src-param SRC_PARAM] [--source-root SOURCE_ROOT] [--src-param SRC_PARAM] [--source-root SOURCE_ROOT]
[--do-check] [--extra-build-inputs DEP1,DEP2,...] [--no-cache] [--do-check] [--extra-build-inputs DEP1,DEP2,...]
[--extra-propagated-build-inputs DEP1,DEP2,...] [--extra-propagated-build-inputs DEP1,DEP2,...]
[--extra-check-inputs DEP1,DEP2,...] [--extra-check-inputs DEP1,DEP2,...]
[--extra-native-build-inputs DEP1,DEP2,...] [--flake] [--extra-native-build-inputs DEP1,DEP2,...] [--flake]
@ -172,6 +172,8 @@ options:
Set sourceRoot attribute value in the generated Nix Set sourceRoot attribute value in the generated Nix
expression. Substring '{package_name}' gets replaced expression. Substring '{package_name}' gets replaced
with the package name. (default: None) with the package name. (default: None)
--no-cache Don't use cache of git checkout sha265 hashes across
generation runs. (default: False)
--do-check Set doCheck attribute to true (default: False) --do-check Set doCheck attribute to true (default: False)
--extra-build-inputs DEP1,DEP2,... --extra-build-inputs DEP1,DEP2,...
Additional dependencies to add to the generated Nix Additional dependencies to add to the generated Nix

View file

@ -15,6 +15,7 @@ import re
import subprocess import subprocess
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path
from textwrap import dedent, indent from textwrap import dedent, indent
from typing import Iterable, Set, List from typing import Iterable, Set, List
@ -26,6 +27,23 @@ from superflore.utils import err, ok, resolve_dep, warn
from .nix_expression import NixExpression, NixLicense from .nix_expression import NixExpression, NixLicense
# Copied from https://github.com/srstevenson/xdg-base-dirs
# Copyright © Scott Stevenson <scott@stevenson.io>
# Less than 10 lines, no need to mention full ISC license here.
def _path_from_env(variable: str, default: Path) -> Path:
if (value := os.environ.get(variable)) and (path := Path(value)).is_absolute():
return path
return default
def xdg_cache_home() -> Path:
"""Return a Path corresponding to XDG_CACHE_HOME."""
return _path_from_env("XDG_CACHE_HOME", Path.home() / ".cache")
cache_file = xdg_cache_home() / "ros2nix" / "git-cache.json"
def resolve_dependencies(deps: Iterable[str]) -> Set[str]: def resolve_dependencies(deps: Iterable[str]) -> Set[str]:
return set(itertools.chain.from_iterable(map(resolve_dependency, deps))) return set(itertools.chain.from_iterable(map(resolve_dependency, deps)))
@ -280,6 +298,11 @@ def ros2nix(args):
help="Set sourceRoot attribute value in the generated Nix expression. " help="Set sourceRoot attribute value in the generated Nix expression. "
"Substring '{package_name}' gets replaced with the package name.", "Substring '{package_name}' gets replaced with the package name.",
) )
parser.add_argument(
"--no-cache",
action="store_true",
help="Don't use cache of git checkout sha265 hashes across generation runs.",
)
parser.add_argument( parser.add_argument(
"--do-check", "--do-check",
action="store_true", action="store_true",
@ -357,6 +380,9 @@ def ros2nix(args):
expressions: dict[str, str] = {} expressions: dict[str, str] = {}
git_cache = {} git_cache = {}
if not args.no_cache and os.path.exists(cache_file):
with open(cache_file) as f:
git_cache = json.load(f)
patch_filenames = set() patch_filenames = set()
for source in args.source: for source in args.source:
@ -414,19 +440,23 @@ def ros2nix(args):
cwd=srcdir, shell=True).decode().strip() cwd=srcdir, shell=True).decode().strip()
if args.use_per_package_src: if args.use_per_package_src:
# we need to get merge_base again to filter out applied patches from the package git hash # Set head to point to the last commit the subdirectory was changed. This is
merge_base = merge_base_to_upstream(head) # not strictly necessary, but it will increase hit rate of git_cache.
merge_base = merge_base_to_upstream(head) # filter out locally applied patches
head = check_output(f"git rev-list {merge_base} -1 -- .".split()) head = check_output(f"git rev-list {merge_base} -1 -- .".split())
if not args.use_per_package_src and toplevel in git_cache: # only use cache if not using separate checkout per package def cache_key(url, prefix):
info = git_cache[toplevel] if args.use_per_package_src:
upstream_rev = info["rev"] return f"{url}?dir={prefix}"
else: return url
# Latest commit present in the upstream repo. If
# the local repository doesn't have additional # Latest commit present in the upstream repo. If
# commits, it is the same as HEAD. Should work # the local repository doesn't have additional
# even with detached HEAD. # commits, it is the same as HEAD. Should work
upstream_rev = merge_base_to_upstream(head) # even with detached HEAD.
upstream_rev = merge_base_to_upstream(head)
info = git_cache.get(cache_key(url, prefix))
if info is None or info["rev"] != upstream_rev:
info = json.loads( info = json.loads(
subprocess.check_output( subprocess.check_output(
["nix-prefetch-git", "--quiet"] ["nix-prefetch-git", "--quiet"]
@ -438,7 +468,7 @@ def ros2nix(args):
+ [toplevel, upstream_rev], + [toplevel, upstream_rev],
).decode() ).decode()
) )
git_cache[toplevel] = info git_cache[cache_key(url, prefix)] = {k : info[k] for k in ["rev", "sha256"]}
match = re.match("https://github.com/(?P<owner>[^/]*)/(?P<repo>.*?)(.git|/.*)?$", url) match = re.match("https://github.com/(?P<owner>[^/]*)/(?P<repo>.*?)(.git|/.*)?$", url)
sparse_checkout = f"""sparseCheckout = ["{prefix}"]; sparse_checkout = f"""sparseCheckout = ["{prefix}"];
@ -568,6 +598,11 @@ def ros2nix(args):
generate_default(args) generate_default(args)
# TODO generate also release.nix (for testing/CI)? # TODO generate also release.nix (for testing/CI)?
if not args.no_cache:
os.makedirs(os.path.dirname(cache_file), exist_ok=True)
with open(cache_file, "w") as f:
json.dump(git_cache, f)
if args.compare and compare_failed: if args.compare and compare_failed:
err("Some files are not up-to-date") err("Some files are not up-to-date")
return 2 return 2