diff --git a/.github/workflows/publish-gradle-plugin.yml b/.github/workflows/publish-gradle-plugin.yml index 57b4635..8a875bf 100644 --- a/.github/workflows/publish-gradle-plugin.yml +++ b/.github/workflows/publish-gradle-plugin.yml @@ -1,12 +1,15 @@ name: Publish the Gradle plugin -# Publishing a GitHub Release tagged `v` (the plugin's tags, #71) stages and checks the plugin -# and its marker (.github/scripts/maven/check_central_bundle.py --plugin-id) and publishes them to the -# Gradle Plugin Portal (`publishPlugins`). A manual run does everything except the upload (#99). +# Publishing a GitHub Release tagged `v` (the plugin's tags, #71) signs and stages the plugin +# (`org.thisisthepy.python.multiplatform:toolchain`) and its marker +# (`org.thisisthepy.python.multiplatform:org.thisisthepy.python.multiplatform.gradle.plugin`), checks +# the bundle against Maven Central's requirements (.github/scripts/maven/check_central_bundle.py --plugin-id) +# and uploads it to the Sonatype Central Portal as USER_MANAGED: the Portal validates it and waits, and +# a person presses Publish there. A manual run does everything except the upload (#99, #116). # -# Nothing can upload until the user has created the `gradle-plugin-portal` environment (restricted to -# `v*` tags) with its secrets and published such a release; without a required secret the job stops -# and names it. The plugin depends on packpack, which the Portal resolves from Maven Central, so the +# Nothing can upload until the user has created the `maven-central` environment (restricted to +# `v*` tags) with its four secrets and published such a release; without a secret the job stops +# and names it. The plugin depends on packpack, which consumers resolve from Maven Central, so the # job also stops while gradle.properties' packpackVersion is not on Central. docs/SPEC.md Β§1.19. on: release: @@ -26,14 +29,14 @@ jobs: if: github.event_name != 'release' || (startsWith(github.event.release.tag_name, 'v')) runs-on: ubuntu-latest timeout-minutes: 30 - environment: ${{ github.event_name == 'release' && 'gradle-plugin-portal' || '' }} + environment: ${{ github.event_name == 'release' && 'maven-central' || '' }} env: IS_RELEASE: ${{ github.event_name == 'release' }} TAG: ${{ github.event.release.tag_name }} steps: - uses: actions/checkout@v4 - - name: The tag names the version; packpack is on Central; the version is new on the Portal + - name: The tag names the version; packpack is on Central; the version is new on Central run: | prop() { sed -n "s/^$1=//p" gradle.properties; } group=$(prop mavenGroup); version=$(prop toolchainVersion); plugin=$(prop pluginId) @@ -47,27 +50,28 @@ jobs: pp_group=${packpack%%:*}; pp_artifact=${packpack##*:} curl -fsS -o /dev/null "https://repo1.maven.org/maven2/${pp_group//.//}/$pp_artifact/$packpack_version/$pp_artifact-$packpack_version.pom" \ || fail "$packpack:$packpack_version is not on Maven Central; publish packpack first (pypackpack publish-maven.yml)" - marker="https://plugins.gradle.org/m2/${plugin//.//}/$plugin.gradle.plugin/$version/$plugin.gradle.plugin-$version.pom" - if curl -fsS -o /dev/null "$marker"; then fail "$plugin $version is already on the Plugin Portal; bump toolchainVersion"; fi + pom="https://repo1.maven.org/maven2/${group//.//}/toolchain/$version/toolchain-$version.pom" + if curl -fsS -o /dev/null "$pom"; then fail "$group:toolchain:$version is already on Maven Central; bump toolchainVersion"; fi + marker="https://repo1.maven.org/maven2/${plugin//.//}/$plugin.gradle.plugin/$version/$plugin.gradle.plugin-$version.pom" + if curl -fsS -o /dev/null "$marker"; then fail "$plugin $version marker is already on Maven Central; bump toolchainVersion"; fi - name: The publishing secrets are there env: - GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} - GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} + CENTRAL_PORTAL_USERNAME: ${{ secrets.CENTRAL_PORTAL_USERNAME }} + CENTRAL_PORTAL_PASSWORD: ${{ secrets.CENTRAL_PORTAL_PASSWORD }} run: | missing="" - for name in GRADLE_PUBLISH_KEY GRADLE_PUBLISH_SECRET; do + for name in SIGNING_KEY SIGNING_KEY_PASSWORD CENTRAL_PORTAL_USERNAME CENTRAL_PORTAL_PASSWORD; do [ -n "${!name}" ] || missing="$missing $name" done if [ -n "$missing" ]; then if [ "$IS_RELEASE" = true ]; then - echo "::error::missing secrets in the gradle-plugin-portal environment:$missing (docs/SPEC.md Β§1.19)"; exit 1 + echo "::error::missing secrets in the maven-central environment:$missing (docs/SPEC.md Β§1.19)"; exit 1 fi - echo "::notice::dry run without:$missing; nothing is uploaded" + echo "::notice::dry run without:$missing; the bundle is built unsigned and nothing is uploaded" fi - # The Portal does not require signatures; with SIGNING_KEY (and SIGNING_KEY_PASSWORD) they are added. - [ -n "$SIGNING_KEY" ] || echo "::notice::no SIGNING_KEY: the plugin is published unsigned" - uses: actions/setup-java@v4 with: @@ -88,7 +92,7 @@ jobs: (cd "$RUNNER_TEMP/pypackpack" && ./gradlew :packpack:publishToMavenLocal --console=plain) fi - - name: Stage the plugin and its marker (signed when a key is there) + - name: Stage the bundle (signed when a key is there) env: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} @@ -96,9 +100,9 @@ jobs: if [ -z "$ORG_GRADLE_PROJECT_signingInMemoryKey" ]; then unset ORG_GRADLE_PROJECT_signingInMemoryKey ORG_GRADLE_PROJECT_signingInMemoryKeyPassword fi - ./gradlew :toolchain:publishAllPublicationsToStagingRepository --console=plain + ./gradlew :toolchain:centralBundle --console=plain - - name: The plugin and its marker meet the requirements + - name: The plugin and its marker meet Central's requirements env: SIGNED: ${{ secrets.SIGNING_KEY != '' }} run: | @@ -106,15 +110,21 @@ jobs: [ "$SIGNED" = true ] || flag="--unsigned" python3 .github/scripts/maven/check_central_bundle.py toolchain/build/staging-deploy "$GROUP" toolchain "$VERSION" --plugin-id "$PLUGIN_ID" $flag - - name: Publish to the Gradle Plugin Portal + - uses: actions/upload-artifact@v4 + with: + name: central-bundle-${{ env.VERSION }} + path: toolchain/build/central/central-bundle-${{ env.VERSION }}.zip + + - name: Upload to the Central Portal (USER_MANAGED; press Publish in the Portal) if: github.event_name == 'release' env: - GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} - GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} + CENTRAL_PORTAL_USERNAME: ${{ secrets.CENTRAL_PORTAL_USERNAME }} + CENTRAL_PORTAL_PASSWORD: ${{ secrets.CENTRAL_PORTAL_PASSWORD }} run: | - if [ -z "$ORG_GRADLE_PROJECT_signingInMemoryKey" ]; then - unset ORG_GRADLE_PROJECT_signingInMemoryKey ORG_GRADLE_PROJECT_signingInMemoryKeyPassword - fi - ./gradlew :toolchain:publishPlugins --console=plain + token=$(printf '%s:%s' "$CENTRAL_PORTAL_USERNAME" "$CENTRAL_PORTAL_PASSWORD" | base64 -w0) + id=$(curl --fail-with-body -sS -X POST \ + -H "Authorization: Bearer $token" \ + -F "bundle=@toolchain/build/central/central-bundle-$VERSION.zip" \ + "https://central.sonatype.com/api/v1/publisher/upload?publishingType=USER_MANAGED&name=toolchain-$VERSION") + echo "Central Portal deployment: $id" + echo "::notice::uploaded as deployment $id; check it at https://central.sonatype.com/publishing/deployments and press Publish" diff --git a/README.md b/README.md index ec45437..0e1a225 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,29 @@ owns the Gradle vocabulary in between. ## πŸš€ Quick start -> `toolchain` is pre-release and published to Maven Local only. Publish `pypackpack`'s `packpack` -> artifact and this plugin locally first, and have `uv` on your `PATH`. +> `toolchain` is published to Maven Central (with its Gradle plugin marker, so add `mavenCentral()` to +> `pluginManagement.repositories` in `settings.gradle.kts`). While pre-release, publish `pypackpack`'s +> `packpack` artifact and this plugin to `mavenLocal()` first, and have `uv` on your `PATH`. ```shell # in pypackpack: ./gradlew :packpack:publishToMavenLocal ./gradlew :toolchain:publishToMavenLocal ``` +```kotlin +// settings.gradle.kts +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() // while using an unreleased local build + } +} +``` + | Coordinates | What | |---|---| -| plugin id `org.thisisthepy.python.multiplatform` | the Gradle plugin; apply it by id (unchanged) | +| plugin id `org.thisisthepy.python.multiplatform` | the Gradle plugin; resolved via its marker on Maven Central | | `org.thisisthepy.python.multiplatform:toolchain` | the plugin's implementation artifact | | PyPI `toolchain-lite` | `tcl`, the command line for Python users (`uv tool install toolchain-lite`) | diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index e608a5c..542960f 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -34,7 +34,7 @@

