nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1import json
2import os
3import pathlib
4import requests
5import shutil
6import subprocess
7import sys
8import tempfile
9
10
11def replace_in_file(file_path, replacements):
12 file_contents = pathlib.Path(file_path).read_text()
13 for old, new in replacements.items():
14 if old == new:
15 continue
16 updated_file_contents = file_contents.replace(old, new)
17 # A dumb way to check that we’ve actually replaced the string.
18 if file_contents == updated_file_contents:
19 print(f"no string to replace: {old} → {new}", file=sys.stderr)
20 sys.exit(1)
21 file_contents = updated_file_contents
22 with tempfile.NamedTemporaryFile(mode="w") as t:
23 t.write(file_contents)
24 t.flush()
25 shutil.copyfile(t.name, file_path)
26
27
28def nix_hash_to_sri(hash):
29 return subprocess.run(
30 [
31 "nix",
32 "--extra-experimental-features", "nix-command",
33 "hash",
34 "to-sri",
35 "--type", "sha256",
36 "--",
37 hash,
38 ],
39 stdout=subprocess.PIPE,
40 text=True,
41 check=True,
42 ).stdout.rstrip()
43
44
45nixpkgs_path = "."
46attr_path = os.getenv("UPDATE_NIX_ATTR_PATH", "sonarr")
47
48package_attrs = json.loads(subprocess.run(
49 [
50 "nix",
51 "--extra-experimental-features", "nix-command",
52 "eval",
53 "--json",
54 "--file", nixpkgs_path,
55 "--apply", """p: {
56 dir = dirOf p.meta.position;
57 version = p.version;
58 sourceHash = p.src.src.outputHash;
59 yarnHash = p.yarnOfflineCache.outputHash;
60 }""",
61 "--",
62 attr_path,
63 ],
64 stdout=subprocess.PIPE,
65 text=True,
66 check=True,
67).stdout)
68
69old_version = package_attrs["version"]
70new_version = old_version
71
72# Note that we use Sonarr API instead of GitHub to fetch latest stable release.
73# This corresponds to the Updates tab in the web UI. See also
74# https://github.com/Sonarr/Sonarr/blob/070919a7e6a96ca7e26524996417c6f8d1b5fcaa/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
75version_update = requests.get(
76 f"https://services.sonarr.tv/v1/update/main?version={old_version}",
77).json()
78if version_update["available"]:
79 new_version = version_update["updatePackage"]["version"]
80
81if new_version == old_version:
82 sys.exit()
83
84source_nix_hash, source_store_path = subprocess.run(
85 [
86 "nix-prefetch-url",
87 "--name", "source",
88 "--unpack",
89 "--print-path",
90 f"https://github.com/Sonarr/Sonarr/archive/v{new_version}.tar.gz",
91 ],
92 stdout=subprocess.PIPE,
93 text=True,
94 check=True,
95).stdout.rstrip().split("\n")
96
97old_source_hash = package_attrs["sourceHash"]
98new_source_hash = nix_hash_to_sri(source_nix_hash)
99
100package_dir = package_attrs["dir"]
101package_file_name = "package.nix"
102deps_file_name = "deps.json"
103
104# To update deps.nix, we copy the package to a temporary directory and run
105# passthru.fetch-deps script there.
106with tempfile.TemporaryDirectory() as work_dir:
107 package_file = os.path.join(work_dir, package_file_name)
108 deps_file = os.path.join(work_dir, deps_file_name)
109
110 shutil.copytree(package_dir, work_dir, dirs_exist_ok=True)
111
112 replace_in_file(package_file, {
113 # NB unlike hashes, versions are likely to be used in code or comments.
114 # Try to be more specific to avoid false positive matches.
115 f"version = \"{old_version}\"": f"version = \"{new_version}\"",
116 old_source_hash: new_source_hash,
117 })
118
119 # We need access to the patched and updated src to get the patched
120 # `yarn.lock`.
121 patched_src = os.path.join(work_dir, "patched-src")
122 subprocess.run(
123 [
124 "nix",
125 "--extra-experimental-features", "nix-command",
126 "build",
127 "--impure",
128 "--nix-path", "",
129 "--include", f"nixpkgs={nixpkgs_path}",
130 "--include", f"package={package_file}",
131 "--expr", "(import <nixpkgs> { }).callPackage <package> { }",
132 "--out-link", patched_src,
133 "src",
134 ],
135 check=True,
136 )
137 old_yarn_hash = package_attrs["yarnHash"]
138 new_yarn_hash = nix_hash_to_sri(subprocess.run(
139 [
140 "prefetch-yarn-deps",
141 # does not support "--" separator :(
142 # Also --verbose writes to stdout, yikes.
143 os.path.join(patched_src, "yarn.lock"),
144 ],
145 stdout=subprocess.PIPE,
146 text=True,
147 check=True,
148 ).stdout.rstrip())
149
150 replace_in_file(package_file, {
151 old_yarn_hash: new_yarn_hash,
152 })
153
154 # Generate nuget-to-json dependency lock file.
155 fetch_deps = os.path.join(work_dir, "fetch-deps")
156 subprocess.run(
157 [
158 "nix",
159 "--extra-experimental-features", "nix-command",
160 "build",
161 "--impure",
162 "--nix-path", "",
163 "--include", f"nixpkgs={nixpkgs_path}",
164 "--include", f"package={package_file}",
165 "--expr", "(import <nixpkgs> { }).callPackage <package> { }",
166 "--out-link", fetch_deps,
167 "passthru.fetch-deps",
168 ],
169 check=True,
170 )
171 subprocess.run(
172 [
173 fetch_deps,
174 deps_file,
175 ],
176 stdout=subprocess.DEVNULL,
177 check=True,
178 )
179
180 shutil.copy(deps_file, os.path.join(package_dir, deps_file_name))
181 shutil.copy(package_file, os.path.join(package_dir, package_file_name))