6.NUR/ci/nur/prefetch.py

97 lines
3.3 KiB
Python

import json
import os
import re
import subprocess
from pathlib import Path
from typing import Optional, Tuple
from urllib.parse import ParseResult
from .error import NurError
from .manifest import LockedVersion, Repo, RepoType
Url = ParseResult
def nix_prefetch_zip(url: str) -> Tuple[str, Path]:
data = subprocess.check_output(
["nix-prefetch-url", "--name", "source", "--unpack", "--print-path", url]
)
sha256, path = data.decode().strip().split("\n")
return sha256, Path(path)
class GitPrefetcher:
def __init__(self, repo: Repo) -> None:
self.repo = repo
def latest_commit(self) -> str:
data = subprocess.check_output(
["git", "ls-remote", self.repo.url.geturl(), self.repo.branch or "HEAD"],
env={**os.environ, "GIT_ASKPASS": "", "GIT_TERMINAL_PROMPT": "0"},
)
return data.decode().split(maxsplit=1)[0]
def prefetch(self, ref: str) -> Tuple[str, Path]:
cmd = ["nix-prefetch-git"]
if self.repo.submodules:
cmd += ["--fetch-submodules"]
if self.repo.branch:
cmd += ["--rev", f"refs/heads/{self.repo.branch}"]
cmd += [self.repo.url.geturl()]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
stdout, stderr = proc.communicate(timeout=30)
except subprocess.TimeoutExpired:
proc.kill()
raise NurError(
f"Timeout expired while prefetching git repository {self. repo.url.geturl()}"
)
if proc.returncode != 0:
raise NurError(
f"Failed to prefetch git repository {self.repo.url.geturl()}: {stderr.decode('utf-8')}"
)
metadata = json.loads(stdout)
lines = stderr.decode("utf-8").split("\n")
repo_path = re.search("path is (.+)", lines[-5])
assert repo_path is not None
path = Path(repo_path.group(1))
sha256 = metadata["sha256"]
return sha256, path
class GithubPrefetcher(GitPrefetcher):
def prefetch(self, ref: str) -> Tuple[str, Path]:
return nix_prefetch_zip(f"{self.repo.url.geturl()}/archive/{ref}.tar.gz")
class GitlabPrefetcher(GitPrefetcher):
def prefetch(self, ref: str) -> Tuple[str, Path]:
hostname = self.repo.url.hostname
assert (
hostname is not None
), f"Expect a hostname for Gitlab repo: {self.repo.name}"
path = Path(self.repo.url.path)
escaped_path = "%2F".join(path.parts[1:])
url = f"https://{hostname}/api/v4/projects/{escaped_path}/repository/archive.tar.gz?sha={ref}"
return nix_prefetch_zip(url)
def prefetch(repo: Repo) -> Tuple[Repo, LockedVersion, Optional[Path]]:
prefetcher: GitPrefetcher
if repo.type == RepoType.GITHUB:
prefetcher = GithubPrefetcher(repo)
elif repo.type == RepoType.GITLAB:
prefetcher = GitlabPrefetcher(repo)
else:
prefetcher = GitPrefetcher(repo)
commit = prefetcher.latest_commit()
locked_version = repo.locked_version
if locked_version is not None:
if locked_version.rev == commit:
return repo, locked_version, None
sha256, path = prefetcher.prefetch(commit)
return repo, LockedVersion(repo.url, commit, sha256, repo.submodules), path