From e48652e47599268e548c855800226bce8532293c Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Mon, 24 Aug 2026 14:59:44 +0100 Subject: [PATCH 1/9] feat(publishing): migrate to Maven Central under uk.co.appoly.droid JitPack rewrites the Gradle Module Metadata it serves, and that single behaviour caused every publishing problem this library has had. Two shapes were tried against real JitPack builds and both failed: a sources variant has its file entry stripped so Android Studio binds a 404, and no variant at all leaves it nothing to bind. 1.8.2 avoided both by publishing POM-only and broke consumer builds instead. Central serves exactly what is uploaded, so the workarounds are deleted rather than ported. Coordinates change: com.github.appoly.AppolyDroid-Toolbox:BaseRepo becomes uk.co.appoly.droid:baserepo, and AppolyDroid-Toolbox-bom becomes bom. Group now matches the package namespace, which has always been uk.co.appoly.droid. Breaking for consumers, hence 1.9.0 rather than a patch. - com.vanniktech.maven.publish 0.37.0 replaces 26 hand-written publishing blocks. Shared POM metadata and coordinates live in the root build; a module declares only its name and description. Central rejects an incomplete POM, and a missing developers block is a hard rejection, so the required fields are set centrally where a new module cannot omit them. - Deleted: the hand-rolled sources jar, the module-metadata juggling, and the BOM's -Pversion handling. All three existed only to survive JitPack. - The plugin produces sources and javadoc jars itself, so withSourcesJar and the AAR sources problem disappear rather than being worked around. - publishing-check, the release workflow and all 26 READMEs move to the new coordinates. The README's JitPack branch-snapshot section is replaced with local installs and Central snapshots, including a warning that a local install carries the same version string as the real release. - scripts/publish.sh reads credentials from the shared 1Password vault, so anyone with vault access can release. Deliberately not a personal shell function, which is single-user by construction. Verified locally with genuine signing from the vault: 26 modules published, every artifact carries a .asc, every code module carries sources and javadoc variants with correctly named file entries, and the BOM correctly carries neither since a java-platform has no code. Signature checks out against key 3F4AD175B7176969. 650 tests green; publishing-check passes on the new coordinates. Not yet possible: the actual upload. The uk.co.appoly namespace is unverified because the DNS TXT record is not live, and the portal token does not exist yet. Nothing here is merged until a real Central publish has been proven. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 51 ++++ AppSnackBar-UiState/README.md | 6 +- AppSnackBar-UiState/build.gradle.kts | 24 +- AppSnackBar/README.md | 2 +- AppSnackBar/build.gradle.kts | 24 +- BaseRepo-AppolyJson/README.md | 4 +- BaseRepo-AppolyJson/build.gradle.kts | 26 +- BaseRepo-Paging-AppolyJson/README.md | 10 +- BaseRepo-Paging-AppolyJson/build.gradle.kts | 26 +- BaseRepo-Paging/README.md | 8 +- BaseRepo-Paging/build.gradle.kts | 24 +- BaseRepo-S3Uploader-Multipart/README.md | 6 +- .../build.gradle.kts | 22 +- BaseRepo-S3Uploader/README.md | 6 +- BaseRepo-S3Uploader/build.gradle.kts | 24 +- BaseRepo/README.md | 2 +- BaseRepo/build.gradle.kts | 24 +- CLAUDE.md | 22 +- ComposeExtensions/README.md | 2 +- ComposeExtensions/build.gradle.kts | 24 +- ConnectivityMonitor/README.md | 2 +- ConnectivityMonitor/build.gradle.kts | 23 +- DateHelperUtil-Room/README.md | 4 +- DateHelperUtil-Room/build.gradle.kts | 24 +- DateHelperUtil-Serialization/README.md | 4 +- DateHelperUtil-Serialization/build.gradle.kts | 24 +- DateHelperUtil/README.md | 2 +- DateHelperUtil/build.gradle.kts | 24 +- LazyGridPagingExtensions/README.md | 4 +- LazyGridPagingExtensions/build.gradle.kts | 24 +- LazyListPagingExtensions/README.md | 4 +- LazyListPagingExtensions/build.gradle.kts | 24 +- MockInterceptor-AppolyJson/README.md | 2 +- MockInterceptor-AppolyJson/build.gradle.kts | 18 +- MockInterceptor-Retrofit/README.md | 2 +- MockInterceptor-Retrofit/build.gradle.kts | 18 +- MockInterceptor-Serialization/README.md | 2 +- .../build.gradle.kts | 18 +- MockInterceptor/README.md | 2 +- MockInterceptor/build.gradle.kts | 18 +- Nav3Navigation/README.md | 6 +- Nav3Navigation/build.gradle.kts | 24 +- PagingExtensions/README.md | 2 +- PagingExtensions/build.gradle.kts | 24 +- README.md | 265 +++++++++--------- S3Uploader-Multipart/README.md | 2 +- S3Uploader-Multipart/build.gradle.kts | 22 +- S3Uploader/README.md | 2 +- S3Uploader/build.gradle.kts | 22 +- SegmentedControl/README.md | 2 +- SegmentedControl/build.gradle.kts | 26 +- UiState/README.md | 2 +- UiState/build.gradle.kts | 24 +- bom/build.gradle.kts | 88 +++--- build.gradle.kts | 82 +++--- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- .../src/main/kotlin/UpdateReadmeVersions.kt | 6 +- gradle/libs.versions.toml | 2 + publishing-check/build.gradle.kts | 14 +- scripts/publish.sh | 43 +++ 60 files changed, 531 insertions(+), 709 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100755 scripts/publish.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1ee41905 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: Release + +# JitPack used to build from tags itself. That responsibility is now ours: a tag publishes, and if +# the gates fail no artifact exists at all — where JitPack would have built one regardless. +on: + push: + tags: [ '[0-9]+.[0-9]+.[0-9]+', '[0-9]+.[0-9]+.[0-9]+-*' ] + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + publish: + name: Publish to Maven Central + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + # FlexiLogger publishes Java 24 bytecode, which the toolbox consumes. + java-version: '24' + + - uses: gradle/actions/setup-gradle@v4 + + # Same gates as CI. A red build must not produce a release. + - name: Tests and coverage gate + run: ./gradlew test koverVerify + + - name: Verify consumer R8 keep rules + run: ./gradlew :app:verifyConsumerKeepRules + + - name: Verify published metadata resolves for an Android consumer + run: | + ./gradlew publishToMavenLocal + ./gradlew -p publishing-check verifyPublishedVariantResolution --refresh-dependencies + env: + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} + + - name: Publish + run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} diff --git a/AppSnackBar-UiState/README.md b/AppSnackBar-UiState/README.md index 52068427..5c7c84b2 100644 --- a/AppSnackBar-UiState/README.md +++ b/AppSnackBar-UiState/README.md @@ -13,9 +13,9 @@ Integration module that bridges the AppSnackBar and UiState modules, providing a ```gradle.kts // Requires both base modules -implementation("com.github.appoly.AppolyDroid-Toolbox:UiState:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar-UiState:1.8.3") +implementation("uk.co.appoly.droid:uistate:1.9.0") +implementation("uk.co.appoly.droid:appsnackbar:1.9.0") +implementation("uk.co.appoly.droid:appsnackbar-uistate:1.9.0") ``` ## Usage diff --git a/AppSnackBar-UiState/build.gradle.kts b/AppSnackBar-UiState/build.gradle.kts index 4730111a..342adef4 100644 --- a/AppSnackBar-UiState/build.gradle.kts +++ b/AppSnackBar-UiState/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.appsnackbar.uistate" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.APP_SNACK_BAR @@ -65,16 +60,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("AppSnackBar-UiState") + description.set("AppSnackBar integration with the UiState result type.") + } } diff --git a/AppSnackBar/README.md b/AppSnackBar/README.md index 312e1964..929aa99d 100644 --- a/AppSnackBar/README.md +++ b/AppSnackBar/README.md @@ -13,7 +13,7 @@ A customizable Jetpack Compose Snackbar implementation with support for differen ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar:1.8.3") +implementation("uk.co.appoly.droid:appsnackbar:1.9.0") ``` ## Usage diff --git a/AppSnackBar/build.gradle.kts b/AppSnackBar/build.gradle.kts index 0ce5bd3e..48615b38 100644 --- a/AppSnackBar/build.gradle.kts +++ b/AppSnackBar/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.appsnackbar" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.APP_SNACK_BAR @@ -67,16 +62,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("AppSnackBar") + description.set("An enhanced Compose Snackbar.") + } } diff --git a/BaseRepo-AppolyJson/README.md b/BaseRepo-AppolyJson/README.md index 85210c26..ebacda98 100644 --- a/BaseRepo-AppolyJson/README.md +++ b/BaseRepo-AppolyJson/README.md @@ -14,8 +14,8 @@ Appoly's JSON format. ```gradle.kts // Requires the base BaseRepo module -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-AppolyJson:1.8.3") +implementation("uk.co.appoly.droid:baserepo:1.9.0") +implementation("uk.co.appoly.droid:baserepo-appolyjson:1.9.0") ``` ## API Response Structure diff --git a/BaseRepo-AppolyJson/build.gradle.kts b/BaseRepo-AppolyJson/build.gradle.kts index 8715f0cf..ee478230 100644 --- a/BaseRepo-AppolyJson/build.gradle.kts +++ b/BaseRepo-AppolyJson/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.baserepo.appolyjson" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.BASE_REPO @@ -56,16 +51,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } -} \ No newline at end of file +mavenPublishing { + pom { + name.set("BaseRepo-AppolyJson") + description.set("BaseRepo support for Appoly's standard JSON response envelope.") + } +} diff --git a/BaseRepo-Paging-AppolyJson/README.md b/BaseRepo-Paging-AppolyJson/README.md index df01b197..2b2f8a73 100644 --- a/BaseRepo-Paging-AppolyJson/README.md +++ b/BaseRepo-Paging-AppolyJson/README.md @@ -15,13 +15,13 @@ follow Appoly's paging format. ```gradle.kts // Requires the base modules -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging-AppolyJson:1.8.3") +implementation("uk.co.appoly.droid:baserepo:1.9.0") +implementation("uk.co.appoly.droid:baserepo-paging:1.9.0") +implementation("uk.co.appoly.droid:baserepo-paging-appolyjson:1.9.0") // For Compose UI integration -implementation("com.github.appoly.AppolyDroid-Toolbox:LazyListPagingExtensions:1.8.3") // For LazyColumn -implementation("com.github.appoly.AppolyDroid-Toolbox:LazyGridPagingExtensions:1.8.3") // For LazyGrid +implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.0") // For LazyColumn +implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.0") // For LazyGrid ``` ## API Response Format diff --git a/BaseRepo-Paging-AppolyJson/build.gradle.kts b/BaseRepo-Paging-AppolyJson/build.gradle.kts index 90db169e..ccd98f3f 100644 --- a/BaseRepo-Paging-AppolyJson/build.gradle.kts +++ b/BaseRepo-Paging-AppolyJson/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.baserepo.paging.appolyjson" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.BASE_REPO @@ -57,16 +52,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } -} \ No newline at end of file +mavenPublishing { + pom { + name.set("BaseRepo-Paging-AppolyJson") + description.set("Jetpack Paging 3 integration for BaseRepo using Appoly's JSON envelope.") + } +} diff --git a/BaseRepo-Paging/README.md b/BaseRepo-Paging/README.md index d4deb75b..b8e37e43 100644 --- a/BaseRepo-Paging/README.md +++ b/BaseRepo-Paging/README.md @@ -17,12 +17,12 @@ extended for specific JSON formats. ```gradle.kts // Requires the base BaseRepo module -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging:1.8.3") +implementation("uk.co.appoly.droid:baserepo:1.9.0") +implementation("uk.co.appoly.droid:baserepo-paging:1.9.0") // For Compose UI integration -implementation("com.github.appoly.AppolyDroid-Toolbox:LazyListPagingExtensions:1.8.3") // For LazyColumn -implementation("com.github.appoly.AppolyDroid-Toolbox:LazyGridPagingExtensions:1.8.3") // For LazyGrid +implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.0") // For LazyColumn +implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.0") // For LazyGrid ``` ## Extensions diff --git a/BaseRepo-Paging/build.gradle.kts b/BaseRepo-Paging/build.gradle.kts index 8e5515c7..ef57169e 100644 --- a/BaseRepo-Paging/build.gradle.kts +++ b/BaseRepo-Paging/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.baserepo.paging" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.BASE_REPO @@ -61,16 +56,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("BaseRepo-Paging") + description.set("Jetpack Paging 3 integration for BaseRepo.") + } } diff --git a/BaseRepo-S3Uploader-Multipart/README.md b/BaseRepo-S3Uploader-Multipart/README.md index d90606c6..0227437d 100644 --- a/BaseRepo-S3Uploader-Multipart/README.md +++ b/BaseRepo-S3Uploader-Multipart/README.md @@ -15,9 +15,9 @@ Extension module that bridges BaseRepo and S3Uploader-Multipart, enabling pausab ```gradle.kts // Requires the base modules -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader-Multipart:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader-Multipart:1.8.3") +implementation("uk.co.appoly.droid:baserepo:1.9.0") +implementation("uk.co.appoly.droid:s3uploader-multipart:1.9.0") +implementation("uk.co.appoly.droid:baserepo-s3uploader-multipart:1.9.0") ``` ## Usage diff --git a/BaseRepo-S3Uploader-Multipart/build.gradle.kts b/BaseRepo-S3Uploader-Multipart/build.gradle.kts index 76e53581..96c81bc8 100644 --- a/BaseRepo-S3Uploader-Multipart/build.gradle.kts +++ b/BaseRepo-S3Uploader-Multipart/build.gradle.kts @@ -3,10 +3,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.baserepo.s3.multipart" @@ -14,10 +13,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.BASE_REPO @@ -56,16 +51,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } +mavenPublishing { + pom { + name.set("BaseRepo-S3Uploader-Multipart") + description.set("BaseRepo integration for pausable, resumable S3 multipart uploads.") } } diff --git a/BaseRepo-S3Uploader/README.md b/BaseRepo-S3Uploader/README.md index 1647b6e2..cad0a2d4 100644 --- a/BaseRepo-S3Uploader/README.md +++ b/BaseRepo-S3Uploader/README.md @@ -18,9 +18,9 @@ An extension module that bridges BaseRepo and S3Uploader, enabling seamless file ```gradle.kts // Requires both the base modules -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader:1.8.3") +implementation("uk.co.appoly.droid:baserepo:1.9.0") +implementation("uk.co.appoly.droid:s3uploader:1.9.0") +implementation("uk.co.appoly.droid:baserepo-s3uploader:1.9.0") ``` ## How it Works diff --git a/BaseRepo-S3Uploader/build.gradle.kts b/BaseRepo-S3Uploader/build.gradle.kts index 4ffc5a58..7210bc69 100644 --- a/BaseRepo-S3Uploader/build.gradle.kts +++ b/BaseRepo-S3Uploader/build.gradle.kts @@ -3,10 +3,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.baserepo.s3" @@ -14,10 +13,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.BASE_REPO @@ -56,16 +51,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("BaseRepo-S3Uploader") + description.set("S3 upload integration for BaseRepo.") + } } diff --git a/BaseRepo/README.md b/BaseRepo/README.md index 37d8c742..8719440a 100644 --- a/BaseRepo/README.md +++ b/BaseRepo/README.md @@ -14,7 +14,7 @@ Foundation module for implementing the repository pattern with standardized API ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:1.8.3") +implementation("uk.co.appoly.droid:baserepo:1.9.0") ``` ## Extensions diff --git a/BaseRepo/build.gradle.kts b/BaseRepo/build.gradle.kts index 34b99ee6..73517de4 100644 --- a/BaseRepo/build.gradle.kts +++ b/BaseRepo/build.gradle.kts @@ -6,10 +6,9 @@ plugins { alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.baserepo" @@ -17,10 +16,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.BASE_REPO @@ -81,16 +76,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("BaseRepo") + description.set("Repository pattern for Retrofit and Sandwich, with APIResult and APIFlowState result types.") + } } diff --git a/CLAUDE.md b/CLAUDE.md index 42bf04d2..751dddb0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,10 +114,24 @@ flow.collect { state -> ## Publishing -Published to JitPack via `com.github.appoly`. Each module has: -- Maven publication configuration -- Sources JAR -- Consumer ProGuard rules (`consumer-rules.pro`) +Published to **Maven Central** under `uk.co.appoly.droid`, with lowercase artifact IDs +(`uk.co.appoly.droid:s3uploader-multipart`). Versions up to 1.8.3 were on JitPack under +`com.github.appoly.AppolyDroid-Toolbox` with PascalCase names; 1.9.0 is the first Central release. + +- `com.vanniktech.maven.publish` handles signing, sources and javadoc. Shared POM metadata and + coordinates live in the root `build.gradle.kts`; a module declares only its name and description. +- Central rejects an incomplete POM — a missing `developers` block is a hard rejection. +- Credentials come from 1Password (*Appoly Shared → Appoly Maven Central Signing*), never from + `~/.gradle/gradle.properties`. Gradle reads them only under the `ORG_GRADLE_PROJECT_` prefix with + exact camelCase. +- `./scripts/publish.sh --local` publishes signed artifacts to `~/.m2`; without `--local` it + releases to Central. Pushing a version tag runs the same thing in CI. +- Releases are **immutable** — a version cannot be re-uploaded. Iterate with local installs or + snapshots, not by retagging. +- `./gradlew -p publishing-check verifyPublishedVariantResolution` resolves the published modules as + an Android consumer and fails on platform-variant duplicates. It reads mavenLocal only, so run + `publishToMavenLocal` first. +- Each module also ships consumer ProGuard rules (`consumer-rules.pro`). ## Knowledge Graph (graphify) diff --git a/ComposeExtensions/README.md b/ComposeExtensions/README.md index c167be86..c3b70afd 100644 --- a/ComposeExtensions/README.md +++ b/ComposeExtensions/README.md @@ -13,7 +13,7 @@ Compose utilities for insets/IME padding, padding arithmetic, serialization-safe ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:ComposeExtensions:1.8.3") +implementation("uk.co.appoly.droid:composeextensions:1.9.0") ``` ## Usage diff --git a/ComposeExtensions/build.gradle.kts b/ComposeExtensions/build.gradle.kts index d5c0eec6..1ef860c1 100644 --- a/ComposeExtensions/build.gradle.kts +++ b/ComposeExtensions/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.compose.extensions" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.COMPOSE_EXTENSIONS @@ -70,16 +65,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("ComposeExtensions") + description.set("Compose utilities: insets and IME padding, padding arithmetic, serialization-safe MutableState, and clipboard helpers.") + } } diff --git a/ConnectivityMonitor/README.md b/ConnectivityMonitor/README.md index 6f5c4cee..f1010396 100644 --- a/ConnectivityMonitor/README.md +++ b/ConnectivityMonitor/README.md @@ -9,7 +9,7 @@ Add the following dependency to your project's `build.gradle` file: ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:connectivitymonitor:1.8.3") +implementation("uk.co.appoly.droid:connectivitymonitor:1.9.0") ``` ## Usage diff --git a/ConnectivityMonitor/build.gradle.kts b/ConnectivityMonitor/build.gradle.kts index bcc5d74c..7a331e37 100644 --- a/ConnectivityMonitor/build.gradle.kts +++ b/ConnectivityMonitor/build.gradle.kts @@ -3,10 +3,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.connectivitymonitor" @@ -14,10 +13,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.CONNECTIVITY_MONITOR testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -57,17 +52,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } +mavenPublishing { + pom { + name.set("ConnectivityMonitor") + description.set("Network connectivity state monitoring for Android.") } } diff --git a/DateHelperUtil-Room/README.md b/DateHelperUtil-Room/README.md index a5bd2617..ad9a6880 100644 --- a/DateHelperUtil-Room/README.md +++ b/DateHelperUtil-Room/README.md @@ -16,8 +16,8 @@ Extension module for DateHelperUtil that provides Room database integration for ```gradle.kts // Requires base DateHelperUtil module -implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Room:1.8.3") +implementation("uk.co.appoly.droid:datehelperutil:1.9.0") +implementation("uk.co.appoly.droid:datehelperutil-room:1.9.0") // Required Room dependencies implementation("androidx.room:room-runtime:2.8.4") diff --git a/DateHelperUtil-Room/build.gradle.kts b/DateHelperUtil-Room/build.gradle.kts index f04629e0..4fbb7ecc 100644 --- a/DateHelperUtil-Room/build.gradle.kts +++ b/DateHelperUtil-Room/build.gradle.kts @@ -4,10 +4,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.datehelper.room" @@ -15,10 +14,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.DATE_HELPER @@ -61,16 +56,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("DateHelperUtil-Room") + description.set("Room type converters for DateHelperUtil types.") + } } diff --git a/DateHelperUtil-Serialization/README.md b/DateHelperUtil-Serialization/README.md index f6220002..0a5de23a 100644 --- a/DateHelperUtil-Serialization/README.md +++ b/DateHelperUtil-Serialization/README.md @@ -16,8 +16,8 @@ Extension module for DateHelperUtil that provides kotlinx.serialization integrat ```gradle.kts // Requires base DateHelperUtil module -implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Serialization:1.8.3") +implementation("uk.co.appoly.droid:datehelperutil:1.9.0") +implementation("uk.co.appoly.droid:datehelperutil-serialization:1.9.0") // Required kotlinx.serialization dependencies implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.11.0") diff --git a/DateHelperUtil-Serialization/build.gradle.kts b/DateHelperUtil-Serialization/build.gradle.kts index cc73a818..a12c104c 100644 --- a/DateHelperUtil-Serialization/build.gradle.kts +++ b/DateHelperUtil-Serialization/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.datehelper.serialization" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.DATE_HELPER @@ -59,16 +54,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("DateHelperUtil-Serialization") + description.set("kotlinx-serialization serializers for DateHelperUtil types.") + } } diff --git a/DateHelperUtil/README.md b/DateHelperUtil/README.md index 21e430bd..fce722aa 100644 --- a/DateHelperUtil/README.md +++ b/DateHelperUtil/README.md @@ -14,7 +14,7 @@ A utility module for standardized date and time operations in Android applicatio ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil:1.8.3") +implementation("uk.co.appoly.droid:datehelperutil:1.9.0") ``` ## 1.4.1 patch note diff --git a/DateHelperUtil/build.gradle.kts b/DateHelperUtil/build.gradle.kts index 77f26cde..27280269 100644 --- a/DateHelperUtil/build.gradle.kts +++ b/DateHelperUtil/build.gradle.kts @@ -4,10 +4,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.datehelper" @@ -15,10 +14,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.DATE_HELPER @@ -56,16 +51,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("DateHelperUtil") + description.set("Date and time utilities for Android.") + } } diff --git a/LazyGridPagingExtensions/README.md b/LazyGridPagingExtensions/README.md index b89a70d7..3b7156de 100644 --- a/LazyGridPagingExtensions/README.md +++ b/LazyGridPagingExtensions/README.md @@ -15,8 +15,8 @@ Extension functions for integrating Jetpack Paging 3 with Compose LazyVerticalGr ```gradle.kts // Requires the base PagingExtensions module -implementation("com.github.appoly.AppolyDroid-Toolbox:PagingExtensions:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:LazyGridPagingExtensions:1.8.3") +implementation("uk.co.appoly.droid:pagingextensions:1.9.0") +implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.0") // Make sure to include Jetpack Paging Compose implementation("androidx.paging:paging-compose:3.5.1") diff --git a/LazyGridPagingExtensions/build.gradle.kts b/LazyGridPagingExtensions/build.gradle.kts index 87d817f0..09f9c4e0 100644 --- a/LazyGridPagingExtensions/build.gradle.kts +++ b/LazyGridPagingExtensions/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.lazygridpagingextensions" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.LAZY_PAGING @@ -73,16 +68,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("LazyGridPagingExtensions") + description.set("Paging helpers for Compose lazy grids.") + } } diff --git a/LazyListPagingExtensions/README.md b/LazyListPagingExtensions/README.md index 693d3ca6..26076ff1 100644 --- a/LazyListPagingExtensions/README.md +++ b/LazyListPagingExtensions/README.md @@ -15,8 +15,8 @@ Extension functions for easy integration of Jetpack Paging 3 with Compose LazyCo ```gradle.kts // Requires the base PagingExtensions module -implementation("com.github.appoly.AppolyDroid-Toolbox:PagingExtensions:1.8.3") -implementation("com.github.appoly.AppolyDroid-Toolbox:LazyListPagingExtensions:1.8.3") +implementation("uk.co.appoly.droid:pagingextensions:1.9.0") +implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.0") // Make sure to include Jetpack Paging Compose implementation("androidx.paging:paging-compose:3.5.1") diff --git a/LazyListPagingExtensions/build.gradle.kts b/LazyListPagingExtensions/build.gradle.kts index 04c79ce3..5d6620d4 100644 --- a/LazyListPagingExtensions/build.gradle.kts +++ b/LazyListPagingExtensions/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.lazylistpagingextensions" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.LAZY_PAGING @@ -73,16 +68,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("LazyListPagingExtensions") + description.set("Paging helpers for Compose LazyColumn and LazyRow.") + } } diff --git a/MockInterceptor-AppolyJson/README.md b/MockInterceptor-AppolyJson/README.md index 0561ede6..e85ada7c 100644 --- a/MockInterceptor-AppolyJson/README.md +++ b/MockInterceptor-AppolyJson/README.md @@ -14,7 +14,7 @@ Extension for [MockInterceptor-Serialization](../MockInterceptor-Serialization/) ```gradle.kts // MockInterceptor and MockInterceptor-Serialization are included transitively -implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-AppolyJson:1.8.3") +implementation("uk.co.appoly.droid:mockinterceptor-appolyjson:1.9.0") ``` ## Usage diff --git a/MockInterceptor-AppolyJson/build.gradle.kts b/MockInterceptor-AppolyJson/build.gradle.kts index 9e5bed73..b553e30e 100644 --- a/MockInterceptor-AppolyJson/build.gradle.kts +++ b/MockInterceptor-AppolyJson/build.gradle.kts @@ -4,10 +4,9 @@ plugins { id("java-library") alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" java { sourceCompatibility = JavaVersion.VERSION_11 @@ -25,14 +24,9 @@ dependencies { testImplementation(libs.junit) } - -publishing { - publications { - create("release") { - from(components["java"]) - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("MockInterceptor-AppolyJson") + description.set("MockInterceptor helpers for Appoly's standard JSON envelope.") + } } diff --git a/MockInterceptor-Retrofit/README.md b/MockInterceptor-Retrofit/README.md index 47fc98df..5572e722 100644 --- a/MockInterceptor-Retrofit/README.md +++ b/MockInterceptor-Retrofit/README.md @@ -13,7 +13,7 @@ Extension for [MockInterceptor](../MockInterceptor/) that reads Retrofit HTTP an ```gradle.kts // MockInterceptor is included transitively -implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Retrofit:1.8.3") +implementation("uk.co.appoly.droid:mockinterceptor-retrofit:1.9.0") ``` > **Note:** Retrofit is a `compileOnly` dependency — your project must already depend on Retrofit. diff --git a/MockInterceptor-Retrofit/build.gradle.kts b/MockInterceptor-Retrofit/build.gradle.kts index b7b78c4b..cfd4cb92 100644 --- a/MockInterceptor-Retrofit/build.gradle.kts +++ b/MockInterceptor-Retrofit/build.gradle.kts @@ -3,10 +3,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("java-library") alias(libs.plugins.kotlin.jvm) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" java { sourceCompatibility = JavaVersion.VERSION_11 @@ -27,14 +26,9 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.retrofit) } - -publishing { - publications { - create("release") { - from(components["java"]) - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("MockInterceptor-Retrofit") + description.set("Registers MockInterceptor routes automatically by reflecting over Retrofit annotations.") + } } diff --git a/MockInterceptor-Serialization/README.md b/MockInterceptor-Serialization/README.md index f3985f1e..59c2bb93 100644 --- a/MockInterceptor-Serialization/README.md +++ b/MockInterceptor-Serialization/README.md @@ -12,7 +12,7 @@ Extension for [MockInterceptor](../MockInterceptor/) that adds type-safe JSON re ```gradle.kts // MockInterceptor is included transitively -implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Serialization:1.8.3") +implementation("uk.co.appoly.droid:mockinterceptor-serialization:1.9.0") ``` ## Usage diff --git a/MockInterceptor-Serialization/build.gradle.kts b/MockInterceptor-Serialization/build.gradle.kts index 78fa2027..e8bec990 100644 --- a/MockInterceptor-Serialization/build.gradle.kts +++ b/MockInterceptor-Serialization/build.gradle.kts @@ -4,10 +4,9 @@ plugins { id("java-library") alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" java { sourceCompatibility = JavaVersion.VERSION_11 @@ -26,14 +25,9 @@ dependencies { testImplementation(libs.junit) } - -publishing { - publications { - create("release") { - from(components["java"]) - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("MockInterceptor-Serialization") + description.set("Type-safe JSON bodies and pagination helpers for MockInterceptor, via kotlinx-serialization.") + } } diff --git a/MockInterceptor/README.md b/MockInterceptor/README.md index a87be6a5..d37dbaed 100644 --- a/MockInterceptor/README.md +++ b/MockInterceptor/README.md @@ -16,7 +16,7 @@ An OkHttp interceptor with a route-matching DSL for mocking API responses during ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor:1.8.3") +implementation("uk.co.appoly.droid:mockinterceptor:1.9.0") ``` ## Usage diff --git a/MockInterceptor/build.gradle.kts b/MockInterceptor/build.gradle.kts index a0a69c5b..6b8cbc2b 100644 --- a/MockInterceptor/build.gradle.kts +++ b/MockInterceptor/build.gradle.kts @@ -3,10 +3,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("java-library") alias(libs.plugins.kotlin.jvm) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" java { sourceCompatibility = JavaVersion.VERSION_11 @@ -25,14 +24,9 @@ dependencies { testImplementation(libs.junit) } - -publishing { - publications { - create("release") { - from(components["java"]) - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("MockInterceptor") + description.set("An OkHttp interceptor that serves mocked responses from a route-matching DSL, with no mock server required.") + } } diff --git a/Nav3Navigation/README.md b/Nav3Navigation/README.md index c0c33393..098a3cbe 100644 --- a/Nav3Navigation/README.md +++ b/Nav3Navigation/README.md @@ -32,14 +32,14 @@ without giving up the fused-screen / ambient-navigator convenience that Voyager ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:Nav3Navigation:1.8.3") +implementation("uk.co.appoly.droid:nav3navigation:1.9.0") ``` Or via the AppolyDroid BOM (version managed by the platform): ```gradle.kts -implementation(platform("com.github.appoly.AppolyDroid-Toolbox:AppolyDroid-Toolbox-bom:1.8.3")) -implementation("com.github.appoly.AppolyDroid-Toolbox:Nav3Navigation") +implementation(platform("uk.co.appoly.droid:bom:1.9.0")) +implementation("uk.co.appoly.droid:nav3navigation") ``` **Requirements** diff --git a/Nav3Navigation/build.gradle.kts b/Nav3Navigation/build.gradle.kts index 8c73ca2d..e2847fb2 100644 --- a/Nav3Navigation/build.gradle.kts +++ b/Nav3Navigation/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.nav3" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.NAV3_NAVIGATION @@ -89,16 +84,9 @@ dependencies { androidTestImplementation(libs.androidx.activity.compose) debugImplementation(libs.androidx.ui.test.manifest) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("Nav3Navigation") + description.set("Voyager-style screens on androidx Navigation 3, with an ambient navigator and screen host.") + } } diff --git a/PagingExtensions/README.md b/PagingExtensions/README.md index d39b742e..4c8f4053 100644 --- a/PagingExtensions/README.md +++ b/PagingExtensions/README.md @@ -12,7 +12,7 @@ Core utilities and extensions for Jetpack Paging 3 integration, providing the fo ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:PagingExtensions:1.8.3") +implementation("uk.co.appoly.droid:pagingextensions:1.9.0") ``` ## Usage diff --git a/PagingExtensions/build.gradle.kts b/PagingExtensions/build.gradle.kts index d402f17b..4f5d5674 100644 --- a/PagingExtensions/build.gradle.kts +++ b/PagingExtensions/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.pagingextensions" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.LAZY_PAGING @@ -72,16 +67,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("PagingExtensions") + description.set("Core Jetpack Paging utilities and LoadState extensions for Compose.") + } } diff --git a/README.md b/README.md index e2273a65..d4d120a9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Appoly's Android development toolbox - a collection of utilities and components to accelerate Android app development -[![Release](https://jitpack.io/v/appoly/AppolyDroid-Toolbox.svg)](https://jitpack.io/#appoly/AppolyDroid-Toolbox) +[![Maven Central](https://img.shields.io/maven-central/v/uk.co.appoly.droid/baserepo)](https://central.sonatype.com/namespace/uk.co.appoly.droid) ## Overview @@ -25,53 +25,50 @@ Add the JitPack repository to your project build file: ```gradle.kts dependencyResolutionManagement { repositories { - ... - maven { - url = uri("https://jitpack.io") - } + mavenCentral() } } ``` -or in your `settings.gradle` with: +The toolbox is published to Maven Central, so no custom repository is required — if `mavenCentral()` +is already in your repositories list, there is nothing to add. -```gradle -allprojects { - repositories { - ... - maven { url "https://jitpack.io" } - } -} -``` +> **Migrating from 1.8.3 or earlier?** Those versions were published on JitPack under +> `com.github.appoly.AppolyDroid-Toolbox` with PascalCase artifact names. From 1.9.0 the coordinates +> are `uk.co.appoly.droid` with lowercase names — for example +> `com.github.appoly.AppolyDroid-Toolbox:BaseRepo` becomes `uk.co.appoly.droid:baserepo`, and +> `AppolyDroid-Toolbox-bom` becomes `bom`. The `jitpack.io` repository entry can be removed unless +> something else needs it. -### Testing an unreleased branch +### Testing an unreleased change -JitPack can build any branch, which is useful for trying a fix before it is tagged. Two things -catch people out: +Maven Central publishes only what is released, so there is no equivalent of JitPack's +build-any-branch behaviour. Two options replace it. -**Use `-SNAPSHOT`, not the build id JitPack reports.** JitPack shows a build id like -`bugfix~My-Fix-24f40efabe-1`, but the POM it serves declares its own version as -`bugfix~My-Fix-SNAPSHOT`. Requesting the build id fails with a confusing error, because the build -genuinely succeeded and the artifacts genuinely exist: +**Install locally.** From a checkout of the branch you want to test: -``` -inconsistent module metadata found. Descriptor: ...:BaseRepo:bugfix~My-Fix-SNAPSHOT -Errors: bad version: expected='bugfix~My-Fix-24f40efabe-1' found='bugfix~My-Fix-SNAPSHOT' +```bash +./scripts/publish.sh --local ``` -Slashes in the branch name become `~`: +That publishes every module to `~/.m2` — signed, and needing no upload credentials, only access to +the shared 1Password vault. Add `mavenLocal()` to the consuming project's repositories, ahead of +`mavenCentral()`. -```kotlin -implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:bugfix~My-Fix-SNAPSHOT") -``` +> Take `mavenLocal()` out again before committing, and before drawing any conclusion about a +> released version. A locally published build carries the same version string as the real one, so +> leaving it in means resolving your own artifacts while believing you are testing the release. -**Pass `--refresh-dependencies`.** `-SNAPSHOT` is a changing module, so Gradle caches it for 24 -hours by default — without it you can silently keep testing a stale copy. Alternatively: +**Publish a snapshot.** Snapshot versions go to Central's snapshot repository rather than the main +one, and need it adding explicitly: ```kotlin -configurations.all { resolutionStrategy.cacheChangingModulesFor(0, "seconds") } +maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") } ``` +Snapshots are changing modules, so pass `--refresh-dependencies` or Gradle will cache one for 24 +hours and you may silently keep testing a stale copy. + ### Using the BOM (Bill of Materials) For easier dependency management, you can use the AppolyDroid BOM which provides version alignment for all modules and their shared dependencies: @@ -82,36 +79,36 @@ In your `libs.versions.toml` file: ```toml [versions] -appolydroidToolbox = "1.8.3" # Replace with the latest version +appolydroidToolbox = "1.9.0" # Replace with the latest version [libraries] -appolydroid-toolbox-bom = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "AppolyDroid-Toolbox-bom", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-bom = { group = "uk.co.appoly.droid", name = "bom", version.ref = "appolydroidToolbox" } # AppolyDroid modules (versions managed by BOM) -appolydroid-toolbox-baseRepo = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo" } -appolydroid-toolbox-baseRepo-appolyJson = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-AppolyJson" } -appolydroid-toolbox-baseRepo-s3 = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-S3Uploader" } -appolydroid-toolbox-baseRepo-s3-multipart = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-S3Uploader-Multipart" } -appolydroid-toolbox-baseRepo-paging = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-Paging" } -appolydroid-toolbox-baseRepo-paging-AppolyJson = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-Paging-AppolyJson" } -appolydroid-toolbox-uiState = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "UiState" } -appolydroid-toolbox-appSnackBar = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "AppSnackBar" } -appolydroid-toolbox-appSnackBar-uiState = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "AppSnackBar-UiState" } -appolydroid-toolbox-dateHelper = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "DateHelperUtil" } -appolydroid-toolbox-dateHelper-room = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "DateHelperUtil-Room" } -appolydroid-toolbox-dateHelper-serialization = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "DateHelperUtil-Serialization" } -appolydroid-toolbox-compose-extensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "ComposeExtensions" } -appolydroid-toolbox-segmentedControl = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "SegmentedControl" } -appolydroid-toolbox-lazyListPagingExtensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "LazyListPagingExtensions" } -appolydroid-toolbox-lazyGridPagingExtensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "LazyGridPagingExtensions" } -appolydroid-toolbox-pagingExtensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "PagingExtensions" } -appolydroid-toolbox-s3Uploader = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "S3Uploader" } -appolydroid-toolbox-s3Uploader-multipart = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "S3Uploader-Multipart" } -appolydroid-toolbox-connectivityMonitor = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "ConnectivityMonitor" } -appolydroid-toolbox-nav3Navigation = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "Nav3Navigation" } -appolydroid-toolbox-mockInterceptor = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor" } -appolydroid-toolbox-mockInterceptor-serialization = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor-Serialization" } -appolydroid-toolbox-mockInterceptor-appolyjson = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor-AppolyJson" } -appolydroid-toolbox-mockInterceptor-retrofit = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor-Retrofit" } +appolydroid-toolbox-baseRepo = { group = "uk.co.appoly.droid", name = "baserepo" } +appolydroid-toolbox-baseRepo-appolyJson = { group = "uk.co.appoly.droid", name = "baserepo-appolyjson" } +appolydroid-toolbox-baseRepo-s3 = { group = "uk.co.appoly.droid", name = "baserepo-s3uploader" } +appolydroid-toolbox-baseRepo-s3-multipart = { group = "uk.co.appoly.droid", name = "baserepo-s3uploader-multipart" } +appolydroid-toolbox-baseRepo-paging = { group = "uk.co.appoly.droid", name = "baserepo-paging" } +appolydroid-toolbox-baseRepo-paging-AppolyJson = { group = "uk.co.appoly.droid", name = "baserepo-paging-appolyjson" } +appolydroid-toolbox-uiState = { group = "uk.co.appoly.droid", name = "uistate" } +appolydroid-toolbox-appSnackBar = { group = "uk.co.appoly.droid", name = "appsnackbar" } +appolydroid-toolbox-appSnackBar-uiState = { group = "uk.co.appoly.droid", name = "appsnackbar-uistate" } +appolydroid-toolbox-dateHelper = { group = "uk.co.appoly.droid", name = "datehelperutil" } +appolydroid-toolbox-dateHelper-room = { group = "uk.co.appoly.droid", name = "datehelperutil-room" } +appolydroid-toolbox-dateHelper-serialization = { group = "uk.co.appoly.droid", name = "datehelperutil-serialization" } +appolydroid-toolbox-compose-extensions = { group = "uk.co.appoly.droid", name = "composeextensions" } +appolydroid-toolbox-segmentedControl = { group = "uk.co.appoly.droid", name = "segmentedcontrol" } +appolydroid-toolbox-lazyListPagingExtensions = { group = "uk.co.appoly.droid", name = "lazylistpagingextensions" } +appolydroid-toolbox-lazyGridPagingExtensions = { group = "uk.co.appoly.droid", name = "lazygridpagingextensions" } +appolydroid-toolbox-pagingExtensions = { group = "uk.co.appoly.droid", name = "pagingextensions" } +appolydroid-toolbox-s3Uploader = { group = "uk.co.appoly.droid", name = "s3uploader" } +appolydroid-toolbox-s3Uploader-multipart = { group = "uk.co.appoly.droid", name = "s3uploader-multipart" } +appolydroid-toolbox-connectivityMonitor = { group = "uk.co.appoly.droid", name = "connectivitymonitor" } +appolydroid-toolbox-nav3Navigation = { group = "uk.co.appoly.droid", name = "nav3navigation" } +appolydroid-toolbox-mockInterceptor = { group = "uk.co.appoly.droid", name = "mockinterceptor" } +appolydroid-toolbox-mockInterceptor-serialization = { group = "uk.co.appoly.droid", name = "mockinterceptor-serialization" } +appolydroid-toolbox-mockInterceptor-appolyjson = { group = "uk.co.appoly.droid", name = "mockinterceptor-appolyjson" } +appolydroid-toolbox-mockInterceptor-retrofit = { group = "uk.co.appoly.droid", name = "mockinterceptor-retrofit" } ``` Then in your module's `build.gradle.kts`: @@ -157,34 +154,34 @@ In your module's `build.gradle.kts`: ```gradle.kts dependencies { // Import the BOM - implementation(platform("com.github.appoly.AppolyDroid-Toolbox:AppolyDroid-Toolbox-bom:1.8.3")) + implementation(platform("uk.co.appoly.droid:bom:1.9.0")) // Now you can use AppolyDroid modules without specifying versions - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-AppolyJson") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader-Multipart") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging-AppolyJson") - implementation("com.github.appoly.AppolyDroid-Toolbox:UiState") - implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar") - implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar-UiState") - implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil") - implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Room") - implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Serialization") - implementation("com.github.appoly.AppolyDroid-Toolbox:ComposeExtensions") - implementation("com.github.appoly.AppolyDroid-Toolbox:SegmentedControl") - implementation("com.github.appoly.AppolyDroid-Toolbox:LazyListPagingExtensions") - implementation("com.github.appoly.AppolyDroid-Toolbox:LazyGridPagingExtensions") - implementation("com.github.appoly.AppolyDroid-Toolbox:PagingExtensions") - implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader") - implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader-Multipart") - implementation("com.github.appoly.AppolyDroid-Toolbox:ConnectivityMonitor") - implementation("com.github.appoly.AppolyDroid-Toolbox:Nav3Navigation") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Serialization") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-AppolyJson") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Retrofit") + implementation("uk.co.appoly.droid:baserepo") + implementation("uk.co.appoly.droid:baserepo-appolyjson") + implementation("uk.co.appoly.droid:baserepo-s3uploader") + implementation("uk.co.appoly.droid:baserepo-s3uploader-multipart") + implementation("uk.co.appoly.droid:baserepo-paging") + implementation("uk.co.appoly.droid:baserepo-paging-appolyjson") + implementation("uk.co.appoly.droid:uistate") + implementation("uk.co.appoly.droid:appsnackbar") + implementation("uk.co.appoly.droid:appsnackbar-uistate") + implementation("uk.co.appoly.droid:datehelperutil") + implementation("uk.co.appoly.droid:datehelperutil-room") + implementation("uk.co.appoly.droid:datehelperutil-serialization") + implementation("uk.co.appoly.droid:composeextensions") + implementation("uk.co.appoly.droid:segmentedcontrol") + implementation("uk.co.appoly.droid:lazylistpagingextensions") + implementation("uk.co.appoly.droid:lazygridpagingextensions") + implementation("uk.co.appoly.droid:pagingextensions") + implementation("uk.co.appoly.droid:s3uploader") + implementation("uk.co.appoly.droid:s3uploader-multipart") + implementation("uk.co.appoly.droid:connectivitymonitor") + implementation("uk.co.appoly.droid:nav3navigation") + implementation("uk.co.appoly.droid:mockinterceptor") + implementation("uk.co.appoly.droid:mockinterceptor-serialization") + implementation("uk.co.appoly.droid:mockinterceptor-appolyjson") + implementation("uk.co.appoly.droid:mockinterceptor-retrofit") } ``` @@ -194,34 +191,34 @@ In your `libs.versions.toml` file: ```toml [versions] -appolydroidToolbox = "1.8.3" # Replace with the latest version +appolydroidToolbox = "1.9.0" # Replace with the latest version [libraries] #AppolyDroid-Toolbox -appolydroid-toolbox-baseRepo = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-baseRepo-appolyJson = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-AppolyJson", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-baseRepo-s3 = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-S3Uploader", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-baseRepo-s3-multipart = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-S3Uploader-Multipart", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-baseRepo-paging = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-Paging", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-baseRepo-paging-AppolyJson = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "BaseRepo-Paging-AppolyJson", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-uiState = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "UiState", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-appSnackBar = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "AppSnackBar", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-appSnackBar-uiState = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "AppSnackBar-UiState", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-dateHelper = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "DateHelperUtil", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-dateHelper-room = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "DateHelperUtil-Room", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-dateHelper-serialization = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "DateHelperUtil-Serialization", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-compose-extensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "ComposeExtensions", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-segmentedControl = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "SegmentedControl", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-lazyListPagingExtensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "LazyListPagingExtensions", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-lazyGridPagingExtensions = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "LazyGridPagingExtensions", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-s3Uploader = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "S3Uploader", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-s3Uploader-multipart = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "S3Uploader-Multipart", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-connectivityMonitor = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "ConnectivityMonitor", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-nav3Navigation = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "Nav3Navigation", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-mockInterceptor = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-mockInterceptor-serialization = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor-Serialization", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-mockInterceptor-appolyjson = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor-AppolyJson", version.ref = "appolydroidToolbox" } -appolydroid-toolbox-mockInterceptor-retrofit = { group = "com.github.appoly.AppolyDroid-Toolbox", name = "MockInterceptor-Retrofit", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-baseRepo = { group = "uk.co.appoly.droid", name = "baserepo", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-baseRepo-appolyJson = { group = "uk.co.appoly.droid", name = "baserepo-appolyjson", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-baseRepo-s3 = { group = "uk.co.appoly.droid", name = "baserepo-s3uploader", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-baseRepo-s3-multipart = { group = "uk.co.appoly.droid", name = "baserepo-s3uploader-multipart", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-baseRepo-paging = { group = "uk.co.appoly.droid", name = "baserepo-paging", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-baseRepo-paging-AppolyJson = { group = "uk.co.appoly.droid", name = "baserepo-paging-appolyjson", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-uiState = { group = "uk.co.appoly.droid", name = "uistate", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-appSnackBar = { group = "uk.co.appoly.droid", name = "appsnackbar", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-appSnackBar-uiState = { group = "uk.co.appoly.droid", name = "appsnackbar-uistate", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-dateHelper = { group = "uk.co.appoly.droid", name = "datehelperutil", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-dateHelper-room = { group = "uk.co.appoly.droid", name = "datehelperutil-room", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-dateHelper-serialization = { group = "uk.co.appoly.droid", name = "datehelperutil-serialization", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-compose-extensions = { group = "uk.co.appoly.droid", name = "composeextensions", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-segmentedControl = { group = "uk.co.appoly.droid", name = "segmentedcontrol", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-lazyListPagingExtensions = { group = "uk.co.appoly.droid", name = "lazylistpagingextensions", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-lazyGridPagingExtensions = { group = "uk.co.appoly.droid", name = "lazygridpagingextensions", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-s3Uploader = { group = "uk.co.appoly.droid", name = "s3uploader", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-s3Uploader-multipart = { group = "uk.co.appoly.droid", name = "s3uploader-multipart", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-connectivityMonitor = { group = "uk.co.appoly.droid", name = "connectivitymonitor", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-nav3Navigation = { group = "uk.co.appoly.droid", name = "nav3navigation", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-mockInterceptor = { group = "uk.co.appoly.droid", name = "mockinterceptor", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-mockInterceptor-serialization = { group = "uk.co.appoly.droid", name = "mockinterceptor-serialization", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-mockInterceptor-appolyjson = { group = "uk.co.appoly.droid", name = "mockinterceptor-appolyjson", version.ref = "appolydroidToolbox" } +appolydroid-toolbox-mockInterceptor-retrofit = { group = "uk.co.appoly.droid", name = "mockinterceptor-retrofit", version.ref = "appolydroidToolbox" } ``` Then in your module's `build.gradle.kts`: @@ -262,32 +259,32 @@ In your module's `build.gradle.kts`: ```gradle.kts dependencies { - val appolydroidToolbox = "1.8.3" // Replace with the latest version + val appolydroidToolbox = "1.9.0" // Replace with the latest version // Add only the modules you need - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-AppolyJson:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader-Multipart:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging-AppolyJson:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:UiState:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar-UiState:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Room:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Serialization:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:ComposeExtensions:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:SegmentedControl:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:LazyListPagingExtensions:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:LazyGridPagingExtensions:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader-Multipart:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:ConnectivityMonitor:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:Nav3Navigation:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Serialization:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-AppolyJson:$appolydroidToolbox") - implementation("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Retrofit:$appolydroidToolbox") + implementation("uk.co.appoly.droid:baserepo:$appolydroidToolbox") + implementation("uk.co.appoly.droid:baserepo-appolyjson:$appolydroidToolbox") + implementation("uk.co.appoly.droid:baserepo-s3uploader:$appolydroidToolbox") + implementation("uk.co.appoly.droid:baserepo-s3uploader-multipart:$appolydroidToolbox") + implementation("uk.co.appoly.droid:baserepo-paging:$appolydroidToolbox") + implementation("uk.co.appoly.droid:baserepo-paging-appolyjson:$appolydroidToolbox") + implementation("uk.co.appoly.droid:uistate:$appolydroidToolbox") + implementation("uk.co.appoly.droid:appsnackbar:$appolydroidToolbox") + implementation("uk.co.appoly.droid:appsnackbar-uistate:$appolydroidToolbox") + implementation("uk.co.appoly.droid:datehelperutil:$appolydroidToolbox") + implementation("uk.co.appoly.droid:datehelperutil-room:$appolydroidToolbox") + implementation("uk.co.appoly.droid:datehelperutil-serialization:$appolydroidToolbox") + implementation("uk.co.appoly.droid:composeextensions:$appolydroidToolbox") + implementation("uk.co.appoly.droid:segmentedcontrol:$appolydroidToolbox") + implementation("uk.co.appoly.droid:lazylistpagingextensions:$appolydroidToolbox") + implementation("uk.co.appoly.droid:lazygridpagingextensions:$appolydroidToolbox") + implementation("uk.co.appoly.droid:s3uploader:$appolydroidToolbox") + implementation("uk.co.appoly.droid:s3uploader-multipart:$appolydroidToolbox") + implementation("uk.co.appoly.droid:connectivitymonitor:$appolydroidToolbox") + implementation("uk.co.appoly.droid:nav3navigation:$appolydroidToolbox") + implementation("uk.co.appoly.droid:mockinterceptor:$appolydroidToolbox") + implementation("uk.co.appoly.droid:mockinterceptor-serialization:$appolydroidToolbox") + implementation("uk.co.appoly.droid:mockinterceptor-appolyjson:$appolydroidToolbox") + implementation("uk.co.appoly.droid:mockinterceptor-retrofit:$appolydroidToolbox") } ``` diff --git a/S3Uploader-Multipart/README.md b/S3Uploader-Multipart/README.md index 7b0bfc8e..59d62c78 100644 --- a/S3Uploader-Multipart/README.md +++ b/S3Uploader-Multipart/README.md @@ -16,7 +16,7 @@ Advanced S3 upload module with pause, resume, and recovery support using AWS S3 ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader-Multipart:1.8.3") +implementation("uk.co.appoly.droid:s3uploader-multipart:1.9.0") ``` This module depends on `S3Uploader` and includes it transitively. diff --git a/S3Uploader-Multipart/build.gradle.kts b/S3Uploader-Multipart/build.gradle.kts index 8cc03656..19be9240 100644 --- a/S3Uploader-Multipart/build.gradle.kts +++ b/S3Uploader-Multipart/build.gradle.kts @@ -6,10 +6,9 @@ plugins { alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) alias(libs.plugins.room) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.s3upload.multipart" @@ -17,10 +16,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.S3_UPLOADER testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -91,16 +86,9 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.room.testing) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } +mavenPublishing { + pom { + name.set("S3Uploader-Multipart") + description.set("Pausable and resumable Android uploads built on the AWS S3 multipart upload API.") } } diff --git a/S3Uploader/README.md b/S3Uploader/README.md index 675e891f..ef007763 100644 --- a/S3Uploader/README.md +++ b/S3Uploader/README.md @@ -16,7 +16,7 @@ Standalone module for Amazon S3 file uploading with progress tracking and error ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:S3Uploader:1.8.3") +implementation("uk.co.appoly.droid:s3uploader:1.9.0") ``` ## Usage diff --git a/S3Uploader/build.gradle.kts b/S3Uploader/build.gradle.kts index 84807b2a..9b631a0c 100644 --- a/S3Uploader/build.gradle.kts +++ b/S3Uploader/build.gradle.kts @@ -5,21 +5,15 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlinxSerialization) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" - configure { namespace = "uk.co.appoly.droid.s3upload" compileSdk { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.S3_UPLOADER testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -78,15 +72,9 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) } -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } +mavenPublishing { + pom { + name.set("S3Uploader") + description.set("Direct S3 uploads from Android with pre-signed URLs and progress tracking.") } } diff --git a/SegmentedControl/README.md b/SegmentedControl/README.md index e5f341b0..f81df40a 100644 --- a/SegmentedControl/README.md +++ b/SegmentedControl/README.md @@ -17,7 +17,7 @@ A highly customizable iOS-style segmented control for Jetpack Compose with smoot ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:SegmentedControl:1.8.3") +implementation("uk.co.appoly.droid:segmentedcontrol:1.9.0") ``` ## Usage diff --git a/SegmentedControl/build.gradle.kts b/SegmentedControl/build.gradle.kts index a8be9545..04136d06 100644 --- a/SegmentedControl/build.gradle.kts +++ b/SegmentedControl/build.gradle.kts @@ -5,10 +5,9 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlinKSP) alias(libs.plugins.kotlin.compose) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.segmentedcontrol" @@ -16,10 +15,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.SEGMENTED_CONTROL @@ -67,16 +62,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } -} \ No newline at end of file +mavenPublishing { + pom { + name.set("SegmentedControl") + description.set("An iOS-style segmented control for Compose, with an animated thumb and drag-to-switch gestures.") + } +} diff --git a/UiState/README.md b/UiState/README.md index 9ea1b00c..77a5319b 100644 --- a/UiState/README.md +++ b/UiState/README.md @@ -13,7 +13,7 @@ A standardized UI state management library for Android applications, providing c ## Installation ```gradle.kts -implementation("com.github.appoly.AppolyDroid-Toolbox:UiState:1.8.3") +implementation("uk.co.appoly.droid:uistate:1.9.0") ``` ## Usage diff --git a/UiState/build.gradle.kts b/UiState/build.gradle.kts index e5fe305f..eefef153 100644 --- a/UiState/build.gradle.kts +++ b/UiState/build.gradle.kts @@ -3,10 +3,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" configure { namespace = "uk.co.appoly.droid.uistate" @@ -14,10 +13,6 @@ configure { version = release(BuildConfig.Sdk.COMPILE) } - publishing { - singleVariant("release") - } - defaultConfig { minSdk = BuildConfig.MinSdk.UI_STATE @@ -52,16 +47,9 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } - -publishing { - publications { - create("release") { - afterEvaluate { - from(components["release"]) - } - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = project.name - version = BuildConfig.TOOLBOX_VERSION - } - } +mavenPublishing { + pom { + name.set("UiState") + description.set("A sealed UI state type covering idle, loading, success and error.") + } } diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts index 55e1d5f9..adf21390 100644 --- a/bom/build.gradle.kts +++ b/bom/build.gradle.kts @@ -1,84 +1,70 @@ plugins { `java-platform` - `maven-publish` + alias(libs.plugins.vanniktech.publish) } -group = "com.github.appoly.AppolyDroid-Toolbox" - -// The version this BOM publishes at, and the version its constraints demand — they must be the -// same, or the BOM asks for modules that were never published. -// -// JitPack builds a branch with `-Pversion=-SNAPSHOT` and rewrites each artifact's own -// version to match, but it does NOT rewrite the versions inside this BOM's . -// Hardcoding TOOLBOX_VERSION here therefore published a snapshot BOM demanding release modules -// (e.g. a `...-SNAPSHOT` BOM requiring BaseRepo:1.8.2), which made every branch and PR snapshot -// unresolvable through the BOM — precisely the workflow needed to verify a publishing change. +// Constraints pin every toolbox module to a single version, so a consumer importing this platform +// gets a coherent set without naming versions itself. // -// Honour -Pversion when present; fall back to TOOLBOX_VERSION for local and tagged builds. -val publishedVersion: String = - (project.findProperty("version") as? String) - ?.takeIf { it.isNotBlank() && it != "unspecified" } - ?: BuildConfig.TOOLBOX_VERSION +// The version is simply TOOLBOX_VERSION. The previous `-Pversion` juggling existed only because +// JitPack rewrote each artifact's version to the branch or tag being built while leaving these +// constraints untouched, which made snapshot BOMs demand release modules. Maven Central publishes +// what it is given, so the workaround is gone. javaPlatform { allowDependencies() } dependencies { - // Define constraints for all AppolyDroid modules constraints { // Core modules - api("com.github.appoly.AppolyDroid-Toolbox:BaseRepo:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging:${publishedVersion}") + api("uk.co.appoly.droid:baserepo:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:baserepo-s3uploader:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:baserepo-paging:${BuildConfig.TOOLBOX_VERSION}") // Core Appoly specific modules - api("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-AppolyJson:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-Paging-AppolyJson:${publishedVersion}") + api("uk.co.appoly.droid:baserepo-appolyjson:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:baserepo-paging-appolyjson:${BuildConfig.TOOLBOX_VERSION}") // UI State modules - api("com.github.appoly.AppolyDroid-Toolbox:UiState:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:AppSnackBar-UiState:${publishedVersion}") + api("uk.co.appoly.droid:uistate:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:appsnackbar:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:appsnackbar-uistate:${BuildConfig.TOOLBOX_VERSION}") // Date/Time modules - api("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Room:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:DateHelperUtil-Serialization:${publishedVersion}") + api("uk.co.appoly.droid:datehelperutil:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:datehelperutil-room:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:datehelperutil-serialization:${BuildConfig.TOOLBOX_VERSION}") // Compose & Pagination modules - api("com.github.appoly.AppolyDroid-Toolbox:ComposeExtensions:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:SegmentedControl:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:LazyListPagingExtensions:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:LazyGridPagingExtensions:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:PagingExtensions:${publishedVersion}") + api("uk.co.appoly.droid:composeextensions:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:segmentedcontrol:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:lazylistpagingextensions:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:lazygridpagingextensions:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:pagingextensions:${BuildConfig.TOOLBOX_VERSION}") // S3 & Utility modules - api("com.github.appoly.AppolyDroid-Toolbox:S3Uploader:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:S3Uploader-Multipart:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:BaseRepo-S3Uploader-Multipart:${publishedVersion}") + api("uk.co.appoly.droid:s3uploader:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:s3uploader-multipart:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:baserepo-s3uploader-multipart:${BuildConfig.TOOLBOX_VERSION}") // Connectivity Monitor - api("com.github.appoly.AppolyDroid-Toolbox:ConnectivityMonitor:${publishedVersion}") + api("uk.co.appoly.droid:connectivitymonitor:${BuildConfig.TOOLBOX_VERSION}") // Navigation modules - api("com.github.appoly.AppolyDroid-Toolbox:Nav3Navigation:${publishedVersion}") + api("uk.co.appoly.droid:nav3navigation:${BuildConfig.TOOLBOX_VERSION}") // Mock Interceptor modules - api("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Serialization:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-AppolyJson:${publishedVersion}") - api("com.github.appoly.AppolyDroid-Toolbox:MockInterceptor-Retrofit:${publishedVersion}") + api("uk.co.appoly.droid:mockinterceptor:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:mockinterceptor-serialization:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:mockinterceptor-appolyjson:${BuildConfig.TOOLBOX_VERSION}") + api("uk.co.appoly.droid:mockinterceptor-retrofit:${BuildConfig.TOOLBOX_VERSION}") } } -publishing { - publications { - create("bom") { - from(components["javaPlatform"]) - groupId = "com.github.appoly.AppolyDroid-Toolbox" - artifactId = "AppolyDroid-Toolbox-bom" - version = publishedVersion - } +mavenPublishing { + pom { + name.set("AppolyDroid Toolbox BOM") + description.set("Bill of Materials pinning a coherent set of AppolyDroid Toolbox module versions.") } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index b7fa0406..4b9d2838 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,4 @@ -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.jvm.tasks.Jar +import com.vanniktech.maven.publish.MavenPublishBaseExtension // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { @@ -10,6 +8,7 @@ plugins { alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlinxSerialization) apply false + alias(libs.plugins.vanniktech.publish) apply false } // Centralised Android unit-test configuration, applied to every Android library module so the @@ -31,46 +30,53 @@ subprojects { } } -// Publish Gradle Module Metadata, but keep the sources jar out of it. +// Shared Maven Central publishing configuration. // -// 1.8.2 published POM-only to stop JitPack mangling the sources variant (see 24f40ef for that -// story). It fixed source navigation and broke Android consumers. Without a `.module`, a module's -// POM records the *resolved* platform artifact of each multiplatform dependency instead of the -// variant-aware root coordinate: `MockInterceptor` recorded `okhttp-jvm` and `flexilogger-jvm`, so -// consuming it from an Android app dragged those onto a classpath that had already correctly -// resolved the `-android` artifacts — 13 duplicate `okhttp3.internal.ws.*` classes and a failed -// `checkDuplicateClasses`. Consumer-side excludes are unbounded whack-a-mole, since every -// multiplatform dependency reachable through a JVM-published module has the same shape. +// Replaces the JitPack-era machinery entirely: the POM-only workaround, the hand-rolled sources +// jar, and the module-metadata juggling all existed to survive JitPack rewriting what it serves. +// Central serves exactly what is uploaded, so the plugin's defaults are correct and the workarounds +// are deleted rather than ported. // -// So the metadata has to come back. The sources problem is avoided differently: never give JitPack -// a sources *variant* to mangle. No module calls `withSourcesJar()` — that would register one in -// GMM. Each publication instead attaches a plain `-sources` classifier artifact, which Gradle finds -// through the POM classifier convention, the same route 24f40ef proved works. -// -// The load-bearing assumption is that Gradle falls back to the classifier when GMM carries no -// sources variant. That CANNOT be verified locally: mavenLocal never reproduces JitPack's -// rewriting, so publishing here looks perfect either way. It needs a real JitPack build of a branch -// snapshot or pre-release tag, checking *source navigation* — not merely that consumers compile. -// -// If that check fails, the fallback is reverting to GMM-with-sources-variant (the 1.8.1 shape): -// builds work for everyone, source navigation needs "Choose Sources". Broken navigation is an -// annoyance; a classpath that will not assemble is a hard block. +// Everything shared lives here; a module declares only its own name and description. Central +// rejects an incomplete POM — a missing developers block is a hard rejection — so the required +// fields are set once, centrally, where they cannot be forgotten on a new module. subprojects { - plugins.withId("maven-publish") { - afterEvaluate { - // The BOM is a java-platform: no sources exist to publish. - if (plugins.hasPlugin("java-platform")) return@afterEvaluate + plugins.withId("com.vanniktech.maven.publish") { + extensions.configure { + publishToMavenCentral() + signAllPublications() - val sourcesJar = tasks.register("toolboxSourcesJar") { - archiveClassifier.set("sources") - // Conventional layout; this repo keeps Kotlin under src/main/java. Directories that - // do not exist simply contribute nothing. - from("src/main/java", "src/main/kotlin") - } + // Artifact IDs are lowercased module names: uk.co.appoly.droid:s3uploader-multipart. + coordinates( + groupId = "uk.co.appoly.droid", + artifactId = project.name.lowercase(), + version = BuildConfig.TOOLBOX_VERSION + ) + + pom { + url.set("https://github.com/appoly/AppolyDroid-Toolbox") + inceptionYear.set("2024") + + licenses { + license { + name.set("GNU General Public License v3.0") + url.set("https://www.gnu.org/licenses/gpl-3.0.html") + } + } + + developers { + developer { + id.set("appoly") + name.set("Appoly") + email.set("android@appoly.co.uk") + url.set("https://github.com/appoly") + } + } - extensions.configure { - publications.withType().configureEach { - artifact(sourcesJar) + scm { + url.set("https://github.com/appoly/AppolyDroid-Toolbox") + connection.set("scm:git:git://github.com/appoly/AppolyDroid-Toolbox.git") + developerConnection.set("scm:git:ssh://git@github.com/appoly/AppolyDroid-Toolbox.git") } } } diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 1a78f0ab..fc7e0c93 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -9,7 +9,7 @@ object BuildConfig { * The current version of the AppolyDroid Toolbox library. * This is used for maven publishing and README version updates. */ - const val TOOLBOX_VERSION = "1.8.3" + const val TOOLBOX_VERSION = "1.9.0" /** * SDK version configuration for Android modules. diff --git a/buildSrc/src/main/kotlin/UpdateReadmeVersions.kt b/buildSrc/src/main/kotlin/UpdateReadmeVersions.kt index 86bfc282..ff1cc0a2 100644 --- a/buildSrc/src/main/kotlin/UpdateReadmeVersions.kt +++ b/buildSrc/src/main/kotlin/UpdateReadmeVersions.kt @@ -111,7 +111,7 @@ abstract class UpdateReadmeVersions : DefaultTask() { // or match Kotlin type annotations (e.g. `HomeScreen : Nav3Screen`) when a // BOM-style unversioned `implementation("…:Module")` appears earlier in the file. val toolboxPattern = Pattern.compile( - "(implementation\\([\"']com\\.github\\.appoly\\.AppolyDroid-Toolbox:[^:\"'\\s)]+:)([\\w.-]+)([\"')])" + "(implementation\\([\"']uk\\.co\\.appoly\\.droid:[^:\"'\\s)]+:)([\\w.-]+)([\"')])" ) val beforeResult = updateVersions( content = beforeBom, @@ -141,7 +141,7 @@ abstract class UpdateReadmeVersions : DefaultTask() { updateVersions( content = content, pattern = Pattern.compile( - "(implementation\\([\"']com\\.github\\.appoly\\.AppolyDroid-Toolbox:[^:\"'\\s)]+:)([\\w.-]+)([\"')])" + "(implementation\\([\"']uk\\.co\\.appoly\\.droid:[^:\"'\\s)]+:)([\\w.-]+)([\"')])" ).toMatchProcessor(1, 3) { it == toolboxVersion }, file = file, versionName = "toolbox", @@ -159,7 +159,7 @@ abstract class UpdateReadmeVersions : DefaultTask() { val bomResult = updateVersions( content = content, pattern = Pattern.compile( - "(implementation\\(platform\\([\"']com\\.github\\.appoly\\.AppolyDroid-Toolbox:AppolyDroid-Toolbox-bom:)([\\w.-]+)([\"')])" + "(implementation\\(platform\\([\"']uk\\.co\\.appoly\\.droid:bom:)([\\w.-]+)([\"')])" ).toMatchProcessor(1, 3) { it == toolboxVersion }, file = file, versionName = "bom", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0905073..7a831bc5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] agp = "9.3.1" kotlin = "2.4.10" +vanniktechPublish = "0.37.0" ksp = "2.3.11" coreKtx = "1.19.0" junit = "4.13.2" @@ -105,5 +106,6 @@ kotlinKSP = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +vanniktech-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktechPublish" } room = { id = "androidx.room", version.ref = "roomVersion" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } diff --git a/publishing-check/build.gradle.kts b/publishing-check/build.gradle.kts index 31450658..472c7764 100644 --- a/publishing-check/build.gradle.kts +++ b/publishing-check/build.gradle.kts @@ -40,10 +40,10 @@ val toolboxVersion: String = (findProperty("toolboxVersion") as String?) ?: error("Could not determine TOOLBOX_VERSION; pass -PtoolboxVersion=x.y.z") val jvmPublishedModules = listOf( - "MockInterceptor", - "MockInterceptor-Serialization", - "MockInterceptor-AppolyJson", - "MockInterceptor-Retrofit", + "mockinterceptor", + "mockinterceptor-serialization", + "mockinterceptor-appolyjson", + "mockinterceptor-retrofit", ) // A configuration shaped like an Android app's runtime classpath. @@ -62,7 +62,7 @@ val androidConsumerProbe: Configuration by configurations.creating { dependencies { jvmPublishedModules.forEach { module -> - androidConsumerProbe("com.github.appoly.AppolyDroid-Toolbox:$module:$toolboxVersion") + androidConsumerProbe("uk.co.appoly.droid:$module:$toolboxVersion") } } @@ -94,7 +94,7 @@ tasks.register("verifyPublishedVariantResolution") { val notes = mutableListOf() // --- floors: prove the machinery ran, so the absences below mean something --- - val toolbox = resolved.filter { it.group == "com.github.appoly.AppolyDroid-Toolbox" } + val toolbox = resolved.filter { it.group == "uk.co.appoly.droid" } if (toolbox.size < jvmPublishedModules.size) { failures += "only ${toolbox.size} of ${jvmPublishedModules.size} probed toolbox modules resolved " + "(${toolbox.map { it.name }.sorted()}) — absence assertions would be vacuous" @@ -106,7 +106,7 @@ tasks.register("verifyPublishedVariantResolution") { } else { notes += "${resolved.size} components on the probe classpath" } - resolved.filter { it.group == "com.github.appoly.AppolyDroid-Toolbox" && it.version != toolboxVersion } + resolved.filter { it.group == "uk.co.appoly.droid" && it.version != toolboxVersion } .takeIf { it.isNotEmpty() } ?.let { failures += "toolbox modules at unexpected versions: ${it.map { m -> "${m.name}:${m.version}" }.sorted()}" } diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755 index 00000000..171d967b --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Publish the toolbox to Maven Central from a developer machine. +# +# Deliberately lives in the repo rather than a personal shell profile: credentials come from the +# shared 1Password vault, so anyone with vault access can release. A wrapper in one person's +# ~/.zshrc is single-user by construction. +# +# ./scripts/publish.sh --local publish to ~/.m2 (signed, no upload credentials needed) +# ./scripts/publish.sh publish to Maven Central +# +set -euo pipefail + +VAULT_ITEM="op://Appoly Shared/Appoly Maven Central Signing" +# Two 1Password accounts are registered on some machines; omitting this fails with a misleading +# "no account found for filter". +export OP_ACCOUNT="${OP_ACCOUNT:-appoly.1password.com}" + +command -v op >/dev/null || { echo "error: 1Password CLI not found. Install it and enable desktop-app integration." >&2; exit 1; } + +read_field() { + op read "$VAULT_ITEM/$1" 2>/dev/null || { + echo "error: could not read '$1' from the vault. Is the 1Password desktop app unlocked?" >&2 + exit 1 + } +} + +echo "Reading signing credentials from 1Password..." +# Gradle only reads these under the ORG_GRADLE_PROJECT_ prefix, with exactly this camelCase. +export ORG_GRADLE_PROJECT_signingInMemoryKey="$(read_field private-key)" +export ORG_GRADLE_PROJECT_signingInMemoryKeyId="$(read_field key-id)" +export ORG_GRADLE_PROJECT_signingInMemoryKeyPassword="$(read_field passphrase)" + +if [[ "${1:-}" == "--local" ]]; then + echo "Publishing to mavenLocal (signed)..." + exec ./gradlew publishToMavenLocal +fi + +export ORG_GRADLE_PROJECT_mavenCentralUsername="$(read_field portal-username)" +export ORG_GRADLE_PROJECT_mavenCentralPassword="$(read_field portal-token)" + +echo "Publishing to Maven Central..." +exec ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache From 2567c2aeec8290d789fd6794c9621413d760a600 Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Mon, 24 Aug 2026 15:48:27 +0100 Subject: [PATCH 2/9] feat(publishing): full release script modelled on FlexiLogger's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the minimal credential shim with the same release ceremony FlexiLogger uses: gates before the prompt, an explicit confirmation, and tagging only after a successful upload. One deliberate difference. FlexiLogger splits this in two — a `flexipublish` function in a personal ~/.zshrc supplies credentials, and ./publish.sh consumes them. That split makes releasing single-user: the credentials live in one person's shell profile and one person's private vault, so nobody else can publish. Here the credential loading is in the script itself, reading the SHARED Appoly vault, so anyone with vault access can release and there is nothing to install but the 1Password CLI. Three modes: (none) gates, confirm, publish to Central, tag, push the tag --dry-run every gate, no publish and no tag --local signed install to ~/.m2 for testing a branch in a consuming app Adapted rather than copied: - Version comes from BuildConfig.kt, not the version catalog. - Release branch is main, and tags are bare (1.9.0), matching this repo's existing tags rather than FlexiLogger's v-prefix. - Gates include the R8 consumer keep rules and the published-metadata check, which FlexiLogger has no equivalent of. - Refuses to run if a tag for the version already exists. Central releases are immutable, so a repeat publish cannot succeed and the clearest place to say so is before the build rather than after the upload fails. - The confirmation prints all 26 coordinates and states the immutability explicitly. This repo has spent the week retagging alphas freely; that stops working here, and the prompt is where that needs to land. - --local warns that a local install carries the same version string as the real release, so leaving mavenLocal() in place means resolving your own artifacts while believing you are testing the published one. Fixed while testing: --dry-run originally skipped credential loading, but its gate sequence runs publishToMavenLocal for the metadata check, which fails without a signing key. Signing is now read in every mode; only the upload token is release-only. Verified: --help, argument rejection, --local (26 modules signed into ~/.m2) and a full --dry-run through every gate. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/publish.sh | 209 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 25 deletions(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index 171d967b..dec47038 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -1,43 +1,202 @@ #!/usr/bin/env bash # -# Publish the toolbox to Maven Central from a developer machine. +# AppolyDroid Toolbox publishing script. # -# Deliberately lives in the repo rather than a personal shell profile: credentials come from the -# shared 1Password vault, so anyone with vault access can release. A wrapper in one person's -# ~/.zshrc is single-user by construction. +# Publishes every module to Maven Central via the Vanniktech Maven Publish plugin. # -# ./scripts/publish.sh --local publish to ~/.m2 (signed, no upload credentials needed) -# ./scripts/publish.sh publish to Maven Central +# Unlike FlexiLogger — where a `flexipublish` function in a personal ~/.zshrc supplies the +# credentials and this script only consumes them — credential loading lives here, reading the +# SHARED Appoly vault. A wrapper in one person's shell profile is single-user by construction: +# anyone else with vault access still could not publish. There is nothing to install but the +# 1Password CLI. +# +# ./scripts/publish.sh publish to Maven Central, then tag +# ./scripts/publish.sh --dry-run every gate, no publish and no tag +# ./scripts/publish.sh --local signed install to ~/.m2, for testing a branch # set -euo pipefail -VAULT_ITEM="op://Appoly Shared/Appoly Maven Central Signing" -# Two 1Password accounts are registered on some machines; omitting this fails with a misleading +readonly VAULT_ITEM="op://Appoly Shared/Appoly Maven Central Signing" +readonly RELEASE_BRANCH="main" +readonly GROUP="uk.co.appoly.droid" + +# Some machines have two 1Password accounts registered; omitting this fails with a misleading # "no account found for filter". export OP_ACCOUNT="${OP_ACCOUNT:-appoly.1password.com}" -command -v op >/dev/null || { echo "error: 1Password CLI not found. Install it and enable desktop-app integration." >&2; exit 1; } +RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; BOLD=$'\033[1m'; NC=$'\033[0m' +info() { echo "${GREEN}[INFO]${NC} $1"; } +warn() { echo "${YELLOW}[WARN]${NC} $1"; } +fail() { echo "${RED}[ERROR]${NC} $1" >&2; } + +MODE="release" +for arg in "$@"; do + case "$arg" in + --dry-run|-n) MODE="dry-run" ;; + --local|-l) MODE="local" ;; + -h|--help) + cat <<'USAGE' +Usage: ./scripts/publish.sh [--local|--dry-run] + + (no flags) Publish every module to Maven Central, then tag the commit. + --local, -l Publish signed artifacts to ~/.m2 instead. Needs no upload + credentials. Use this to test a branch in a consuming app. + --dry-run, -n Run every gate — clean, tests, coverage, keep rules, metadata + check — but do not publish or tag. + +Credentials are read from 1Password (Appoly Shared -> Appoly Maven Central +Signing). The 1Password desktop app must be unlocked, with CLI integration on. +USAGE + exit 0 ;; + *) fail "Unknown argument: $arg"; echo "Try --help" >&2; exit 1 ;; + esac +done + +# ---------------------------------------------------------------- version --- + +VERSION=$(sed -n 's/.*TOOLBOX_VERSION *= *"\([^"]*\)".*/\1/p' buildSrc/src/main/kotlin/BuildConfig.kt) +[[ -n "$VERSION" ]] || { fail "Could not read TOOLBOX_VERSION from buildSrc/src/main/kotlin/BuildConfig.kt"; exit 1; } +info "AppolyDroid Toolbox version: ${BOLD}$VERSION${NC}" +[[ "$MODE" != "release" ]] && warn "Mode: $MODE — nothing will be published to Maven Central." + +# ------------------------------------------------------------ repo checks --- +# Only enforced for a real release. A local install is precisely how you test uncommitted work. +if [[ "$MODE" == "release" ]]; then + if [[ -n $(git status --porcelain) ]]; then + fail "Uncommitted changes. Commit or stash them before publishing." + git status --short + exit 1 + fi + + BRANCH=$(git branch --show-current) + if [[ "$BRANCH" != "$RELEASE_BRANCH" ]]; then + warn "You are on '$BRANCH', not '$RELEASE_BRANCH'." + read -rp "Continue anyway? (y/N) " -n 1 reply; echo + [[ $reply =~ ^[Yy]$ ]] || exit 1 + fi + + if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then + fail "Tag $VERSION already exists, so this version was published before." + fail "Maven Central releases are immutable — bump TOOLBOX_VERSION instead." + exit 1 + fi +fi + +# ------------------------------------------------------------ credentials --- + +# Only these five names are read by Gradle. Aliases like MAVEN_CENTRAL_USERNAME are what the CI +# workflow calls its secrets, but Gradle never reads them directly — accepting one here would pass +# this check and then fail obscurely at signing or upload. read_field() { - op read "$VAULT_ITEM/$1" 2>/dev/null || { - echo "error: could not read '$1' from the vault. Is the 1Password desktop app unlocked?" >&2 - exit 1 - } + local value + if ! value=$(op read "$VAULT_ITEM/$1" 2>/dev/null) || [[ -z "$value" ]]; then + fail "Could not read '$1' from 1Password." + fail "Is the desktop app unlocked, with CLI integration enabled?" + exit 1 + fi + printf '%s' "$value" } -echo "Reading signing credentials from 1Password..." -# Gradle only reads these under the ORG_GRADLE_PROJECT_ prefix, with exactly this camelCase. -export ORG_GRADLE_PROJECT_signingInMemoryKey="$(read_field private-key)" -export ORG_GRADLE_PROJECT_signingInMemoryKeyId="$(read_field key-id)" -export ORG_GRADLE_PROJECT_signingInMemoryKeyPassword="$(read_field passphrase)" +command -v op >/dev/null || { fail "1Password CLI not found. Install it and enable desktop-app integration."; exit 1; } + +# Signing is needed in EVERY mode, dry run included: the metadata gate below runs +# publishToMavenLocal, and an unsigned publication fails with "no configured signatory". Only the +# upload token is release-only. +info "Reading signing credentials from 1Password..." +ORG_GRADLE_PROJECT_signingInMemoryKey=$(read_field private-key) +ORG_GRADLE_PROJECT_signingInMemoryKeyId=$(read_field key-id) +ORG_GRADLE_PROJECT_signingInMemoryKeyPassword=$(read_field passphrase) +export ORG_GRADLE_PROJECT_signingInMemoryKey ORG_GRADLE_PROJECT_signingInMemoryKeyId ORG_GRADLE_PROJECT_signingInMemoryKeyPassword -if [[ "${1:-}" == "--local" ]]; then - echo "Publishing to mavenLocal (signed)..." - exec ./gradlew publishToMavenLocal +if [[ "$MODE" == "release" ]]; then + info "Reading Maven Central token from 1Password..." + ORG_GRADLE_PROJECT_mavenCentralUsername=$(read_field portal-username) + ORG_GRADLE_PROJECT_mavenCentralPassword=$(read_field portal-token) + export ORG_GRADLE_PROJECT_mavenCentralUsername ORG_GRADLE_PROJECT_mavenCentralPassword fi +# No unsetting needed: these live in this script's process, not the caller's shell. -export ORG_GRADLE_PROJECT_mavenCentralUsername="$(read_field portal-username)" -export ORG_GRADLE_PROJECT_mavenCentralPassword="$(read_field portal-token)" +# ------------------------------------------------------------------ local --- + +if [[ "$MODE" == "local" ]]; then + info "Publishing signed artifacts to ~/.m2 ..." + ./gradlew publishToMavenLocal + echo + info "Installed $GROUP:* at $VERSION in ~/.m2" + warn "Add mavenLocal() to the consuming project — and take it out again afterwards." + warn "A local install carries the same version string as the real release, so leaving it in" + warn "means resolving your own artifacts while believing you are testing the published one." + exit 0 +fi + +# ------------------------------------------------------------------ gates --- + +# Everything runs before the confirmation prompt, so a broken build never waits on it. +info "Cleaning..." +./gradlew clean + +info "Running tests and the coverage gate..." +./gradlew test koverVerify || { fail "Tests or coverage gate failed. Fix before publishing."; exit 1; } + +info "Verifying consumer R8 keep rules..." +./gradlew :app:verifyConsumerKeepRules || { fail "Consumer keep rules regressed."; exit 1; } + +info "Verifying published metadata resolves for an Android consumer..." +./gradlew publishToMavenLocal +./gradlew -p publishing-check verifyPublishedVariantResolution --refresh-dependencies \ + || { fail "Published metadata would break an Android consumer."; exit 1; } + +info "All gates passed." + +if [[ "$MODE" == "dry-run" ]]; then + echo + info "================================================" + info " Dry run complete for $VERSION" + info "================================================" + info "Skipped: ./gradlew publishAndReleaseToMavenCentral" + info "Skipped: tagging $VERSION" + info "Re-run without --dry-run to publish for real." + exit 0 +fi + +# ---------------------------------------------------------------- confirm --- + +MODULES=$(sed -n 's/^include(":\(.*\)")$/\1/p' settings.gradle.kts | grep -v '^app$' | tr 'A-Z' 'a-z' | sort) +COUNT=$(echo "$MODULES" | wc -l | tr -d ' ') + +echo +echo "================================================" +echo " Publishing AppolyDroid Toolbox ${BOLD}$VERSION${NC} to Maven Central" +echo "================================================" +echo +echo "$COUNT modules under ${BOLD}$GROUP${NC}:" +echo "$MODULES" | awk '{printf " %-34s", $0; if (NR % 2 == 0) print ""} END {if (NR % 2) print ""}' +echo +warn "Maven Central releases are IMMUTABLE. $VERSION can never be re-uploaded or corrected." +warn "If anything is wrong, the only remedy is publishing a new version." +echo +read -rp "Proceed with publish? (y/N) " -n 1 reply; echo +[[ $reply =~ ^[Yy]$ ]] || { info "Publish cancelled."; exit 0; } + +# ---------------------------------------------------------------- publish --- + +info "Publishing to Maven Central..." +./gradlew publishAndReleaseToMavenCentral --no-configuration-cache + +# Tag only after a successful upload, so a failed publish never leaves a tag claiming otherwise. +if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then + warn "Tag $VERSION already exists — skipping tag creation." +else + info "Tagging $VERSION and pushing..." + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" +fi -echo "Publishing to Maven Central..." -exec ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache +echo +info "================================================" +info " Published AppolyDroid Toolbox $VERSION" +info "================================================" +echo +info "Artifacts appear on Maven Central within ~15 minutes, and in search a little later." +info "Check: https://central.sonatype.com/namespace/$GROUP" From 81d5aacce983010e1bb612173c7ecb53ade3066d Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Mon, 24 Aug 2026 16:37:11 +0100 Subject: [PATCH 3/9] - Update FlexiLogger version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a831bc5..a8ae7681 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ lifecycleRuntime = "2.11.0" appcompat = "1.8.0" activityCompose = "1.13.0" composeBom = "2026.08.00" -flexiLoggerVersion = "2.1.3" +flexiLoggerVersion = "2.1.4" okhttp = "5.5.0" retrofit = "3.0.0" sandwichVersion = "2.4.0" From a1c28be0d7d15fd28e0d62411de34370ad2670c2 Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Wed, 26 Aug 2026 12:40:08 +0100 Subject: [PATCH 4/9] fix(ci): sign only when a key is present, and bump to AGP 9.3.2 / Gradle 9.7.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull-request CI failed at `:bom:signMavenPublication` with "No configured signatory". The job runs publishToMavenLocal to feed the variant-resolution gate, but holds no PGP key — deliberately, since a pull-request build must not carry the release signing key — while signAllPublications() was unconditional. The gate therefore died before the check it exists to run. Signing is now enabled only when a key is actually present. The gate reads module metadata and POMs and never looks at a signature, so an unsigned local publish serves it perfectly well. A task-graph guard refuses any Maven Central upload without a key, so the relaxation cannot leak into a real release: it fails loudly at configuration time rather than uploading artifacts Central would reject, in a release that cannot be undone. scripts/publish.sh keeps loading signing credentials in every mode. Its comment said signing was required for the metadata gate, which is no longer true; the real reason is that a dry run must still exercise the step a release cannot survive failing, and --local exists to test the signed artifacts. Also syncs the toolchain bump: tasks.wrapper still pinned gradleVersion to 9.7.0, which would have re-downgraded the wrapper on the next `./gradlew wrapper`. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- build.gradle.kts | 26 ++++++++++++++++++++++-- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- scripts/publish.sh | 8 +++++--- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 751dddb0..47f363a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,7 +103,7 @@ flow.collect { state -> ## Tech Stack -- Kotlin 2.4.10, AGP 9.3.1, Gradle 9.7.0 +- Kotlin 2.4.10, AGP 9.3.2, Gradle 9.7.1 - Target/Compile SDK 37, Java 11 - Jetpack Compose BOM 2026.08.00 - OkHttp 5.5.0, Retrofit 3.0.0 diff --git a/build.gradle.kts b/build.gradle.kts index 4b9d2838..660627e4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,26 @@ subprojects { } } +// A signing key is supplied by scripts/publish.sh (from 1Password) and by the release workflow's +// secrets. Pull-request CI has neither, deliberately — a PR build must not hold the release key — +// yet it still runs publishToMavenLocal to feed the variant-resolution gate, which reads module +// metadata and POMs and never looks at a signature. Calling signAllPublications() unconditionally +// therefore failed that step with "no configured signatory". Signing is enabled only when a key is +// actually present; the task-graph guard below makes an unsigned Central upload impossible rather +// than merely unlikely, so the relaxation cannot leak into a real release. +val hasSigningKey = providers.gradleProperty("signingInMemoryKey").isPresent || + providers.gradleProperty("signing.keyId").isPresent + +gradle.taskGraph.whenReady { + if (!hasSigningKey && allTasks.any { it.name.contains("MavenCentral") || it.name == "releaseRepository" }) { + throw GradleException( + "Refusing to upload to Maven Central without a signing key: Central rejects unsigned " + + "artifacts, and a partial upload cannot be undone. Publish with ./scripts/publish.sh, " + + "or set ORG_GRADLE_PROJECT_signingInMemoryKey/KeyId/KeyPassword." + ) + } +} + // Shared Maven Central publishing configuration. // // Replaces the JitPack-era machinery entirely: the POM-only workaround, the hand-rolled sources @@ -44,7 +64,9 @@ subprojects { plugins.withId("com.vanniktech.maven.publish") { extensions.configure { publishToMavenCentral() - signAllPublications() + if (hasSigningKey) { + signAllPublications() + } // Artifact IDs are lowercased module names: uk.co.appoly.droid:s3uploader-multipart. coordinates( @@ -84,7 +106,7 @@ subprojects { } tasks.wrapper { - gradleVersion = "9.7.0" + gradleVersion = "9.7.1" distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a8ae7681..8a3e6774 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "9.3.1" +agp = "9.3.2" kotlin = "2.4.10" vanniktechPublish = "0.37.0" ksp = "2.3.11" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c6f915a7..8c157390 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-all.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/scripts/publish.sh b/scripts/publish.sh index dec47038..b2c03f8e 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -100,9 +100,11 @@ read_field() { command -v op >/dev/null || { fail "1Password CLI not found. Install it and enable desktop-app integration."; exit 1; } -# Signing is needed in EVERY mode, dry run included: the metadata gate below runs -# publishToMavenLocal, and an unsigned publication fails with "no configured signatory". Only the -# upload token is release-only. +# Signing is read in EVERY mode, dry run included, even though the root build now skips signing +# when no key is present (so that keyless PR CI can run publishToMavenLocal for the metadata gate). +# A dry run that skipped signing would stop exercising the one step a real release cannot survive +# failing, and --local exists precisely to test the signed artifacts. Only the upload token is +# release-only. info "Reading signing credentials from 1Password..." ORG_GRADLE_PROJECT_signingInMemoryKey=$(read_field private-key) ORG_GRADLE_PROJECT_signingInMemoryKeyId=$(read_field key-id) From 4161e79d0e3dfb4d56ab0193f8c71ee35d9d5468 Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Wed, 26 Aug 2026 13:13:47 +0100 Subject: [PATCH 5/9] docs(publishing): move the JitPack migration plan in-repo, drop release CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plan lived only as an artifact, where it drifted out of date and could not be reviewed alongside the code it describes. It moves to docs/maven-central-migration.md, updated to what has actually landed: the namespace is verified, the publishing configuration and publish script are in, and the only work left is cutting 1.9.0 and migrating the four in-house consumers. Deletes .github/workflows/release.yml. Releases are run manually and locally for now, so a tag-triggered publish job is not the chosen path — and with no Maven Central secrets in the repo it could never have published anyway. Left in place it would have reported a red release run for a release that succeeded on a developer's machine. CLAUDE.md said a version tag ran the same publish in CI, which is no longer true. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 51 -------- CLAUDE.md | 12 +- docs/maven-central-migration.md | 207 ++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 docs/maven-central-migration.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 1ee41905..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Release - -# JitPack used to build from tags itself. That responsibility is now ours: a tag publishes, and if -# the gates fail no artifact exists at all — where JitPack would have built one regardless. -on: - push: - tags: [ '[0-9]+.[0-9]+.[0-9]+', '[0-9]+.[0-9]+.[0-9]+-*' ] - -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: false - -jobs: - publish: - name: Publish to Maven Central - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-java@v4 - with: - distribution: temurin - # FlexiLogger publishes Java 24 bytecode, which the toolbox consumes. - java-version: '24' - - - uses: gradle/actions/setup-gradle@v4 - - # Same gates as CI. A red build must not produce a release. - - name: Tests and coverage gate - run: ./gradlew test koverVerify - - - name: Verify consumer R8 keep rules - run: ./gradlew :app:verifyConsumerKeepRules - - - name: Verify published metadata resolves for an Android consumer - run: | - ./gradlew publishToMavenLocal - ./gradlew -p publishing-check verifyPublishedVariantResolution --refresh-dependencies - env: - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} - - - name: Publish - run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache - env: - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} diff --git a/CLAUDE.md b/CLAUDE.md index 47f363a0..7349e6d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,7 +125,11 @@ Published to **Maven Central** under `uk.co.appoly.droid`, with lowercase artifa `~/.gradle/gradle.properties`. Gradle reads them only under the `ORG_GRADLE_PROJECT_` prefix with exact camelCase. - `./scripts/publish.sh --local` publishes signed artifacts to `~/.m2`; without `--local` it - releases to Central. Pushing a version tag runs the same thing in CI. + releases to Central. **Releases are run manually and locally** — there is no release CI job and + no Maven Central secrets in the repo, so a version tag publishes nothing on its own. +- PR CI runs `publishToMavenLocal` with no signing key, so `signAllPublications()` is applied only + when a key is present. A task-graph guard refuses any Central upload without one, so the + relaxation cannot reach a real release. - Releases are **immutable** — a version cannot be re-uploaded. Iterate with local installs or snapshots, not by retagging. - `./gradlew -p publishing-check verifyPublishedVariantResolution` resolves the published modules as @@ -133,6 +137,12 @@ Published to **Maven Central** under `uk.co.appoly.droid`, with lowercase artifa `publishToMavenLocal` first. - Each module also ships consumer ProGuard rules (`consumer-rules.pro`). +## Publishing migration + +`docs/maven-central-migration.md` is the working plan and status for the JitPack → Central move: +what has landed, what is deliberate deviation, and what remains (publish 1.9.0, then migrate the +four in-house consumers). Update it as phases complete rather than letting it drift. + ## Knowledge Graph (graphify) This repo can be analysed with `/graphify` — it builds a navigable knowledge graph of the diff --git a/docs/maven-central-migration.md b/docs/maven-central-migration.md new file mode 100644 index 00000000..06aede0b --- /dev/null +++ b/docs/maven-central-migration.md @@ -0,0 +1,207 @@ +# Leaving JitPack + +Migration of AppolyDroid Toolbox publishing from JitPack to Maven Central. + +**Status as of 26 Aug 2026** — phases 1–3 complete, 1.9.0 not yet released. +Current release: 1.8.3 on JitPack. 26 published modules. + +Two attempts to fix AAR source navigation were defeated by the same JitPack rewrite. +Publishing to Maven Central removes the rewriter rather than working around it. + +--- + +## Where this stands + +| Item | Then (24 Aug) | Now | +|------|---------------|-----| +| Namespace `uk.co.appoly` | TXT record absent from authoritative NS | **Verified** on the Central portal | +| Publishing configuration | 26 hand-written blocks, JitPack workarounds | **Landed** — `e48652e` | +| Release path | Planned as a tag-triggered CI job | **Superseded** — manual local run | +| 1.9.0 on Central | Not started | **Not published** — no tag, Central 404s | + +Nothing blocks cutting 1.9.0 except the decision to run it. After that, four in-house +consumers move over. + +--- + +## Why JitPack can't be fixed from our side + +JitPack strips the `-sources` classifier from the file entry inside a sources-typed variant +in the published `.module`. Both available shapes were tried against real JitPack builds: + +| Version | Shape | Result for AAR modules | | +|---------|-------|------------------------|---| +| 1.8.1 | Sources variant published | Variant's file entry stripped; Gradle requests a name that 404s and gives up silently | broken | +| 1.8.2 | No module metadata at all | Sources work — but POMs pin platform artifacts, so consumers get duplicate classes and cannot build | worse | +| 1.8.3 | Metadata restored, sources as POM classifier only | Builds fixed. Android Studio binds AAR sources from the variant, so with no variant it decompiles 25 of 26 modules | broken | +| 1.8.4-alpha01 | Sources variant restored for AARs | Stripped again. Android Studio requests ~25 non-existent files per sync, each timing out after 30s against a shared rate limit | worse | + +The two reachable states on JitPack are "no AAR source navigation" and "no AAR source +navigation, plus 25 doomed requests every sync". 1.8.3 is the better of those, and it is +where the library sits today. + +## What Central changes + +| | JitPack | Maven Central | +|---|---------|---------------| +| Metadata fidelity | Rewritten on serve | Byte-for-byte as uploaded | +| AAR source navigation | Not achievable | Works via the standard sources variant | +| Rate limits | Shared per-IP; has blocked us repeatedly | CDN-backed, no practical limit | +| First resolve | Builds on demand; minutes of latency | Already built and hosted | +| Release mutability | Retag and rebuild freely | **Immutable once released** | +| Credentials | None | Portal token + GPG signing key | + +Immutability cuts both ways. It is a discipline improvement, but the alpha-retag loop used +throughout the 1.8.3 work stops being possible. + +One incidental benefit: the whole `withSourcesJar()` question disappears. The publishing +plugin configures sources and javadoc for Android library variants itself, so the +hand-rolled sources-jar machinery was deleted rather than fixed. + +--- + +## Phases + +### 1. Portal and namespace — done + +- **Namespace verified.** `uk.co.appoly` shows as Verified on the Central portal under org + *Appoly*. The TXT record that was genuinely absent from `appoly.co.uk`'s authoritative + nameservers is now live. A verified namespace covers its subgroups, so + `uk.co.appoly.droid` needs no separate claim. +- **Signing key.** RSA 4096, no expiry, `2FF86BD312C381D279FA36F23F4AD175B7176969`, uid + `Appoly (Maven Central Signing)`. In 1Password under *Appoly Shared → Appoly Maven + Central Signing*, with both revocation certificates pre-generated and no copy on disk. + The key belongs to the organisation, not a person — anyone with vault access can publish. +- **Public half** on `hkps://keyserver.ubuntu.com`, verified by fetching it back into a + clean keyring rather than trusting `--send-keys`, whose exit code reports success + regardless. That keyserver only — which is the one Central checks. + +Unverified: whether `portal-username` and `portal-token` are populated in the vault item. +`scripts/publish.sh` reads both by those exact names and fails clearly if either is missing, +so the first release run will say so immediately. + +### 2. Publishing configuration — done (`e48652e`) + +All 26 modules carry `com.vanniktech.maven.publish` 0.37.0. Shared POM metadata — licence, +developers, SCM, URL — lives once in the root build, so a module declares only its own name +and description. No hand-written `publishing { }` block survives anywhere in the tree. + +Central rejects an incomplete POM, and a missing `developers` block is a hard rejection. + +- Sources-jar machinery and module-metadata workarounds **deleted**, not ported. They + existed only to fight the rewriter. +- The BOM's constraints are rewritten to the new coordinates. +- `publishing-check` is kept. Platform-variant leakage is a defect class independent of the + host, and it runs in CI on every PR. + +### 3. Release path — done, revised + +**Superseded.** This phase originally specified a tag-triggered GitHub Actions release job. +That is not the chosen path: **releases are run manually, locally, by a developer** for now. +No release CI, and no Maven Central secrets in the repository — repo-level Actions secrets +are empty, which is correct rather than missing. `.github/workflows/release.yml` has been +deleted to match. + +The wrapper lives **in the repo**, not a personal shell profile. FlexiLogger's equivalent +sits in one person's `~/.zshrc`, which is single-user by construction; `scripts/publish.sh` +reads the shared vault, so anyone with access can release. It offers `--dry-run` (every +gate, no publish, no tag) and `--local` (signed install to `~/.m2`). + +Gradle reads these only under the `ORG_GRADLE_PROJECT_` prefix with exact camelCase — the +script exports all five: + +| 1Password field | Environment variable | +|-----------------|----------------------| +| `portal-username` | `ORG_GRADLE_PROJECT_mavenCentralUsername` | +| `portal-token` | `ORG_GRADLE_PROJECT_mavenCentralPassword` | +| `private-key` | `ORG_GRADLE_PROJECT_signingInMemoryKey` | +| `key-id` | `ORG_GRADLE_PROJECT_signingInMemoryKeyId` | +| `passphrase` | `ORG_GRADLE_PROJECT_signingInMemoryKeyPassword` | + +```bash +export OP_ACCOUNT=appoly.1password.com # required: two accounts are registered +``` + +### 4. Publish 1.9.0 and verify — next + +A minor bump, not a patch — the coordinates change, so consumers must act. +`TOOLBOX_VERSION` is already `1.9.0`; no `1.9.0` tag exists and Central returns 404 for the +coordinates, so nothing has been claimed yet. + +Verification drops the parts that existed only to catch JitPack's rewriting. What remains is +the check 1.8.3's verification missed: confirm the served `.module` carries a sources variant +whose file entry ends `-sources.jar`, then confirm cmd+B lands on real source for an **AAR** +module — not just the one JVM module. + +**Immutability bites here.** A released version can never be re-uploaded or corrected. +Iterate with `--local` *before* the release, not after. + +Owner: Bradley to run `./scripts/publish.sh`; one consumer session to verify. + +### 5. Migrate consumers + +All known consumers are in-house — WenWe, AssistantHood, AIM Capture, Accelerate-Android — +so a clean cut is simpler than dual-publishing. 1.8.3 stays available on JitPack indefinitely +as the last release there. + +Worth pairing deliberately: WenWe and AssistantHood exercise different module sets, so a +single green consumer proves little about the other. + +Owner: per-app sessions, roughly 30 minutes each. + +--- + +## One deliberate deviation: keyless local publishing + +This plan originally warned that once signing was configured, a bare +`./gradlew publishToMavenLocal` would fail with "no configured signatory", and called that +intended. It has since been relaxed, because it broke pull-request CI. + +PR CI runs `publishToMavenLocal` to feed the variant-resolution gate, and deliberately holds +no signing key — a pull-request build must not carry the release key. With +`signAllPublications()` unconditional, the gate died at `:bom:signMavenPublication` before +the check it exists to run. Signing is now enabled only when a key is present; the gate reads +module metadata and POMs and never looks at a signature. + +The safety property is preserved by a task-graph guard rather than by accident: any Maven +Central upload without a key fails at configuration time with an explicit refusal, instead of +uploading artifacts Central would reject in a release that cannot be undone. + +Side effect worth knowing: a keyless local publish leaves *stale* `.asc` files from earlier +signed runs sitting beside freshly-written unsigned artifacts in `~/.m2`. Listing the +directory looks signed. Timestamps are the only honest signal. + +--- + +## What consumers change + +| Before | After | +|--------|-------| +| `com.github.appoly.AppolyDroid-Toolbox:BaseRepo` | `uk.co.appoly.droid:baserepo` | +| `com.github.appoly.AppolyDroid-Toolbox:S3Uploader-Multipart` | `uk.co.appoly.droid:s3uploader-multipart` | +| `com.github.appoly.AppolyDroid-Toolbox:AppolyDroid-Toolbox-bom` | `uk.co.appoly.droid:bom` | +| `maven { url = "https://jitpack.io" }` | Removable, if nothing else needs it | + +## Risks and gotchas + +- **No more retagging.** Released versions are immutable. Pre-release iteration happens + through `--local`, which behaves differently from the alpha tags used during 1.8.3. +- **The key is on one keyserver only.** `keyserver.ubuntu.com`, which is what Central checks. + Do not assume it resolves elsewhere. +- **Coordinated cutover.** Four apps need updating; 1.8.3 and 1.9.0 will briefly coexist + across the estate. +- **Release runs on one machine.** With no CI release job, publishing depends on a + developer's local toolchain and an unlocked 1Password. That is the accepted trade for now, + not an oversight. + +### Dead ends — do not resurrect + +- **"GPG Appoly key"** in the Appoly *Employee* vault (2023) holds a username and passphrase + but no key material, and the key exists nowhere reachable. Abandoned earlier attempt. +- **"Nexus Staging"** in Appoly Shared (2020) belongs to Calum and is explicitly out of + scope. Unrelated to this migration. + +--- + +1.8.3 remains the current JitPack release and is unaffected; its heap, progress, notification +and duplicate-class fixes are all verified and unrelated to the sources question. From 1ad7152e03e6ffa2e24499cb2f96eb30ddd7dcbc Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Wed, 26 Aug 2026 13:30:23 +0100 Subject: [PATCH 6/9] docs(publishing): record that the Central portal token is missing Listing the vault item's field labels confirms what phase 1 left open: the signing fields are all present, but portal-username and portal-token were never added. publish.sh reads those two only in release mode, so --dry-run and --local work today and a real release fails immediately at the credential step, before any gate runs. That is the last thing standing between this branch and 1.9.0. Co-Authored-By: Claude Opus 5 (1M context) --- docs/maven-central-migration.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/maven-central-migration.md b/docs/maven-central-migration.md index 06aede0b..f21aaa08 100644 --- a/docs/maven-central-migration.md +++ b/docs/maven-central-migration.md @@ -17,10 +17,11 @@ Publishing to Maven Central removes the rewriter rather than working around it. | Namespace `uk.co.appoly` | TXT record absent from authoritative NS | **Verified** on the Central portal | | Publishing configuration | 26 hand-written blocks, JitPack workarounds | **Landed** — `e48652e` | | Release path | Planned as a tag-triggered CI job | **Superseded** — manual local run | -| 1.9.0 on Central | Not started | **Not published** — no tag, Central 404s | +| 1.9.0 on Central | Not started | **Blocked** — portal token missing from the vault | -Nothing blocks cutting 1.9.0 except the decision to run it. After that, four in-house -consumers move over. +One thing blocks cutting 1.9.0: the Central portal token is not in the vault yet (see +phase 1). Everything else is ready — `--dry-run` and `--local` run clean today. After the +release, four in-house consumers move over. --- @@ -76,9 +77,22 @@ hand-rolled sources-jar machinery was deleted rather than fixed. clean keyring rather than trusting `--send-keys`, whose exit code reports success regardless. That keyserver only — which is the one Central checks. -Unverified: whether `portal-username` and `portal-token` are populated in the vault item. -`scripts/publish.sh` reads both by those exact names and fails clearly if either is missing, -so the first release run will say so immediately. +**Outstanding: the portal token does not exist yet.** Confirmed 26 Aug by listing the vault +item's field labels: it holds `private-key`, `passphrase`, `key-id`, `fingerprint`, +`master-key`, `signing-subkey`, `uid`, `revoke-retired`, `revoke-compromised` and +`keyserver` — but no `portal-username` and no `portal-token`. + +`scripts/publish.sh` reads those two by exactly those names, and only in release mode, so: + +- `--dry-run` and `--local` work today — they need only the three signing fields. +- A real release fails immediately, before any gate runs, with + `Could not read 'portal-username' from 1Password.` + +To unblock: generate a user token on the Central portal (Account → Generate User Token) as +`android@appoly.co.uk`, then add its two halves to the *Appoly Maven Central Signing* item as +fields named exactly `portal-username` and `portal-token`. Hyphens, no spaces — a label +containing a space or parenthesis cannot be read by an `op://` reference at all, which is why +FlexiLogger's `private key (armored)` needs a JSON workaround its wrapper carries. ### 2. Publishing configuration — done (`e48652e`) @@ -126,7 +140,7 @@ export OP_ACCOUNT=appoly.1password.com # required: two accounts are registered A minor bump, not a patch — the coordinates change, so consumers must act. `TOOLBOX_VERSION` is already `1.9.0`; no `1.9.0` tag exists and Central returns 404 for the -coordinates, so nothing has been claimed yet. +coordinates, so nothing has been claimed yet. Needs the portal token from phase 1 first. Verification drops the parts that existed only to catch JitPack's rewriting. What remains is the check 1.8.3's verification missed: confirm the served `.module` carries a sources variant From b9f5422be314cfcd3ed53326872d83b57efcb86b Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Wed, 26 Aug 2026 13:39:21 +0100 Subject: [PATCH 7/9] fix(publishing): read the portal token in dry-run mode too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The portal credentials were read only in release mode, so a dry run rehearsed every gate except the two fields most likely to be wrong — a missing or misnamed vault field would first surface on the immutable run it exists to protect. Reading them in dry-run mode costs nothing and uploads nothing. --local still skips them; it needs no upload credentials at all. Also records that portal-username and portal-token are now in the vault, so the plan no longer lists the release as blocked. Co-Authored-By: Claude Opus 5 (1M context) --- docs/maven-central-migration.md | 31 +++++++++++-------------------- scripts/publish.sh | 7 ++++++- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/docs/maven-central-migration.md b/docs/maven-central-migration.md index f21aaa08..7833b38a 100644 --- a/docs/maven-central-migration.md +++ b/docs/maven-central-migration.md @@ -17,11 +17,10 @@ Publishing to Maven Central removes the rewriter rather than working around it. | Namespace `uk.co.appoly` | TXT record absent from authoritative NS | **Verified** on the Central portal | | Publishing configuration | 26 hand-written blocks, JitPack workarounds | **Landed** — `e48652e` | | Release path | Planned as a tag-triggered CI job | **Superseded** — manual local run | -| 1.9.0 on Central | Not started | **Blocked** — portal token missing from the vault | +| 1.9.0 on Central | Not started | **Ready to publish** — nothing outstanding | -One thing blocks cutting 1.9.0: the Central portal token is not in the vault yet (see -phase 1). Everything else is ready — `--dry-run` and `--local` run clean today. After the -release, four in-house consumers move over. +Nothing blocks cutting 1.9.0 except the decision to run it. After the release, four +in-house consumers move over. --- @@ -77,22 +76,14 @@ hand-rolled sources-jar machinery was deleted rather than fixed. clean keyring rather than trusting `--send-keys`, whose exit code reports success regardless. That keyserver only — which is the one Central checks. -**Outstanding: the portal token does not exist yet.** Confirmed 26 Aug by listing the vault -item's field labels: it holds `private-key`, `passphrase`, `key-id`, `fingerprint`, -`master-key`, `signing-subkey`, `uid`, `revoke-retired`, `revoke-compromised` and -`keyserver` — but no `portal-username` and no `portal-token`. +- **Portal token.** `portal-username` and `portal-token` are in the vault item alongside the + key, added 26 Aug. Field labels use hyphens and no spaces, which matters: a label + containing a space or parenthesis cannot be read through an `op://` reference at all — + that is why FlexiLogger's `private key (armored)` needs the JSON workaround its wrapper + carries, and why this script does not. -`scripts/publish.sh` reads those two by exactly those names, and only in release mode, so: - -- `--dry-run` and `--local` work today — they need only the three signing fields. -- A real release fails immediately, before any gate runs, with - `Could not read 'portal-username' from 1Password.` - -To unblock: generate a user token on the Central portal (Account → Generate User Token) as -`android@appoly.co.uk`, then add its two halves to the *Appoly Maven Central Signing* item as -fields named exactly `portal-username` and `portal-token`. Hyphens, no spaces — a label -containing a space or parenthesis cannot be read by an `op://` reference at all, which is why -FlexiLogger's `private key (armored)` needs a JSON workaround its wrapper carries. +`scripts/publish.sh` reads all five by exactly those names and fails clearly if any is +missing, so a misnamed field surfaces immediately rather than mid-upload. ### 2. Publishing configuration — done (`e48652e`) @@ -140,7 +131,7 @@ export OP_ACCOUNT=appoly.1password.com # required: two accounts are registered A minor bump, not a patch — the coordinates change, so consumers must act. `TOOLBOX_VERSION` is already `1.9.0`; no `1.9.0` tag exists and Central returns 404 for the -coordinates, so nothing has been claimed yet. Needs the portal token from phase 1 first. +coordinates, so nothing has been claimed yet. Verification drops the parts that existed only to catch JitPack's rewriting. What remains is the check 1.8.3's verification missed: confirm the served `.module` carries a sources variant diff --git a/scripts/publish.sh b/scripts/publish.sh index b2c03f8e..0f3ad34b 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -111,7 +111,12 @@ ORG_GRADLE_PROJECT_signingInMemoryKeyId=$(read_field key-id) ORG_GRADLE_PROJECT_signingInMemoryKeyPassword=$(read_field passphrase) export ORG_GRADLE_PROJECT_signingInMemoryKey ORG_GRADLE_PROJECT_signingInMemoryKeyId ORG_GRADLE_PROJECT_signingInMemoryKeyPassword -if [[ "$MODE" == "release" ]]; then +# Read in dry-run mode too, not just release. A dry run exists to fail before the immutable +# step does, and a missing or misnamed portal field is exactly the kind of thing it should +# catch — reading it here costs nothing and means the first real release is not the first +# time these two fields have ever been fetched. Nothing uploads in dry-run mode regardless. +# --local skips them: it needs no upload credentials at all. +if [[ "$MODE" != "local" ]]; then info "Reading Maven Central token from 1Password..." ORG_GRADLE_PROJECT_mavenCentralUsername=$(read_field portal-username) ORG_GRADLE_PROJECT_mavenCentralPassword=$(read_field portal-token) From 94fcdce652a567ba1c1ee03c272908e44be3cc1c Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Wed, 26 Aug 2026 13:57:02 +0100 Subject: [PATCH 8/9] fix(publishing): pin Gradle memory for the release script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A dry run failed with a bare "Metaspace" error partway through publishing. Cause: publishing drives Dokka across all 26 modules in one daemon, which needs well over the 1 GiB metaspace a typical personal ~/.gradle/gradle.properties sets. User-level properties take precedence over the repo's, so the project cannot set this itself — meaning a release succeeded or failed depending on whose machine ran it. GRADLE_OPTS beats both files, so the script now pins its own. Also guards `./gradlew publishToMavenLocal` in the metadata gate, the only unchecked Gradle call in that section. set -e already aborted correctly; this just names what broke instead of leaving a raw Gradle failure. With both in, `--dry-run` passes end to end: all five vault fields read, gates green, 26 modules signed into ~/.m2, variant resolution clean. Co-Authored-By: Claude Opus 5 (1M context) --- docs/maven-central-migration.md | 11 +++++++++++ scripts/publish.sh | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/maven-central-migration.md b/docs/maven-central-migration.md index 7833b38a..a7edc0ec 100644 --- a/docs/maven-central-migration.md +++ b/docs/maven-central-migration.md @@ -141,6 +141,17 @@ module — not just the one JVM module. **Immutability bites here.** A released version can never be re-uploaded or corrected. Iterate with `--local` *before* the release, not after. +A full `--dry-run` passed on 26 Aug: all five vault fields read, tests and coverage green, +consumer keep rules intact, 26 modules signed and installed to `~/.m2`, and the variant gate +clean (4 toolbox modules at 1.9.0, no `-jvm` duplicates). The only untested step is the +upload itself — reading the portal token proves it is fetchable, not that Central accepts it. + +Note the script pins `GRADLE_OPTS` for its own Gradle runs. Publishing drives Dokka across all +26 modules in a single daemon, which exceeds the 1 GiB metaspace a typical personal +`~/.gradle/gradle.properties` sets — and user-level properties beat the repo's, so the project +cannot fix this itself. Unpinned, the release fails with a bare `Metaspace` error on an +arbitrary module, naming the real cause nowhere. + Owner: Bradley to run `./scripts/publish.sh`; one consumer session to verify. ### 5. Migrate consumers diff --git a/scripts/publish.sh b/scripts/publish.sh index 0f3ad34b..e479cb4a 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -20,6 +20,15 @@ readonly VAULT_ITEM="op://Appoly Shared/Appoly Maven Central Signing" readonly RELEASE_BRANCH="main" readonly GROUP="uk.co.appoly.droid" +# Pin the daemon's memory for this script's Gradle invocations rather than inheriting whatever +# each developer keeps in ~/.gradle/gradle.properties — which takes precedence over the repo's +# own value, so the project cannot set this itself. Publishing runs Dokka across all 26 modules +# in one daemon, which needs well over the 1 GiB metaspace a typical personal config sets; the +# failure is a bare "Metaspace" on an arbitrary module, with the real cause named nowhere. +# A release must not succeed or fail depending on whose machine it runs on. +export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=2048m -Dfile.encoding=UTF-8" + + # Some machines have two 1Password accounts registered; omitting this fails with a misleading # "no account found for filter". export OP_ACCOUNT="${OP_ACCOUNT:-appoly.1password.com}" @@ -150,7 +159,7 @@ info "Verifying consumer R8 keep rules..." ./gradlew :app:verifyConsumerKeepRules || { fail "Consumer keep rules regressed."; exit 1; } info "Verifying published metadata resolves for an Android consumer..." -./gradlew publishToMavenLocal +./gradlew publishToMavenLocal || { fail "Publishing to ~/.m2 failed; the metadata gate cannot run."; exit 1; } ./gradlew -p publishing-check verifyPublishedVariantResolution --refresh-dependencies \ || { fail "Published metadata would break an Android consumer."; exit 1; } From b3e00ea48e6345bfd382b734680e8d969cffacb7 Mon Sep 17 00:00:00 2001 From: Bradley Duck Date: Wed, 26 Aug 2026 14:05:52 +0100 Subject: [PATCH 9/9] chore: drop the last two JitPack remnants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing resolves from jitpack.io any more. The one plausible candidate is com.github.skydoves:sandwich, whose group name suggests otherwise, but it publishes to Maven Central — so the repository entry was only widening the set of hosts the build trusts. Verified by resolving :app's releaseRuntimeClasspath with --refresh-dependencies after removal: 1199 components, nothing unresolved. jitpack.yml is likewise vestigial. JitPack builds a tag from that tag's own copy of the file, so removing it here cannot affect anyone still resolving 1.8.3. Co-Authored-By: Claude Opus 5 (1M context) --- jitpack.yml | 8 -------- settings.gradle.kts | 3 --- 2 files changed, 11 deletions(-) delete mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml deleted file mode 100644 index 1b99ca85..00000000 --- a/jitpack.yml +++ /dev/null @@ -1,8 +0,0 @@ -jdk: - - openjdk11 -before_install: - - sdk update - - sdk install java 17.0.6-amzn - - sdk use java 17.0.6-amzn - - sdk install maven - - mvn -v \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f15b8255..a35a2f14 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,9 +43,6 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - maven { - url = uri("https://jitpack.io") - } } }