-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathautomation.py
executable file
·156 lines (124 loc) · 5.9 KB
/
automation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python3
import json
from argparse import ArgumentParser
from dataclasses import dataclass
from os import chdir, environ
from subprocess import DEVNULL, PIPE, STDOUT, CompletedProcess
from subprocess import run as _run
from sys import stderr, path
from typing import Any, Dict, List, Set
################################################################
## General Utilities
################################################################
def run(*command: str, **kwargs) -> CompletedProcess[Any]:
"""Wrapper around `run` that prints the command."""
format_arg = lambda arg: repr(arg) if " " in arg else arg
print(f"\033[34m$ {' '.join(map(format_arg, command))}\033[0m", file=stderr)
return _run(list(command), **kwargs)
def nix(*args: str) -> str:
"""Run `nix <ARGS>` and return the output."""
return run("nix", *args, stdout=PIPE, check=True).stdout.decode("utf-8")
def nix_eval_json(*args: str) -> Any:
"""Run `nix eval --json <ARGS>` and parse and return the output."""
return json.loads(nix("eval", "--json", *args))
def nix_config() -> Dict[str, str]:
"""Run `nix show-config` and parse and return the output."""
return dict(
line.split(" = ", 1)
for line in nix("show-config").splitlines()
if " = " in line
)
def set_github_output(key: str, value: Any) -> None:
"""Set the specified (JSON) output if running in GitHub Actions."""
if "GITHUB_OUTPUT" in environ:
with open(environ["GITHUB_OUTPUT"], "a") as file:
print(f"{key}={json.dumps(value)}", file=file)
################################################################
## Package Utilities
################################################################
@dataclass(frozen=True)
class Package:
name: str
path: str
broken: bool
has_update_script: bool
def is_in_cache(self, cache: str) -> bool:
"""Check whether the package is in the specified binary cache."""
command = ["nix", "path-info", "--store", cache, self.path]
return run(*command, stdout=DEVNULL, stderr=DEVNULL).returncode == 0
def is_in_any_cache(self, caches: List[str]) -> bool:
"""Check whether the package is in any of the specified binary caches."""
return any(self.is_in_cache(cache) for cache in caches)
def get_all_packages() -> Set[Package]:
"""Get all the packages in `.#packages.${builtins.currentSystem}`."""
system = nix_eval_json("--impure", "--expr", "builtins.currentSystem")
all_packages = nix_eval_json(
f".#packages.{system}",
"--apply",
"builtins.mapAttrs (_: drv: {"
" path = drv.outPath;"
" broken = drv.meta.broken;"
" has_update_script = drv.passthru ? updateScript;"
" })",
)
return {Package(name, **attrs) for name, attrs in all_packages.items()}
def print_packages(title: str, packages: Set[Package]) -> None:
"""Display a set of packages in alphabetical order by store path."""
print(f"**{title}:**")
for package in sorted(list(packages), key=lambda p: p.name) or [None]:
print(f"- {package.path if package else '(none)'}")
print()
################################################################
## Main Stuff
################################################################
def get_packages_to_build() -> None:
"""Get the list of packages to build (not broken or cached)."""
# Reverse substituters so that extra-substituters are checked first.
caches = list(reversed(nix_config()["substituters"].split()))
all_packages = get_all_packages()
broken_packages = {package for package in all_packages if package.broken}
cached_packages = {
package
for package in all_packages - broken_packages
if package.is_in_any_cache(caches)
}
packages_to_build = all_packages - broken_packages - cached_packages
print_packages("These packages will be built", packages_to_build)
print_packages("These packages are marked as broken", broken_packages)
print_packages("These packages have already been built", cached_packages)
set_github_output("packages", [package.name for package in packages_to_build])
def get_packages_to_update() -> None:
"""Get the list of packages to update (has `passthru.updateScript`)."""
all_packages = get_all_packages()
packages_to_update = {
package for package in all_packages if package.has_update_script
}
print_packages("These packages support automated updates", packages_to_update)
set_github_output("packages", [package.name for package in packages_to_update])
def update_package(name: str) -> None:
"""Update the specified package and commit the changes, if any."""
version_pre = nix_eval_json(f".#{name}.version")
nix("build", f".#{name}.passthru.updateScript")
run("./result", name, stderr=STDOUT, check=True)
version_post = nix_eval_json(f".#{name}.version")
if version_pre != version_post:
commit_message = f"{name}: {version_pre} -> {version_post}"
run("git", "commit", "-m", commit_message, "--", name)
if __name__ == "__main__":
chdir(path[0])
parser = ArgumentParser(description="nixpkgs-overlay automation helper")
command = parser.add_subparsers(dest="command", required=True)
command.add_parser("get-packages-to-build", help="get list of packages to build")
command.add_parser("get-packages-to-update", help="get list of packages to update")
update = command.add_parser("update-package", help="update the specified package")
update.add_argument("name", help="name of the package to update")
args = parser.parse_args()
try:
{
"get-packages-to-build": get_packages_to_build,
"get-packages-to-update": get_packages_to_update,
"update-package": lambda: update_package(args.name),
}[args.command]()
except Exception as error:
print(f"\033[31m{type(error).__name__}: {error}\033[0m", file=stderr)
exit(1)