Document the APK filename/versionCode naming contract #14
@@ -272,6 +272,28 @@ collision; it prevents the collision from silently corrupting the repo.
|
|||||||
Skipped entirely for a plain `--index-only` invocation (below), since that
|
Skipped entirely for a plain `--index-only` invocation (below), since that
|
||||||
pass can never upload a `*.apk` in the first place.
|
pass can never upload a `*.apk` in the first place.
|
||||||
|
|
||||||
|
### APK filenames must carry the versionCode (fdroid-repo-ci#13)
|
||||||
|
|
||||||
|
That guard is correct -- a published versionCode's artifact has to stay
|
||||||
|
immutable -- but it has a consequence nothing else here states: **the
|
||||||
|
filename has to carry the versionCode**, or the guard permits exactly one
|
||||||
|
publish into a prefix, ever. `publish.sh` checks the destination *key*, not
|
||||||
|
the artifact's contents, so a build that always emits the same filename
|
||||||
|
collides with itself on its second run regardless of what actually changed
|
||||||
|
inside the APK.
|
||||||
|
|
||||||
|
Name the APK `<applicationId>_<versionCode>.apk` -- F-Droid's own
|
||||||
|
convention, and what `fdroid update` expects -- before handing it to
|
||||||
|
`fdroid-update.sh`/`publish.sh`. Concretely: Flutter's
|
||||||
|
`flutter build apk` always emits `app-<flavor>-release.apk`, independent of
|
||||||
|
`--build-number`, so a consumer that publishes that path unchanged gets a
|
||||||
|
successful first publish and then a permanent refusal on every publish
|
||||||
|
after it -- exactly what happened to weblib-viewer (weblib-viewer#29) before
|
||||||
|
it started renaming the artifact to `<applicationId>_<versionCode>.apk`
|
||||||
|
ahead of this step. The failure only shows up on the *second* publish, long
|
||||||
|
after the integration looks finished, and reads like a bucket-state problem
|
||||||
|
(see the error text below) rather than the naming contract it actually is.
|
||||||
|
|
||||||
### `--files-only` and `--index-only`
|
### `--files-only` and `--index-only`
|
||||||
|
|
||||||
The two passes can also be run as two separate invocations:
|
The two passes can also be run as two separate invocations:
|
||||||
|
|||||||
+12
@@ -267,6 +267,18 @@ if [ "$run_files" = true ]; then
|
|||||||
fi
|
fi
|
||||||
if head_out=$("${aws_base[@]}" s3api head-object --bucket "$bucket" --key "$key" 2>&1); then
|
if head_out=$("${aws_base[@]}" s3api head-object --bucket "$bucket" --key "$key" 2>&1); then
|
||||||
echo "publish.sh: refusing to publish -- $key already exists in s3://$bucket (would overwrite an existing APK, fdroid-repo-ci#6)" >&2
|
echo "publish.sh: refusing to publish -- $key already exists in s3://$bucket (would overwrite an existing APK, fdroid-repo-ci#6)" >&2
|
||||||
|
# Hint only, never a control-flow change (fdroid-repo-ci#13): a
|
||||||
|
# colliding filename with no digits at all cannot be carrying a
|
||||||
|
# versionCode, which is the single most common way to trip this
|
||||||
|
# guard on every publish after the first -- Flutter's
|
||||||
|
# app-<flavor>-release.apk being the concrete case that motivated
|
||||||
|
# this. A filename that does contain digits still gets refused with
|
||||||
|
# just the message above, exactly as before; it may yet be a genuine
|
||||||
|
# collision (fdroid-repo-ci#6) and this heuristic cannot tell.
|
||||||
|
base=$(basename "$key")
|
||||||
|
if [[ $base != *[0-9]* ]]; then
|
||||||
|
echo "publish.sh: $base does not appear to carry a versionCode -- F-Droid's convention is <applicationId>_<versionCode>.apk; a constant filename plus this guard means only the first publish into a prefix can ever succeed" >&2
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
elif echo "$head_out" | grep -qi '404\|not found\|NoSuchKey'; then
|
elif echo "$head_out" | grep -qi '404\|not found\|NoSuchKey'; then
|
||||||
: # does not exist yet -- fine, this is the common case
|
: # does not exist yet -- fine, this is the common case
|
||||||
|
|||||||
@@ -213,6 +213,45 @@ if [ "$have_python" = true ]; then
|
|||||||
else
|
else
|
||||||
pass "publish.sh never starts pass 1 after refusing a colliding APK"
|
pass "publish.sh never starts pass 1 after refusing a colliding APK"
|
||||||
fi
|
fi
|
||||||
|
# de.example.app_1.apk carries a digit (the versionCode "1"), so the
|
||||||
|
# no-digits hint below (fdroid-repo-ci#13) must not fire for it -- this is
|
||||||
|
# the negative control for that hint, proving it is not just always on.
|
||||||
|
if echo "$out" | grep -q "does not appear to carry a versionCode"; then
|
||||||
|
fail "publish.sh must not hint about a missing versionCode when the colliding filename has digits (it did!)"
|
||||||
|
else
|
||||||
|
pass "publish.sh does not add the no-versionCode hint when the colliding filename has digits"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--- a colliding filename with no digits gets the versionCode hint (fdroid-repo-ci#13) ---"
|
||||||
|
# The Flutter case the issue is about: a build that always emits the same
|
||||||
|
# filename regardless of version, e.g. app-dev-release.apk. The guard
|
||||||
|
# still refuses exactly as above; only the message grows a line pointing
|
||||||
|
# at the naming contract, since a bucket-state-shaped error is what took a
|
||||||
|
# full CI cycle to diagnose in weblib-viewer#29.
|
||||||
|
nodigits_dir="$work/nodigits"
|
||||||
|
mkdir -p "$nodigits_dir"
|
||||||
|
echo '{}' > "$nodigits_dir/index-v2.json"
|
||||||
|
echo 'unsigned bytes, same name every build' > "$nodigits_dir/app-dev-release.apk"
|
||||||
|
start_stub mybucket "myprefix/app-dev-release.apk"
|
||||||
|
out=$(bash "$here/publish.sh" "$nodigits_dir" mybucket "http://127.0.0.1:$stub_port" myprefix 2>&1) && rc=0 || rc=$?
|
||||||
|
kill "$stub_pid" 2>/dev/null || true
|
||||||
|
wait "$stub_pid" 2>/dev/null || true
|
||||||
|
if [ "${rc:-0}" -ne 0 ]; then
|
||||||
|
pass "publish.sh refuses to publish a colliding APK filename with no digits"
|
||||||
|
else
|
||||||
|
fail "publish.sh refuses to publish a colliding APK filename with no digits (got exit 0)"
|
||||||
|
fi
|
||||||
|
assert_contains "the refusal names the colliding key" "$out" "myprefix/app-dev-release.apk"
|
||||||
|
assert_contains "the refusal explains why" "$out" "already exists"
|
||||||
|
assert_contains "the refusal hints that the filename carries no versionCode" "$out" \
|
||||||
|
"does not appear to carry a versionCode"
|
||||||
|
assert_contains "the hint states F-Droid's naming convention" "$out" \
|
||||||
|
"<applicationId>_<versionCode>.apk"
|
||||||
|
if echo "$out" | grep -q "pass 1/2"; then
|
||||||
|
fail "publish.sh must not start pass 1 after refusing a colliding no-digits APK (it did!)"
|
||||||
|
else
|
||||||
|
pass "publish.sh never starts pass 1 after refusing a colliding no-digits APK"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "--- negative control: a non-colliding APK does not trip the same check ---"
|
echo "--- negative control: a non-colliding APK does not trip the same check ---"
|
||||||
start_stub mybucket # nothing pre-exists
|
start_stub mybucket # nothing pre-exists
|
||||||
@@ -244,6 +283,7 @@ if [ "$have_python" = true ]; then
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
skip "credential-check-is-real / APK-collision-refusal (no python3 available, even via with-nixpkgs.sh)"
|
skip "credential-check-is-real / APK-collision-refusal (no python3 available, even via with-nixpkgs.sh)"
|
||||||
|
skip "APK-collision refusal includes the no-versionCode hint for a digit-free filename"
|
||||||
skip "negative control: non-colliding APK does not trip the check"
|
skip "negative control: non-colliding APK does not trip the check"
|
||||||
skip "--index-only never runs the APK-collision check"
|
skip "--index-only never runs the APK-collision check"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user