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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 4 ]; then
echo "usage: scripts/build-socialweb-computer.sh --dev [--platform P]... [--build-root DIR] [--publish DIR [--sync-only]]" >&2
exit 2
fi
root="$1"
build_root="$2"
publish="$3"
sync_only="$4"
shift 4
platforms=("$@")
die() { echo "build-socialweb-computer --dev: $*" >&2; exit 1; }
say() { echo "build-socialweb-computer --dev: $(date -u +%Y-%m-%dT%H:%M:%SZ) $*" >&2; }
sha_of() { sha256sum "$1" | cut -d' ' -f1; }
started_s="$(date +%s.%N)"
elapsed() { awk -v a="$started_s" -v b="$(date +%s.%N)" 'BEGIN { printf "%.1f", b - a }'; }
command -v rsync >/dev/null 2>&1 || die "rsync is required for --dev"
command -v flock >/dev/null 2>&1 || die "flock is required for --dev"
name="$(basename "$root")"
dev_root="$build_root/dev/$name"
versions="$root/internal/versions/versions.json"
case "$publish" in
"") [ "$sync_only" = 0 ] || die "--sync-only needs --publish DIR" ;;
/*) ;;
*) die "--publish $publish is not an absolute path" ;;
esac
mirror() {
local dst="$1/src" list
mkdir -p "$dst" "$dev_root"
list="$dev_root/mirror.include"
git -C "$root" ls-files -z -co --exclude-standard \
| tr '\0' '\n' \
| awk '{ n = split($0, p, "/"); d = ""; for (i = 1; i < n; i++) { d = d "/" p[i]; if (!(d in seen)) { seen[d] = 1; print d "/" } } print "/" $0 }' \
>"$list"
rsync -a --delete --delete-excluded --include-from="$list" --exclude='*' "$root/" "$dst/"
chmod a+rx "$1"
chmod -R a+rX "$dst"
say "mirrored $(grep -vc '/$' "$list") files of $root to $dst ($(elapsed)s)"
}
if [ "$sync_only" = 1 ]; then
mirror "$publish"
exit 0
fi
want_go="$(awk '$1=="toolchain"{print $2; exit}' "$root/go.mod")"
[ -n "$want_go" ] || want_go="go$(awk '$1=="go"{print $2; exit}' "$root/go.mod")"
cache="$build_root/cache"
mkdir -p "$cache/assets" "$cache/gomod" "$cache/gocache" "$build_root/tmp" "$dev_root"
export TMPDIR="$build_root/tmp"
export GOTMPDIR="$build_root/tmp"
export GOMODCACHE="$cache/gomod"
export GOCACHE="$cache/gocache"
export GUEST_DOWNLOADS_CACHE="$cache/assets"
export GOFLAGS=-mod=readonly
export CGO_ENABLED=0
unset GOOS GOARCH GOPATH GOROOT SOURCE_DATE_EPOCH
goroot="$(GOTOOLCHAIN="$want_go" go env GOROOT)" || die "cannot get the pinned toolchain $want_go"
export PATH="$goroot/bin:$PATH"
export GOTOOLCHAIN=local
have_go="$(go env GOVERSION)"
[ "$have_go" = "$want_go" ] || die "the toolchain at $goroot is $have_go; go.mod pins $want_go"
hostplat="$(go env GOHOSTOS)/$(go env GOHOSTARCH)"
if [ ${#platforms[@]} -eq 0 ]; then
platforms=("$hostplat")
fi
for plat in "${platforms[@]}"; do
[ "$(jq -r --arg p "$plat" '.platforms[$p].status // ""' "$versions")" = "release" ] || die "$plat is not a release platform of versions.json"
done
if [ -n "$publish" ] && [[ " ${platforms[*]} " != *" $hostplat "* ]]; then
die "--publish copies the host binary; add --platform $hostplat"
fi
stage_plats=("${platforms[@]}")
[[ " ${stage_plats[*]} " == *" $hostplat "* ]] || stage_plats+=("$hostplat")
commit="$(git -C "$root" rev-parse HEAD)"
changed="$(git -C "$root" status --porcelain | grep -c . || true)"
dirty=false
[ "$changed" = 0 ] || dirty=true
say "worktree $root at $commit (dirty $dirty, $changed changed paths), platforms ${platforms[*]}, go $have_go"
shared_blobs="$build_root/dev/assets"
own_blobs="$dev_root/blobs"
mkdir -p "$shared_blobs" "$own_blobs"
from_tree() {
[ "$(jq -r --arg a "$1" --arg p "$2" '.assets[$a].fetch[$p].kind // ""' "$versions")" = go ]
}
blob_of() {
local plat="$1" asset="$2"
if from_tree "$asset" "$plat"; then
echo "$own_blobs/${plat/\//_}/$asset.tar.zst"
return
fi
echo "$shared_blobs/${plat/\//_}/$asset-$(jq -cS --arg a "$asset" '.assets[$a]' "$versions" | sha256sum | cut -c1-16).tar.zst"
}
{
flock 9
for plat in "${stage_plats[@]}"; do
key="${plat/\//_}"
missing=()
loose=()
for asset in $(jq -r --arg p "$plat" '.platforms[$p].assets[]' "$versions"); do
from_tree "$asset" "$plat" && continue
[ -f "$(blob_of "$plat" "$asset")" ] && continue
if [ "$asset" = socialweb-computer-k8s-downloads ] && [ "$(. /etc/os-release && echo "$ID")" != fedora ]; then
loose+=(--asset "$asset")
else
missing+=(--asset "$asset")
fi
done
if [ ${#missing[@]} -gt 0 ] || [ ${#loose[@]} -gt 0 ]; then
say "stage ${missing[*]} ${loose[*]} for $plat into $shared_blobs (once per versions.json entry of the asset)"
rm -rf "$shared_blobs/.stage"
[ ${#missing[@]} -eq 0 ] || "$root/scripts/stage-assets.sh" --cache "$cache/assets" --blobs "$shared_blobs/.stage" --platform "$plat" "${missing[@]}"
# ponytail: the meshd bytes follow the compiler packages, so only Fedora matches the pin; elsewhere the downloads asset is staged unpinned (the gateway's /downloads would not match the pin), add a pinned Fedora build when a non-Fedora host needs the web app downloads
[ ${#loose[@]} -eq 0 ] || "$root/scripts/stage-assets.sh" --cache "$cache/assets" --blobs "$shared_blobs/.stage" --platform "$plat" "${loose[@]}" --print-pins >/dev/null
mkdir -p "$shared_blobs/$key"
for f in "$shared_blobs/.stage/$key"/*.tar.zst; do
mv -f "$f" "$(blob_of "$plat" "$(basename "$f" .tar.zst)")"
done
rm -rf "$shared_blobs/.stage"
fi
done
} 9>"$shared_blobs/.lock"
for plat in "${platforms[@]}"; do
tree_assets=()
built=()
for asset in $(jq -r --arg p "$plat" '.platforms[$p].assets[]' "$versions"); do
from_tree "$asset" "$plat" || continue
spec="$(jq -c --arg a "$asset" --arg p "$plat" '.assets[$a].fetch[$p]' "$versions")"
probe="$dev_root/tree-bin/${plat/\//_}/$asset"
mkdir -p "$(dirname "$probe")"
(cd "$root/$(echo "$spec" | jq -r '.module // "."')" && GOOS="${plat%/*}" GOARCH="${plat#*/}" go build -trimpath \
-buildvcs=false -ldflags='-s -w -buildid=' -o "$probe" "$(echo "$spec" | jq -r .package)")
sum="$(sha_of "$probe")"
if [ -f "$(blob_of "$plat" "$asset")" ] && [ "$(cat "$probe.staged" 2>/dev/null)" = "$sum" ]; then
continue
fi
tree_assets+=(--asset "$asset")
built+=("$probe:$sum")
done
[ ${#tree_assets[@]} -gt 0 ] || continue
say "stage ${tree_assets[*]} for $plat (built from the working tree, changed)"
"$root/scripts/stage-assets.sh" --cache "$cache/assets" --blobs "$own_blobs" --platform "$plat" \
"${tree_assets[@]}" --print-pins >"$dev_root/tree-assets-pins.json" 2>"$dev_root/tree-assets-stage.log" \
|| die "staging ${tree_assets[*]} failed: $(tail -3 "$dev_root/tree-assets-stage.log")"
for b in "${built[@]}"; do
echo "${b##*:}" >"${b%:*}.staged"
done
if grep -q 'versions.json pins' "$dev_root/tree-assets-stage.log"; then
say "warn: $(grep 'versions.json pins' "$dev_root/tree-assets-stage.log" | cut -d' ' -f2 | tr '\n' ' ')built from the working tree differ from their versions.json pins (a release build refuses them)"
fi
done
say "assets staged ($(elapsed)s)"
hostkey="${hostplat/\//_}"
source_root="$root"
[ -z "$publish" ] || source_root="$publish/src"
stamp="github.com/johnandersen777/socialweb-computer-kcp/internal/buildstamp"
ldflags="-s -w -X $stamp.Kind=dev -X $stamp.SourceRoot=$source_root -X $stamp.Commit=$commit -X $stamp.Dirty=$dirty -X $stamp.ChangedPaths=$changed -X $stamp.BuiltUTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
for plat in "${platforms[@]}"; do
goos="${plat%/*}"
goarch="${plat#*/}"
key="${goos}_${goarch}"
out="$dev_root/$key"
mkdir -p "$out"
overlay="$dev_root/overlay-$key.json"
{
for asset in $(jq -r --arg p "$plat" '.platforms[$p].assets[]' "$versions"); do
jq -n --arg k "$root/internal/assets/blobs/$key/$asset.tar.zst" --arg v "$(blob_of "$plat" "$asset")" '{($k): $v}'
done
} | jq -s '{Replace: add}' >"$overlay"
say "go build $plat"
(cd "$root" && GOOS="$goos" GOARCH="$goarch" go build -tags release -trimpath -buildvcs=true -overlay "$overlay" \
-ldflags="$ldflags" -o "$out/socialweb-computer.new" ./cmd/socialweb-computer)
mv -f "$out/socialweb-computer.new" "$out/socialweb-computer"
if [ "$plat" = "$hostplat" ]; then
ln -sfn "$key/socialweb-computer" "$dev_root/socialweb-computer"
fi
say "built $out/socialweb-computer ($(elapsed)s)"
done
if [ -n "$publish" ]; then
mkdir -p "$publish/bin"
cp -f "$dev_root/$hostkey/socialweb-computer" "$publish/bin/socialweb-computer.new"
mv -f "$publish/bin/socialweb-computer.new" "$publish/bin/socialweb-computer"
chmod a+rx "$publish/bin" "$publish/bin/socialweb-computer"
mirror "$publish"
say "published $publish/bin/socialweb-computer, source from $publish/src"
fi
if [[ " ${platforms[*]} " == *" $hostplat "* ]]; then
"$dev_root/$hostkey/socialweb-computer" version | tee "$dev_root/$hostkey/version.txt"
fi
say "done in $(elapsed)s"