Skip to content

Commit c5324bc

Browse files
author
Shelvacu
committed
Grab proot from bootstrap zip rather than including its nix path directly.
This means that the cachix substituter (or already having the package in your nix store somehow) is no longer required to build. This required reworking the deploy script. As a bonus you can now omit the second argument and it will tell you what it would have copied instead of copying anything. This is fixes one source of impurity, but for now flake builds will still require the --impure flag
1 parent 248cc08 commit c5324bc

File tree

12 files changed

+278
-121
lines changed

12 files changed

+278
-121
lines changed

.github/workflows/emulator.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
rm -rf n-o-d
4848
mkdir -p n-o-d
4949
git -C . archive --format=tar.gz --prefix n-o-d/ HEAD > n-o-d/archive.tar.gz
50-
ARCHES=x86_64 nix run '.#deploy' -- file:///data/local/tmp/n-o-d/archive.tar.gz n-o-d/
50+
ARCHES=x86_64 nix run '.#deploy' -- file:///data/local/tmp/n-o-d/archive.tar.gz --rsync-target n-o-d/
5151
tar cf n-o-d.tar n-o-d
5252
5353
- name: Store zipball and channel tarball to inject (n-o-d)

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
deploy = {
6161
type = "app";
62-
program = toString (import ./scripts/deploy.nix { inherit nixpkgs system; });
62+
program = import ./scripts/deploy.nix { inherit nixpkgs system; };
6363
};
6464
});
6565

modules/environment/login/default.nix

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ in
3737

