#!/usr/bin/env bash # The Android library as maven-publish writes it, before it is published anywhere: # # scripts/check-publication.sh # # is a Maven repository on disk (sink/build/staging-repo, which # `scripts/gradle.sh :sink:publishReleasePublicationToStagingRepository` writes). It must hold one # publication, science.transaction.wai:wai-sink:, and nothing else: its POM and Gradle # module metadata naming those coordinates, the media player library and its HTTP presentations # module as compile dependencies, and the AAR, whose manifest's package is # science.transaction.wai.sink and which holds libwai.so and libwai_sink_jni.so for arm64-v8a, # armeabi-v7a and x86_64 and the classes of wai.sink, with its sources jar beside it. Exit 0 when # all of that holds, 1 with one line for each thing that does not. set -euo pipefail [ $# -eq 2 ] || { echo "usage: scripts/check-publication.sh " >&2; exit 2; } exec python3 -I - "$1" "$2" <<'PY' import json, os, sys, zipfile import xml.etree.ElementTree as ET repo, version = sys.argv[1], sys.argv[2] GROUP, ARTIFACT, NAMESPACE = "science.transaction.wai", "wai-sink", "science.transaction.wai.sink" ABIS = ("arm64-v8a", "armeabi-v7a", "x86_64") LIBS = ("libwai.so", "libwai_sink_jni.so") CLASSES = ("wai/sink/WaiNative.class", "wai/sink/WaiSink.class", "wai/sink/WaiRelay.class", "wai/sink/WaiPlayerSession.class") DEPS = {("androidx.media3", "media3-exoplayer"), ("androidx.media3", "media3-exoplayer-hls")} bad = [] home = os.path.join(repo, *GROUP.split("."), ARTIFACT) here = os.path.join(home, version) base = os.path.join(here, f"{ARTIFACT}-{version}") # Nothing but the one publication: a group or a name of another spelling is a publication # nobody asked for, and the one asked for is then missing. outside = set() for d, _, files in os.walk(repo): for f in files: p = os.path.join(d, f) if not os.path.abspath(p).startswith(os.path.abspath(home) + os.sep): outside.add(os.path.relpath(d, repo)) for d in sorted(outside): bad.append(f"{d}/ holds a publication other than {GROUP}:{ARTIFACT}") for ext in (".aar", ".pom", ".module", "-sources.jar"): if not os.path.isfile(base + ext): bad.append(f"no {os.path.relpath(base + ext, repo)}") def text(e, tag, ns): x = e.find(f"m:{tag}", ns) return x.text.strip() if x is not None and x.text else None if os.path.isfile(base + ".pom"): root = ET.parse(base + ".pom").getroot() ns = {"m": root.tag[1:].split("}")[0]} if root.tag.startswith("{") else {"m": ""} got = (text(root, "groupId", ns), text(root, "artifactId", ns), text(root, "version", ns)) if got != (GROUP, ARTIFACT, version): bad.append(f"the POM names {':'.join(map(str, got))}, not {GROUP}:{ARTIFACT}:{version}") if text(root, "packaging", ns) != "aar": bad.append(f"the POM's packaging is {text(root, 'packaging', ns)}, not aar") deps = {(text(d, "groupId", ns), text(d, "artifactId", ns)): (text(d, "version", ns), text(d, "scope", ns)) for d in root.findall("m:dependencies/m:dependency", ns)} for g, a in sorted(DEPS): v = deps.get((g, a)) if v is None: bad.append(f"the POM does not depend on {g}:{a}") elif v[1] not in (None, "compile"): bad.append(f"the POM depends on {g}:{a} at scope {v[1]}, not compile") if os.path.isfile(base + ".module"): m = json.load(open(base + ".module")) c = m.get("component", {}) got = (c.get("group"), c.get("module"), c.get("version")) if got != (GROUP, ARTIFACT, version): bad.append(f"the module metadata names {':'.join(map(str, got))}, not {GROUP}:{ARTIFACT}:{version}") if os.path.isfile(base + ".aar"): with zipfile.ZipFile(base + ".aar") as z: names = set(z.namelist()) for abi in ABIS: for lib in LIBS: if f"jni/{abi}/{lib}" not in names: bad.append(f"the AAR has no jni/{abi}/{lib}") if "AndroidManifest.xml" not in names: bad.append("the AAR has no AndroidManifest.xml") else: pkg = ET.fromstring(z.read("AndroidManifest.xml")).get("package") if pkg != NAMESPACE: bad.append(f"the AAR's manifest is of package {pkg}, not {NAMESPACE}") if "classes.jar" not in names: bad.append("the AAR has no classes.jar") else: import io with zipfile.ZipFile(io.BytesIO(z.read("classes.jar"))) as j: held = set(j.namelist()) for cls in CLASSES: if cls not in held: bad.append(f"the AAR's classes.jar has no {cls}") if bad: for b in bad: print(f"check-publication: {b}", file=sys.stderr) sys.exit(1) size = os.path.getsize(base + ".aar") print(f"check-publication: {GROUP}:{ARTIFACT}:{version}: the POM, the module metadata, the sources and the AAR " f"({size // 1024} KiB, manifest package {NAMESPACE}, {len(ABIS)} ABIs, two libraries each)") PY