-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun-build-java-docs-with-act.sh
More file actions
executable file
·304 lines (288 loc) · 14.8 KB
/
Copy pathrun-build-java-docs-with-act.sh
File metadata and controls
executable file
·304 lines (288 loc) · 14.8 KB
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env bash
# Runs the "Build Java Docs (Local)" GitHub Actions workflow
# (.github/workflows/build-java-docs-local.yaml) locally via act
# (https://github.com/nektos/act).
#
# This drives the *local* workflow, not build-java-docs.yaml. The Drive
# workflow authenticates to Google Cloud with Workload Identity Federation,
# and WIF validates the OIDC token's issuer against GitHub's own token
# endpoint for a specific repo and run - act cannot mint a token GCP will
# accept, so that workflow can never get past its auth step locally no matter
# what secrets you supply. build-java-docs-local.yaml exists precisely to be
# runnable here: it reads its documentation.db from disk and writes its outputs
# back to disk, and is otherwise step-for-step identical to the Drive workflow
# (same build-jdk-json-docs -> flatten_templates -> sync_javadoc_json_to_db,
# same parity and decode verification).
#
# The Kotlin counterpart is run-build-kotlin-docs-with-act.sh; this mirrors it.
#
# Requires:
# - act (https://github.com/nektos/act#installation) on PATH
# - a running Docker daemon (act executes each step inside a container)
#
# CONTAINER MEMORY: documenting the whole JDK analyses ~4,800 source files in
# one pass, inside the job container, and that does NOT fit a small container
# VM. Measured on a colima VM with 7.7 GB, --modules unset:
#
# (no flag, 24g ceiling) exit 137 after 1m17 - kernel SIGKILL: the JVM
# grows until the VM is out
# --dokka-worker-heap 6g exit 137 after 2m26 - same
# --dokka-worker-heap 5g exit 137 after 2m33 - same
# --dokka-worker-heap 4g "Java heap space" - the JVM hit its own cap;
# 4g is too little for the job
#
# So no heap setting works at 7.7 GB: below ~5g the analysis genuinely needs
# more, and at ~5g and up the process no longer fits alongside the Gradle
# daemon. Raise the container VM instead. Measured working configuration:
#
# colima start --memory 16 (docker reports 15.6 GB)
# --dokka-worker-heap 8g full JDK, all 60 modules, ~4 minutes:
# 2m30 generating, 1m15 syncing
#
# Or use --modules to document a subset, which runs in about a minute on the
# smaller VM and is enough to exercise the whole pipeline.
#
# Those two failure modes are also how to tell whether --dokka-worker-heap took
# effect at all: "Java heap space" means the JVM hit the cap you set, exit 137
# means it was killed from outside. The build additionally logs a "Dokka worker
# heap" line whenever the flag is applied.
#
# Secrets: none are required. SLACK_WEBHOOK_URL is the only secret this
# workflow reads, and it is optional - the two "Notify Slack" steps print a
# skip notice and continue when it is unset. Export it if you want to see them
# actually fire ("build complete" additionally needs --live, since it is gated
# on dry_run being false). GitHub never exposes a stored secret's value through
# any API or CLI, so if you do want the real webhook you have to supply your
# own copy of the value.
#
# Inputs are host paths, bind-mounted into the job container at fixed
# locations and passed to the workflow as those in-container paths (a
# GitHub-hosted runner has no access to your disk, so the workflow only ever
# sees the mounted paths). Note this means the host paths must live somewhere
# your container runtime is allowed to share - under $HOME is safe for both
# colima and Docker Desktop; /tmp on macOS often is not.
#
# --db-path is mounted :ro. The workflow is written not to touch it, but a
# read-only mount is the part of that guarantee that does not depend on anyone
# remembering - which is why --output-db-path defaults into --output-dir
# rather than back over the input.
#
# Usage:
# ./run-build-java-docs-with-act.sh --db-path PATH [options] [-- <extra act args>]
#
# Typical first run - a two-module subset, nothing written anywhere but the
# output directory:
#
# ./run-build-java-docs-with-act.sh \
# --db-path ~/docs/documentation.db \
# --modules java.sql,java.transaction.xa --no-verify-parity
#
# Options:
# --db-path PATH Host path to the input documentation.db
# (required). Never written to: it is bind-mounted
# into the container read-only, so no step of the
# run can modify it even by mistake.
# --output-db-path PATH Host path to save the updated database to.
# Default: <output-dir>/documentation.db. Refused
# if it resolves to the same file as --db-path.
# --output-dir PATH Host directory for outputs - a run-numbered copy
# of the built database and of the original it was
# built from. Created if absent.
# (default: ./build-java-docs-output)
# --live dry_run=false: actually write --output-db-path
# when the run finishes. Also required for the
# "build complete" Slack notification to fire.
# Default is dry_run=true, which still writes the
# run-numbered copy into --output-dir.
# --java-version V JDK whose lib/src.zip is documented, and which
# the Gradle builds run on (default: 17). Must be
# 21 or lower: kdoc-to-json is pinned to Kotlin
# 1.9.24, whose compiler cannot run on a newer JDK.
# --modules LIST Comma-separated JPMS modules to document instead
# of all of them, e.g. "java.sql,java.xml". Turns a
# multi-minute run into seconds, which is the way
# to smoke-test a change. Requires
# --no-verify-parity, since a subset cannot match
# the full reference docs.
# --dokka-worker-heap SIZE Max heap for Dokka's worker process, e.g. 6g.
# Only needed if a run OOMs (see CONTAINER MEMORY).
# --no-verify-parity verify_parity=false: skip the comparison against
# the reference javadoc in SourceDocs/JavaDocs.
# --delete-missing delete_missing=true: also delete j/html/api/ rows
# with no JSON counterpart (class-use/,
# package-use, the tree pages). About half the
# rows. Default is to leave them alone.
#
# Every workflow input is passed explicitly on every run, including the ones
# whose YAML "default:" would cover them. act does not apply
# workflow_dispatch input defaults - an input you don't pass arrives empty -
# and for dry_run that inverts the intended behaviour: "${{ !inputs.dry_run }}"
# on an empty value is true, so the save step would run when it was meant not
# to. Passing all of them keeps a local run's semantics identical to a real
# dispatch.
#
# On Apple Silicon act warns about container architecture; append
# `-- --container-architecture linux/arm64` if you want to silence it (the
# default works).
set -euo pipefail
if ! command -v act >/dev/null 2>&1; then
echo "error: act is required - see https://github.com/nektos/act#installation" >&2
exit 1
fi
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKFLOW="$REPO_ROOT/.github/workflows/build-java-docs-local.yaml"
# Where the host paths below get bind-mounted inside the job container, and
# therefore what the workflow itself is told its inputs are.
CONTAINER_DB_PATH="/mnt/act-inputs/documentation.db"
CONTAINER_OUTPUT_DIR="/mnt/act-output"
# Filled in from --output-db-path once the arguments are parsed. It is reached
# through the output-dir mount rather than getting a mount of its own: it
# generally names a file that does not exist yet, and Docker creates a
# *directory* at the host path for a bind-mount source that is missing.
CONTAINER_OUTPUT_DB_PATH=""
DB_PATH=""
OUTPUT_DB_PATH=""
OUTPUT_DIR="$REPO_ROOT/build-java-docs-output"
# Mirrors build-java-docs-local.yaml's own default. Restated here because act
# does not apply workflow_dispatch defaults (see the note above); passing the
# input unconditionally is what keeps a local run equivalent to a real one.
JAVA_VERSION="17"
MODULES=""
DOKKA_WORKER_HEAP=""
VERIFY_PARITY="true"
DELETE_MISSING="false"
DRY_RUN="true"
EXTRA_ACT_ARGS=()
while [ $# -gt 0 ]; do
case "$1" in
--db-path) DB_PATH="$2"; shift 2 ;;
--output-db-path) OUTPUT_DB_PATH="$2"; shift 2 ;;
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
--live) DRY_RUN="false"; shift ;;
--java-version) JAVA_VERSION="$2"; shift 2 ;;
--modules) MODULES="$2"; shift 2 ;;
--dokka-worker-heap) DOKKA_WORKER_HEAP="$2"; shift 2 ;;
--no-verify-parity) VERIFY_PARITY="false"; shift ;;
--delete-missing) DELETE_MISSING="true"; shift ;;
--) shift; EXTRA_ACT_ARGS+=("$@"); break ;;
*) echo "error: unrecognized argument '$1'" >&2; exit 1 ;;
esac
done
if [ -z "$DB_PATH" ]; then
echo "error: --db-path is required (host path to the documentation.db to build against)" >&2
exit 1
fi
if [ ! -f "$DB_PATH" ]; then
echo "error: --db-path '$DB_PATH' does not exist or is not a file" >&2
exit 1
fi
DB_PATH="$(cd "$(dirname "$DB_PATH")" && pwd)/$(basename "$DB_PATH")"
if [ -n "$MODULES" ] && [ "$VERIFY_PARITY" = "true" ]; then
echo "error: --modules documents only part of the JDK, so the parity check against the" >&2
echo "error: full reference docs in SourceDocs/JavaDocs is guaranteed to fail. Pass" >&2
echo "error: --no-verify-parity alongside it." >&2
exit 1
fi
mkdir -p "$OUTPUT_DIR"
OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
# Map --output-db-path onto its in-container location. It has to sit under
# --output-dir, because that directory's mount is how anything the run writes
# reaches the host; a path anywhere else would be written inside the container
# and thrown away with it. Saying so is better than silently producing a run
# whose output cannot be found afterwards.
if [ -z "$OUTPUT_DB_PATH" ]; then
OUTPUT_DB_PATH="$OUTPUT_DIR/documentation.db"
CONTAINER_OUTPUT_DB_PATH="$CONTAINER_OUTPUT_DIR/documentation.db"
else
OUTPUT_DB_DIR="$(cd "$(dirname "$OUTPUT_DB_PATH")" 2>/dev/null && pwd)" || {
echo "error: --output-db-path '$OUTPUT_DB_PATH' is in a directory that does not exist" >&2
exit 1
}
OUTPUT_DB_PATH="$OUTPUT_DB_DIR/$(basename "$OUTPUT_DB_PATH")"
case "$OUTPUT_DB_PATH" in
"$OUTPUT_DIR"/*)
CONTAINER_OUTPUT_DB_PATH="$CONTAINER_OUTPUT_DIR/${OUTPUT_DB_PATH#"$OUTPUT_DIR"/}"
;;
*)
echo "error: --output-db-path '$OUTPUT_DB_PATH' is outside --output-dir '$OUTPUT_DIR'." >&2
echo "error: Only --output-dir is mounted for writing, so a file written anywhere else" >&2
echo "error: would stay inside the container. Put it under --output-dir, or point" >&2
echo "error: --output-dir at the directory you want to write into." >&2
exit 1
;;
esac
fi
if [ "$OUTPUT_DB_PATH" = "$DB_PATH" ]; then
echo "error: --output-db-path is the same file as --db-path. This workflow keeps its" >&2
echo "error: input intact so a bad run can be compared against it; pick another path." >&2
exit 1
fi
# One -v per input. Mounting the database individually (rather than its parent
# directory) keeps the container's view to exactly what the run needs, and lets
# --db-path and --output-dir live in unrelated places on the host.
#
# act takes --container-options as one string and splits it with shell-style
# quoting rules, so each mount spec is emitted double-quoted: an unquoted join
# would break the moment a host path contained a space.
CONTAINER_OPTIONS=""
add_mount() { CONTAINER_OPTIONS+=" -v \"$1:$2${3:-}\""; }
# :ro on the input. The workflow does not write db_path, and refuses an
# output_db_path that resolves to it - this is the third lock on the same door,
# and the only one that holds regardless of what the workflow does.
add_mount "$DB_PATH" "$CONTAINER_DB_PATH" ":ro"
add_mount "$OUTPUT_DIR" "$CONTAINER_OUTPUT_DIR"
# '$DB_PATH' is never modified, with or without --live, so neither branch
# warns about it - the difference is only whether --output-db-path is written.
if [ "$DRY_RUN" = "true" ]; then
echo "note: dry_run=true - '$OUTPUT_DB_PATH' will NOT be written. The run-numbered" >&2
echo "note: copy still lands in '$OUTPUT_DIR', so there is something to inspect." >&2
echo "note: The 'build started' Slack notification still fires (if SLACK_WEBHOOK_URL" >&2
echo "note: is set) but 'build complete' is gated on dry_run=false. Pass --live for it." >&2
else
echo "note: --live - the updated database will be saved to '$OUTPUT_DB_PATH'." >&2
if [ -e "$OUTPUT_DB_PATH" ]; then
echo "WARNING: '$OUTPUT_DB_PATH' already exists and will be overwritten." >&2
fi
fi
# The workflow reads SLACK_WEBHOOK_URL and tolerates it being unset, so pass
# it through when it's in the environment and stay silent when it isn't.
#
# SECRET_ARGS and EXTRA_ACT_ARGS are expanded below as
# ${arr[@]+"${arr[@]}"} rather than plain "${arr[@]}": macOS still ships bash
# 3.2, where `set -u` treats an empty array's "${arr[@]}" as an unbound
# variable and aborts. Both arrays are empty on a normal run.
SECRET_ARGS=()
if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
SECRETS_FILE="$(mktemp)"
trap 'rm -f "$SECRETS_FILE"' EXIT
printf 'SLACK_WEBHOOK_URL=%s\n' "$SLACK_WEBHOOK_URL" > "$SECRETS_FILE"
SECRET_ARGS=(--secret-file "$SECRETS_FILE")
fi
echo "== Running $WORKFLOW via act =="
echo " db_path $DB_PATH -> $CONTAINER_DB_PATH (read-only)"
echo " output_dir $OUTPUT_DIR -> $CONTAINER_OUTPUT_DIR"
echo " output_db_path $OUTPUT_DB_PATH -> $CONTAINER_OUTPUT_DB_PATH"
echo " java_version=$JAVA_VERSION modules=${MODULES:-(all)} dry_run=$DRY_RUN"
echo " verify_parity=$VERIFY_PARITY delete_missing=$DELETE_MISSING"
echo " dokka_worker_heap=${DOKKA_WORKER_HEAP:-(build default)}"
# --container-daemon-socket - : act otherwise bind-mounts the host's Docker
# socket into the job container so steps can run Docker themselves. Nothing in
# this workflow does, and the mount outright fails on runtimes whose socket
# isn't a plain bind-mountable file - under colima it aborts the run with
# "error while creating mount source path ...: operation not supported".
act workflow_dispatch \
-W "$WORKFLOW" \
-P ubuntu-latest=catthehacker/ubuntu:act-latest \
--container-daemon-socket - \
--container-options "$CONTAINER_OPTIONS" \
--input db_path="$CONTAINER_DB_PATH" \
--input output_db_path="$CONTAINER_OUTPUT_DB_PATH" \
--input output_dir="$CONTAINER_OUTPUT_DIR" \
--input java_version="$JAVA_VERSION" \
--input modules="$MODULES" \
--input dokka_worker_heap="$DOKKA_WORKER_HEAP" \
--input verify_parity="$VERIFY_PARITY" \
--input delete_missing="$DELETE_MISSING" \
--input dry_run="$DRY_RUN" \
${SECRET_ARGS[@]+"${SECRET_ARGS[@]}"} \
${EXTRA_ACT_ARGS[@]+"${EXTRA_ACT_ARGS[@]}"}