1. Prerequisites1. μ€€λΉ„ # in this repository ./gradlew :toolchain:publishToMavenLocal -

The plugin is pre-release and resolved from mavenLocal(). Add mavenLocal() to pluginManagement.repositories in your settings.gradle.kts.

ν”ŒλŸ¬κ·ΈμΈμ€ μΆœμ‹œ 전이며 mavenLocal() μ—μ„œ λ°›μŠ΅λ‹ˆλ‹€. settings.gradle.kts 의 pluginManagement.repositories 에 mavenLocal() 을 μΆ”κ°€ν•˜μ„Έμš”.

+

The plugin and its marker are distributed through Maven Central (with mavenLocal() as the fallback while pre-release). Put mavenCentral() in pluginManagement.repositories in your settings.gradle.kts (plus mavenLocal() for local builds).

ν”ŒλŸ¬κ·ΈμΈκ³Ό λ§ˆμ»€λŠ” Maven Central 둜 λ°°ν¬λ©λ‹ˆλ‹€(μΆœμ‹œ μ „μ—λŠ” mavenLocal() 둜 λŒ€μ²΄). settings.gradle.kts 의 pluginManagement.repositories 에 mavenCentral() 을 λ‘μ„Έμš”(둜컬 λΉŒλ“œλ₯Ό μ“Έ λ•ŒλŠ” mavenLocal() 도 ν•¨κ»˜).

diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index bf52e1d..86118b8 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -47,17 +47,29 @@ Python 을 λ‚΄μž₯ν•œ Kotlin Multiplatform μ•±μ—λŠ” 맞물렀 λŒμ•„κ°€μ•Ό ν•˜ ## πŸš€ λΉ λ₯Έ μ‹œμž‘ -> `toolchain` 은 정식 μΆœμ‹œ 전이며 Maven Local μ—λ§Œ λ°°ν¬λ©λ‹ˆλ‹€. `pypackpack` 의 `packpack` -> μ•„ν‹°νŒ©νŠΈμ™€ 이 ν”ŒλŸ¬κ·ΈμΈμ„ λ¨Όμ € λ‘œμ»¬μ— λ°°ν¬ν•˜κ³ , `PATH` 에 `uv` κ°€ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. +> `toolchain` 은 Gradle ν”ŒλŸ¬κ·ΈμΈ λ§ˆμ»€μ™€ ν•¨κ»˜ Maven Central 에 λ°°ν¬λ©λ‹ˆλ‹€(`settings.gradle.kts` 의 +> `pluginManagement.repositories` 에 `mavenCentral()` 을 λ‘μ„Έμš”). 정식 μΆœμ‹œ μ „μ—λŠ” `pypackpack` 의 +> `packpack` μ•„ν‹°νŒ©νŠΈμ™€ 이 ν”ŒλŸ¬κ·ΈμΈμ„ λ¨Όμ € `mavenLocal()` 에 λ°°ν¬ν•˜κ³ , `PATH` 에 `uv` κ°€ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. ```shell # pypackpack μ—μ„œ: ./gradlew :packpack:publishToMavenLocal ./gradlew :toolchain:publishToMavenLocal ``` +```kotlin +// settings.gradle.kts +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() // μΆœμ‹œ μ „ 둜컬 λΉŒλ“œλ₯Ό μ“Έ λ•Œ + } +} +``` + | μ’Œν‘œ | 무엇 | |---|---| -| ν”ŒλŸ¬κ·ΈμΈ id `org.thisisthepy.python.multiplatform` | Gradle ν”ŒλŸ¬κ·ΈμΈ; id 둜 적용 (λ³€κ²½ μ—†μŒ) | +| ν”ŒλŸ¬κ·ΈμΈ id `org.thisisthepy.python.multiplatform` | Gradle ν”ŒλŸ¬κ·ΈμΈ; Maven Central 의 마컀둜 해석 | | `org.thisisthepy.python.multiplatform:toolchain` | ν”ŒλŸ¬κ·ΈμΈ κ΅¬ν˜„ μ•„ν‹°νŒ©νŠΈ | | PyPI `toolchain-lite` | Python μ‚¬μš©μžλ₯Ό μœ„ν•œ λͺ…령쀄 도ꡬ `tcl` (`uv tool install toolchain-lite`) | diff --git a/toolchain/build.gradle.kts b/toolchain/build.gradle.kts index acb5968..321f119 100644 --- a/toolchain/build.gradle.kts +++ b/toolchain/build.gradle.kts @@ -7,13 +7,16 @@ plugins { `maven-publish` `java-gradle-plugin` signing - // The Gradle Plugin Portal's publishing (`publishPlugins`); it also adds the sources and javadoc - // jars the portal and Maven Central require (toolchain#97). Nothing runs it here. - id("com.gradle.plugin-publish") version "1.3.1" // The native `tcl` (toolchain-lite) that the PyPI wheels carry; see the cli source sets below. id("org.graalvm.buildtools.native") version "0.10.6" } +// Maven Central requires `-sources.jar` and `-javadoc.jar` beside the main jar (toolchain#97, #116). +java { + withSourcesJar() + withJavadocJar() +} + // packpack's coordinate, from gradle.properties (#99). val packpack = "${providers.gradleProperty("packpackModule").get()}:${providers.gradleProperty("packpackVersion").get()}" @@ -64,8 +67,6 @@ dependencies { } gradlePlugin { - website.set("https://thisisthepy.github.io/toolchain/") - vcsUrl.set("https://github.com/thisisthepy/toolchain") plugins { create("toolchain") { // The id comes from gradle.properties (#99); the class is a Kotlin package, not a coordinate. @@ -75,7 +76,6 @@ gradlePlugin { description = "Python in Kotlin Multiplatform apps: dependencies per target, bundling and staging of the " + "app's Python, through pypackpack." - tags.set(listOf("python", "kotlin-multiplatform", "android", "ios", "packaging")) } } } @@ -114,8 +114,8 @@ tasks.withType().configureEach // plugin id (resolved via the marker) and get the jar through `pluginMaven` -- so it was pure // duplication, not an intentional second artifact. Removed rather than re-coordinated: there was // no second artifact to keep. -// Maven Central and the Plugin Portal read the same POM fields (toolchain#97); both publications, the -// plugin's jar (`pluginMaven`) and its marker, carry them. +// Both publications, the plugin's jar (`pluginMaven`) and its marker (`:.gradle.plugin`), +// carry the POM fields Maven Central requires (toolchain#97, #116). publishing { publications.withType().configureEach { pom { @@ -153,11 +153,23 @@ publishing { } } +// The upload format of the Central Portal (https://central.sonatype.org/publish/publish-portal-upload/): +// the staged repository layout (both the plugin artifact and its `.gradle.plugin` marker), zipped, +// without Gradle's maven-metadata files. Built, never uploaded here. +tasks.register("centralBundle") { + group = "publishing" + description = "Zips build/staging-deploy into the bundle the Sonatype Central Portal accepts." + dependsOn("publishAllPublicationsToStagingRepository") + from(layout.buildDirectory.dir("staging-deploy")) { exclude("**/maven-metadata*") } + archiveFileName.set("central-bundle-${project.version}.zip") + destinationDirectory.set(layout.buildDirectory.dir("central")) +} + // Signs only when a key is given (`-PsigningInMemoryKey`, `-PsigningInMemoryKeyPassword`, or the -// ORG_GRADLE_PROJECT_ environment variables of the same names), as pypackpack does. +// ORG_GRADLE_PROJECT_ environment variables of the same names), as pypackpack does, so a local +// `publishToMavenLocal` works without one. signing { val key = providers.gradleProperty("signingInMemoryKey").orNull - // plugin-publish signs every publication once `signing` is applied; without a key, skip instead of failing. isRequired = key != null if (key != null) { useInMemoryPgpKeys(key, providers.gradleProperty("signingInMemoryKeyPassword").orNull) diff --git a/usage-example/settings.gradle.kts b/usage-example/settings.gradle.kts index e1f7cfe..a065f3c 100644 --- a/usage-example/settings.gradle.kts +++ b/usage-example/settings.gradle.kts @@ -1,16 +1,10 @@ // usage-example/ is its own Gradle build (toolchain#22, #62), run with the repository's wrapper: // ./gradlew -p usage-example …. It consumes python-multiplatform, which is built with Kotlin 2.4, so it uses // that toolchain (gradle/libs.versions.toml here); the plugin in ../toolchain stays on -// its own Kotlin for its consumers. Both plugins and the -// python-multiplatform library come from mavenLocal: publish them first (see AGENTS.md Β§16). +// its own Kotlin for its consumers. Plugins and libraries resolve from Maven Central once published, +// and fall back to mavenLocal while unreleased (publish them locally first; see AGENTS.md Β§16). pluginManagement { repositories { - mavenLocal { - content { - excludeGroupAndSubgroups("org.thisisthepy.compose") - excludeGroupAndSubgroups("org.thisisthepy.kotlin") - } - } google { mavenContent { includeGroupAndSubgroups("androidx") @@ -28,17 +22,18 @@ pluginManagement { } } gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositories { mavenLocal { content { + includeGroupAndSubgroups("org.thisisthepy") excludeGroupAndSubgroups("org.thisisthepy.compose") excludeGroupAndSubgroups("org.thisisthepy.kotlin") } } + } +} + +dependencyResolutionManagement { + repositories { google { mavenContent { includeGroupAndSubgroups("androidx") @@ -55,6 +50,13 @@ dependencyResolutionManagement { mavenPom() } } + mavenLocal { + content { + includeGroupAndSubgroups("org.thisisthepy") + excludeGroupAndSubgroups("org.thisisthepy.compose") + excludeGroupAndSubgroups("org.thisisthepy.kotlin") + } + } } }