diff --git a/.claude/skills/quartz-integration/SKILL.md b/.claude/skills/quartz-integration/SKILL.md index f060a36c64..136ae31af4 100644 --- a/.claude/skills/quartz-integration/SKILL.md +++ b/.claude/skills/quartz-integration/SKILL.md @@ -7,7 +7,7 @@ description: Integration guide for using the Quartz Nostr KMP library in externa Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr KMP projects. -**Published artifact**: `com.vitorpamplona.quartz:quartz:1.15.0` (Maven Central) +**Published artifact**: `com.vitorpamplona.quartz:quartz:1.15.1` (Maven Central) **Targets**: JVM 21+, Android (minSdk 21+), iOS (XCFramework `quartz-kmpKit`) **License**: MIT @@ -19,7 +19,7 @@ Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr ```toml [versions] -quartz = "1.15.0" +quartz = "1.15.1" [libraries] quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" } @@ -41,7 +41,7 @@ kotlin { ```kotlin dependencies { - implementation("com.vitorpamplona.quartz:quartz:1.15.0") + implementation("com.vitorpamplona.quartz:quartz:1.15.1") } ``` diff --git a/.claude/skills/quartz-integration/references/gradle-setup.md b/.claude/skills/quartz-integration/references/gradle-setup.md index 742822d673..63c34ba1ee 100644 --- a/.claude/skills/quartz-integration/references/gradle-setup.md +++ b/.claude/skills/quartz-integration/references/gradle-setup.md @@ -3,7 +3,7 @@ ## Current version ``` -com.vitorpamplona.quartz:quartz:1.15.0 +com.vitorpamplona.quartz:quartz:1.15.1 ``` Check latest: https://central.sonatype.com/artifact/com.vitorpamplona.quartz/quartz @@ -16,7 +16,7 @@ Check latest: https://central.sonatype.com/artifact/com.vitorpamplona.quartz/qua ```toml [versions] -quartz = "1.15.0" +quartz = "1.15.1" [libraries] quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" } @@ -55,7 +55,7 @@ kotlin { ```kotlin // build.gradle.kts (app module) dependencies { - implementation("com.vitorpamplona.quartz:quartz:1.15.0") + implementation("com.vitorpamplona.quartz:quartz:1.15.1") } ``` @@ -70,7 +70,7 @@ plugins { } dependencies { - implementation("com.vitorpamplona.quartz:quartz:1.15.0") + implementation("com.vitorpamplona.quartz:quartz:1.15.1") // JNA needed for libsodium (NIP-44) on JVM implementation("net.java.dev.jna:jna:5.18.1") } diff --git a/BUILDING.md b/BUILDING.md index af87acd699..f354492819 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -533,7 +533,7 @@ reads an optional per-release changelog from ## Bootstrap runbook (one-time) -> **Status as of v1.15.0:** both Homebrew packages are now live upstream — the +> **Status as of v1.15.1:** both Homebrew packages are now live upstream — the > `amethyst-nostr` cask (`Homebrew/homebrew-cask`, at 1.14.0) and the `amy` > formula (`Homebrew/homebrew-core`) both answer 200 on `formulae.brew.sh`, so > `bump-homebrew.yml` finally has something to bump. **Winget is still not @@ -587,7 +587,7 @@ The token then lives only in that maintainer's shell: ```bash export HOMEBREW_GITHUB_API_TOKEN=ghp_... # classic PAT, `repo` scope -scripts/bump-homebrew-cask.sh v1.15.0 +scripts/bump-homebrew-cask.sh v1.15.1 ``` Create one at @@ -603,7 +603,7 @@ Same split, and it needs **no token at all**. `scripts/bump-winget.sh` drives runs fine from macOS or Linux: ```bash -scripts/bump-winget.sh v1.15.0 +scripts/bump-winget.sh v1.15.1 ``` CI (`bump-winget.yml`, `GITHUB_TOKEN` only) does the bookkeeping: downloads the diff --git a/README.md b/README.md index 11c018c51e..f6218a8750 100644 --- a/README.md +++ b/README.md @@ -328,16 +328,16 @@ repositories { Add the following line to your `commonMain` dependencies: ```gradle -implementation('com.vitorpamplona.quartz:quartz:1.15.0') +implementation('com.vitorpamplona.quartz:quartz:1.15.1') ``` Variations to each platform are also available: ```gradle -implementation('com.vitorpamplona.quartz:quartz-android:1.15.0') -implementation('com.vitorpamplona.quartz:quartz-jvm:1.15.0') -implementation('com.vitorpamplona.quartz:quartz-iosarm64:1.15.0') -implementation('com.vitorpamplona.quartz:quartz-iossimulatorarm64:1.15.0') +implementation('com.vitorpamplona.quartz:quartz-android:1.15.1') +implementation('com.vitorpamplona.quartz:quartz-jvm:1.15.1') +implementation('com.vitorpamplona.quartz:quartz-iosarm64:1.15.1') +implementation('com.vitorpamplona.quartz:quartz-iossimulatorarm64:1.15.1') ``` Check versions on [MavenCentral](https://central.sonatype.com/search?q=com.vitorpamplona.quartz) diff --git a/RELEASE_OPS.md b/RELEASE_OPS.md index 7ca75fc9f1..7b1fd6cf8b 100644 --- a/RELEASE_OPS.md +++ b/RELEASE_OPS.md @@ -240,9 +240,9 @@ readable by anyone with push access here), so a maintainer runs the last step: ```bash # after merging the sync PRs export HOMEBREW_GITHUB_API_TOKEN=ghp_... # classic PAT, `repo` scope -scripts/bump-homebrew-cask.sh v1.15.0 +scripts/bump-homebrew-cask.sh v1.15.1 -scripts/bump-winget.sh v1.15.0 # no token — uses your `gh` auth +scripts/bump-winget.sh v1.15.1 # no token — uses your `gh` auth ``` Both scripts re-verify the published artifact's sha256 before submitting, and diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index 4b9583cc82..77010ea9fe 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -1,3 +1,7 @@ +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -611,3 +615,75 @@ dependencies { implementation(libs.androidx.camera.view) implementation(libs.androidx.camera.extensions) } + +// AGP 9.4.0's PerModuleBundleTask refuses to write an AAB entry whose name contains a colon: +// +// Entry name contains invalid characters: root/META-INF/zoomable-root:zoomable.kotlin_module +// +// A .kotlin_module is named after the Gradle project path that produced it, colons included, and +// 14 of the 98 merged into this app carry one -- zoomable, Negentropy, vico, seven coil3 artifacts +// and four of ours. The entries are identical under 9.3.1, which writes them without complaint, so +// 9.4.0 added the rejection rather than the names. +// +// packaging.resources.excludes cannot remove them: with minification on, R8 emits the java +// resources and PerModuleBundleTask.addHybridFolder hands JarFlinger its own predicate, so those +// filters are never consulted. Nothing in the AAB reads a .kotlin_module either -- it exists for +// the Kotlin compiler to resolve top-level declarations across modules at COMPILE time. +// +// So drop them from R8's java-res jar in the moment before the bundle task opens it, then put the +// jar back exactly as R8 left it. The strip is doFirst on the CONSUMER rather than doLast on R8, so +// a build-cache hit on R8 cannot skip it; the restore is what keeps R8 up to date next build -- +// without it Gradle sees a modified output and re-runs R8 every time, which measured ~2 min a build +// here for no work. +fun stripColonNamedEntries(jar: File): Int { + val offenders = ZipFile(jar).use { zip -> zip.entries().toList().count { ':' in it.name } } + if (offenders == 0) return 0 + + val rewritten = File(jar.parentFile, "${jar.name}.stripped") + ZipFile(jar).use { zip -> + ZipOutputStream(rewritten.outputStream().buffered()).use { out -> + zip.entries().asSequence().filterNot { ':' in it.name }.forEach { entry -> + out.putNextEntry(ZipEntry(entry.name)) + zip.getInputStream(entry).use { it.copyTo(out) } + out.closeEntry() + } + } + } + rewritten.copyTo(jar, overwrite = true) + rewritten.delete() + return offenders +} + +androidComponents.onVariants { variant -> + val variantName = variant.name + val capitalized = variantName.replaceFirstChar { it.uppercase() } + tasks.matching { it.name == "build${capitalized}PreBundle" }.configureEach { + val javaResDir = layout.buildDirectory.dir("intermediates/merged_java_res/$variantName") + val backups = mutableMapOf() + + doFirst { + javaResDir.get().asFile + .walkTopDown() + .filter { it.isFile && it.extension == "jar" } + .forEach { jar -> + val backup = File(jar.parentFile, "${jar.name}.orig") + jar.copyTo(backup, overwrite = true) + val dropped = stripColonNamedEntries(jar) + if (dropped > 0) { + backups[jar] = backup + logger.lifecycle("Stripped $dropped colon-named entries from ${jar.name}") + } else { + backup.delete() + } + } + } + + doLast { + backups.forEach { (jar, backup) -> + backup.copyTo(jar, overwrite = true) + backup.delete() + } + backups.clear() + } + } +} diff --git a/docs/changelog/README.md b/docs/changelog/README.md index 8396285f58..61cc4fa258 100644 --- a/docs/changelog/README.md +++ b/docs/changelog/README.md @@ -2,6 +2,7 @@ Release notes for Amethyst, one file per version. Files are named with zero-padded version numbers so they sort correctly in any file browser. Use [`TEMPLATE.md`](TEMPLATE.md) as the starting point for the next release. +- [v1.15.1 — Release Build Fix](v1.15.01.md) - [v1.15.0 — Marmot Updates, a Advanced Search Box, and Books, DVM Updates](v1.15.00.md) - [v1.14.0 — Highlights, Relay Login, and a Much Faster Start](v1.14.00.md) - [v1.13.1 — Follow-up Fixes for Buzz, Concord and the Keyboard](v1.13.01.md) diff --git a/docs/changelog/v1.15.01.md b/docs/changelog/v1.15.01.md new file mode 100644 index 0000000000..904215956e --- /dev/null +++ b/docs/changelog/v1.15.01.md @@ -0,0 +1,30 @@ +# v1.15.1: Release Build Fix + +v1.15.0 was tagged but never shipped: its `Create Release Assets` run failed in +`deploy-android` before producing a single artifact. This release carries the +whole of [v1.15.0](v1.15.00.md) plus the fix for that failure — read those notes +for what actually changed in the app. + +## Build + +- Strips colon-named `.kotlin_module` entries out of R8's java-resource jar + before the app bundle is written. AGP 9.4.0 refuses them: + + ``` + Execution failed for task ':amethyst:buildFdroidReleasePreBundle' + > Entry name contains invalid characters: root/META-INF/zoomable-root:zoomable.kotlin_module + ``` + + A `.kotlin_module` is named after the Gradle project path that produced it, + colons included, and 14 of the 98 modules merged into the app carry one — + zoomable, Negentropy, vico, seven coil3 artifacts, and four of ours + (`Amethyst:quartz`, `:commons`, `:quic`, `:nestsClient`). The entries are + byte-identical under AGP 9.3.1, which writes the bundle without complaint, so + 9.4.0 added the rejection rather than the names. + + `packaging.resources.excludes` cannot remove them — with minification on, R8 + emits the java resources and `PerModuleBundleTask` hands `JarFlinger` its own + predicate, so those filters are never consulted. Nothing reads a + `.kotlin_module` out of an installed app either; the file exists for the + Kotlin compiler to resolve top-level declarations across modules at compile + time. diff --git a/geode/README.md b/geode/README.md index cd5031e6ea..1e8a8d8bbc 100644 --- a/geode/README.md +++ b/geode/README.md @@ -32,7 +32,7 @@ docker run -d --name geode -p 7447:7447 \ ``` with `in_memory = false` and `file = "/var/lib/geode/events.db"` in your -`geode.toml`. Pin a version (`:1.15.0`) instead of `:latest` for reproducible +`geode.toml`. Pin a version (`:1.15.1`) instead of `:latest` for reproducible deploys. To build the image yourself, from the repo root: ```bash @@ -47,7 +47,7 @@ it — a minimal JRE is bundled, so no system Java is required. It installs to `/opt/geode/` with the launcher at `/opt/geode/bin/geode`. ```bash -sudo dpkg -i geode-1.15.0-linux-x64.deb # or: sudo rpm -i geode-1.15.0-linux-x64.rpm +sudo dpkg -i geode-1.15.1-linux-x64.deb # or: sudo rpm -i geode-1.15.1-linux-x64.rpm ``` To run it as a managed service, wire up the shipped systemd unit @@ -69,7 +69,7 @@ formula: [`packaging/homebrew/geode-relay.rb`](packaging/homebrew/geode-relay.rb Download `geode---.tar.gz`, unpack, and run: ```bash -tar xzf geode-1.15.0-linux-x64.tar.gz +tar xzf geode-1.15.1-linux-x64.tar.gz ./geode/bin/geode --config geode/share/geode/config.example.toml ``` diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 904ecf238e..7def77e9aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ # Amethyst app version — single source of truth consumed by both Android (amethyst/) # and Desktop (desktopApp/). `appCode` is the Android versionCode: a monotonic # integer that must increment on every release, even when `app` is unchanged. -app = "1.15.0" -appCode = "458" +app = "1.15.1" +appCode = "459" accompanistAdaptive = "0.37.3" # Pinned: 0.3.0 turns CacheMap.entries/keys/values into DeprecationLevel.ERROR (and # throws UnsupportedOperationException at runtime), which breaks quartz's appleMain @@ -13,6 +13,9 @@ accompanistAdaptive = "0.37.3" cachemapVersion = "0.2.4" composeMultiplatform = "1.12.0" activityCompose = "1.13.0" +# 9.4.0's PerModuleBundleTask rejects an AAB entry whose name contains a colon, which every +# .kotlin_module named after a Gradle project path has. Worked around in amethyst/build.gradle.kts +# (stripColonNamedEntries) rather than by pinning to 9.3.1; drop that block if AGP fixes it. agp = "9.4.0" android-compileSdk = "37" android-minSdk = "26"