3838
prootStatic = mkOption {
3939
type = types.package;
40-
readOnly = true;
40+
# not readOnly, this needs to be overridden when building bootstrap zip
4141
internal = true;
4242
description = "<literal>proot-static</literal> package.";
4343
};
@@ -84,14 +84,27 @@ in
8484
environment.files = {
8585
inherit login loginInner;
8686

87+
# Ideally this would build the static proot binary, but doing that on aarch64 is HARD so instead pull it from the bootstrap tarball
8788
prootStatic =
8889
let
89-
crossCompiledPaths = {
90-
aarch64-linux = "/nix/store/7qd99m1w65x2vgqg453nd70y60sm3kay-proot-termux-static-aarch64-unknown-linux-android-unstable-2024-05-04";
91-
x86_64-linux = "/nix/store/pakj3svvw84rhkzdc6211yhc2cgvc21f-proot-termux-static-x86_64-unknown-linux-android-unstable-2024-05-04";
90+
attrs = (import ./proot-attrs).${targetSystem};
91+
prootFile = pkgs.fetchurl {
92+
name = "proot-static-file";
93+
inherit (attrs) url hash;
94+
95+
downloadToTemp = true;
96+
executable = true;
97+
postFetch = ''
98+
${pkgs.unzip}/bin/unzip -u $downloadedFile bin/proot-static
99+
echo $PWD >&2
100+
mv bin/proot-static $out
101+
'';
92102
};
93103
in
94-
"${crossCompiledPaths.${targetSystem}}";
104+
pkgs.runCommand "proot-static" { } ''
105+
mkdir -p $out/bin
106+
cp ${prootFile} $out/bin/proot-static
107+
'';
95108
};
96109

97110
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# WARNING: This file is autogenerated by the deploy script. Any changes will be overridden
2+
{
3+
url = "https://nix-on-droid.unboiled.info/bootstrap-testing/bootstrap-aarch64.zip";
4+
hash = "sha256-fZyqldmHWbv2e6543mwb5UPcKxcODRg+PDvZNcjVyUU=";
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
x86_64-linux = import ./x86_64.nix;
3+
aarch64-linux = import ./aarch64.nix;
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# WARNING: This file is autogenerated by the deploy script. Any changes will be overridden
2+
{
3+
url = "https://nix-on-droid.unboiled.info/bootstrap-testing/bootstrap-x86_64.zip";
4+
hash = "sha256-1WZBmFNEmZucOwuzDAFO+Sl+b2XO7Lp1aQr/4egl4wU=";
5+
}

pkgs/default.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ let
4242
# Fix invoking bash after initial build.
4343
user.shell = "${initialPackageInfo.bash}/bin/bash";
4444

45+
environment.files.prootStatic = pkgs.lib.mkForce customPkgs.prootTermux;
46+
4547
build = {
4648
channel = {
4749
nixpkgs = urlOptionValue nixpkgsChannelURL "NIXPKGS_CHANNEL_URL";

scripts/deploy.nix

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,44 @@
44

55
let
66
pkgs = nixpkgs.legacyPackages.${system};
7-
8-
runtimePackages = with pkgs; [
9-
coreutils
10-
git
11-
gnugrep
12-
gnused
13-
gnutar
14-
gzip
15-
jq
16-
nix
17-
openssh
18-
rsync
7+
pypkgs = pkgs.python311Packages;
8+
disablePyLints = [
9+
"line-too-long"
10+
"missing-module-docstring"
11+
"wrong-import-position" # import should be at top of file: we purposefully don't import click and such so that users that try to run the script directly get a friendly error
12+
"missing-function-docstring"
13+
# c'mon, it's a script
14+
"too-many-locals"
15+
"too-many-branches"
16+
"too-many-statements"
1917
];
20-
in
18+
deriv = pypkgs.buildPythonApplication {
19+
pname = "deploy";
20+
version = "0.0";
21+
src = ./.;
22+
23+
inherit (pkgs) nix git rsync;
2124

22-
pkgs.runCommand
23-
"deploy"
24-
{
25-
preferLocalBuild = true;
26-
allowSubstitutes = false;
27-
}
28-
''
29-
install -D -m755 ${./deploy.sh} $out
25+
propagatedBuildInputs = [ pypkgs.click ];
3026

31-
substituteInPlace $out \
32-
--subst-var-by bash "${pkgs.bash}" \
33-
--subst-var-by path "${pkgs.lib.makeBinPath runtimePackages}"
34-
''
27+
doCheck = true;
28+
nativeCheckInputs = with pypkgs; [ mypy pylint black ];
29+
checkPhase = ''
30+
mypy --strict --no-color deploy.py
31+
PYLINTHOME="$PWD/.pylint" pylint \
32+
--score=n \
33+
--clear-cache-post-run=y \
34+
--disable=${pkgs.lib.concatStringsSep "," disablePyLints} \
35+
deploy.py
36+
black --check --diff deploy.py
37+
'';
38+
39+
patchPhase = ''
40+
substituteInPlace deploy.py \
41+
--subst-var nix \
42+
--subst-var git \
43+
--subst-var rsync
44+
'';
45+
};
46+
in
47+
"${deriv}/bin/deploy"

scripts/deploy.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import os
2+
import sys
3+
4+
GIT = "@git@/bin/git"
5+
NIX = "@nix@/bin/nix"
6+
NIX_HASH = "@nix@/bin/nix-hash"
7+
RSYNC = "@rsync@/bin/rsync"
8+
9+
if GIT.startswith("@"):
10+
sys.stderr.write(
11+
"Do not run this script directly, instead try: nix run .#deploy -- --help"
12+
)
13+
sys.exit(1)
14+
15+
import subprocess
16+
import re
17+
import inspect
18+
from typing import Never
19+
from pathlib import Path
20+
21+
import click
22+
23+
24+
def err(text: str) -> Never:
25+
sys.stderr.write(text)
26+
sys.exit(1)
27+
28+
29+
def run(*args: str) -> None:
30+
subprocess.run(args, check=True)
31+
32+
33+
def run_capture(*args: str, env: dict[str, str] | None = None) -> str:
34+
proc = subprocess.run(
35+
args, check=True, stdout=subprocess.PIPE, stderr=None, env=env
36+
)
37+
return proc.stdout.decode("utf-8").strip()
38+
39+
40+
def log(msg: str) -> None:
41+
print(f"> {msg}")
42+
43+
44+
@click.command()
45+
@click.option(
46+
"--rsync-target",
47+
help="Where bootstrap zipballs and source tarball will be copied to. If given, this is passed directly to rsync so it can be a local folder, ssh path, etc. For production builds this should be a webroot directory that will be served at bootstrap-url",
48+
)
49+
@click.option(
50+
"--bootstrap-url",
51+
help="URL where bootstrap zip files are available. Defaults to folder part of public-url if not given.",
52+
)
53+
@click.option(
54+
"--arches",
55+
default="aarch64,x86_64",
56+
help="Which architectures to build for, comma-separated.",
57+
)
58+
@click.argument("public-url")
59+
def go(
60+
public_url: str,
61+
rsync_target: str | None,
62+
bootstrap_url: str | None,
63+
arches: str,
64+
) -> None:
65+
"""
66+
Builds bootstrap zip balls and source code tar ball (for usage as a channel or flake). If rsync_target is specified, uploads it to the directory specified in rsync_target. The contents of this directory should be reachable by the android device with public_url.
67+
68+
Examples:
69+
70+
\b
71+
$ nix run .#deploy -- \\
72+
'https://example.com/bootstrap/source.tar.gz' \\
73+
--rsync-target 'user@host:/path/to/bootstrap'
74+
75+
\b
76+
$ nix run .#deploy -- \\
77+
'github:USER/nix-on-droid/BRANCH' \\
78+
--rsync-target 'user@host:/path/to/bootstrap' \\
79+
--bootstrap-url 'https://example.com/bootstrap/'
80+
81+
\b
82+
$ nix run .#deploy -- \\
83+
'file:///data/local/tmp/n-o-d/archive.tar.gz'
84+
85+
^ useful for testing. Note this is a path on the android device running the APK, not on the build machine
86+
"""
87+
repo_dir = run_capture(GIT, "rev-parse", "--show-toplevel")
88+
os.chdir(repo_dir)
89+
source_file = "source.tar.gz"
90+
if (m := re.search("^github:(.*)/(.*)/(.*)", public_url)) is not None:
91+
channel_url = f"https://github.com/{m[1]}/{m[2]}/archive/{m[3]}.tar.gz"
92+
if bootstrap_url is None:
93+
err("--botstrap-url must be provided for github URLs")
94+
elif re.search("^(https?|file)://", public_url):
95+
channel_url = public_url
96+
else:
97+
err(f"unsupported url {public_url}")
98+
99+
# for CI and local testing
100+
if (m := re.search("^file:///(.*)/archive.tar.gz$", public_url)) is not None:
101+
flake_url = f"/{m[1]}/unpacked"
102+
else:
103+
flake_url = public_url
104+
base_url = re.sub("/[^/]*$", "", public_url)
105+
if bootstrap_url is None:
106+
bootstrap_url = base_url
107+
108+
log(f"channel_url = {channel_url}")
109+
log(f"flake_url = {flake_url}")
110+
log(f"base_url = {base_url}")
111+
log(f"bootstrap_url = {bootstrap_url}")
112+
113+
uploads: list[str] = []
114+
115+
for arch in arches.split(","):
116+
log(f"building {arch} proot...")
117+
proot = run_capture(
118+
NIX, "build", "--no-link", "--print-out-paths", f".#prootTermux-{arch}"
119+
)
120+
proot_hash = run_capture(
121+
NIX_HASH, "--type", "sha256", "--sri", f"{proot}/bin/proot-static"
122+
)
123+
attrs_file = Path(f"modules/environment/login/proot-attrs/{arch}.nix")
124+
attrs_text = inspect.cleandoc(
125+
f"""
126+
# WARNING: This file is autogenerated by the deploy script. Any changes will be overridden
127+
{{
128+
url = "{bootstrap_url}/bootstrap-{arch}.zip";
129+
hash = "{proot_hash}";
130+
}}
131+
"""
132+
)
133+
# nixpkgs-fmt insists files must end with a newline
134+
attrs_text = attrs_text + "\n"
135+
write_attrs_file = True
136+
if not attrs_file.exists():
137+
log(f"warn: {attrs_file} not present; creating")
138+
elif (old_attrs_text := attrs_file.read_text(encoding="utf-8")) != attrs_text:
139+
log(f"updating contents of {attrs_file}")
140+
print("<<<<<<")
141+
print(old_attrs_text)
142+
print("======")
143+
print(attrs_text)
144+
print(">>>>>>")
145+
else:
146+
write_attrs_file = False
147+
log(f"no changes needed to {attrs_file}")
148+
149+
if write_attrs_file:
150+
attrs_file.write_text(attrs_text, newline="\n", encoding="utf-8")
151+
log(f"adding {attrs_file} to git index")
152+
run(GIT, "add", str(attrs_file))
153+
154+
bootstrap_zip_store_path = run_capture(
155+
NIX,
156+
"build",
157+
"--no-link",
158+
"--print-out-paths",
159+
"--impure",
160+
f".#bootstrapZip-{arch}",
161+
env={
162+
"NIX_ON_DROID_CHANNEL_URL": channel_url,
163+
"NIX_ON_DROID_FLAKE_URL": flake_url,
164+
},
165+
)
166+
uploads.append(bootstrap_zip_store_path + f"/bootstrap-{arch}.zip")
167+
168+
log("creating tarball of current HEAD")
169+
run(GIT, "archive", "--prefix", "nix-on-droid/", "--output", source_file, "HEAD")
170+
uploads.append(source_file)
171+
172+
if rsync_target is not None:
173+
log("uploading artifacts...")
174+
run(RSYNC, "--progress", *uploads, rsync_target)
175+
else:
176+
log(f"Would have uploaded {uploads}")
177+
178+
179+
if __name__ == "__main__":
180+
# pylint: disable = no-value-for-parameter
181+
go()

0 commit comments

Comments
 (0)