diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73f8143..8713def 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,16 @@ on: push: branches: - master + # Documentation only changes (README, badges, screenshots) do not need a build or a release + paths-ignore: + - '**.md' + - 'art/**' pull_request: branches: - master + paths-ignore: + - '**.md' + - 'art/**' workflow_dispatch: # Allows manual trigger of the workflow permissions: @@ -22,6 +29,7 @@ env: TEST_JAVA_VERSION: '21' # Stages: Resolve Version -> Test (lowest and latest Android SDK) -> Build library & sample app -> Tag & Release +# -> Publish to GitHub Packages and Maven Central # The Test report job publishes the JUnit results of every SDK, also when the tests fail. # Every stage must succeed for the next one to start. jobs: @@ -166,10 +174,15 @@ jobs: echo "ANDROID_KEYSTORE_FILE=$RUNNER_TEMP/release.keystore" >> "$GITHUB_ENV" - name: Build + env: + # Sign the Maven artifacts outside pull requests, when a key is configured + ORG_GRADLE_PROJECT_signingKey: ${{ github.event_name != 'pull_request' && secrets.SIGNING_KEY || '' }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} run: | ./gradlew \ :flowlayoutmanager:assembleRelease \ :flowlayoutmanager:publishToMavenLocal \ + :flowlayoutmanager:publishReleasePublicationToStagingRepository \ :app:assembleDebug \ :app:assembleRelease @@ -196,6 +209,15 @@ jobs: path: dist/* if-no-files-found: error + # Maven repository layout (AAR, sources, javadoc, POM and signatures), + # published as is to GitHub Packages and Maven Central + - name: Upload Maven artifacts + uses: actions/upload-artifact@v7 + with: + name: maven-${{ env.VERSION }} + path: flowlayoutmanager/build/staging-deploy/ + if-no-files-found: error + release: name: Tag & Release runs-on: ubuntu-latest @@ -218,6 +240,12 @@ jobs: name: flowlayoutmanager-${{ env.VERSION }} path: release + - name: Download Maven artifacts + uses: actions/download-artifact@v8 + with: + name: maven-${{ env.VERSION }} + path: maven + - name: Checksums run: | cd release @@ -227,7 +255,7 @@ jobs: - name: Attest build provenance uses: actions/attest-build-provenance@v4 with: - subject-path: 'release/*.aar, release/*.apk' + subject-path: 'release/*.aar, release/*.apk, maven/**/*.aar, maven/**/*.jar, maven/**/*.pom' - name: Tag release id: tag @@ -267,3 +295,91 @@ jobs: -f state="$STATE" \ -f description="$DESCRIPTION" \ -f context="release" + + publish-github: + name: Publish to GitHub Packages + runs-on: ubuntu-latest + needs: [version, release] + if: needs.version.outputs.tag_exists != 'true' + permissions: + packages: write + env: + VERSION: ${{ needs.version.outputs.version }} + + steps: + - name: Download Maven artifacts + uses: actions/download-artifact@v8 + with: + name: maven-${{ env.VERSION }} + path: maven + + # Same files attested in the release job, uploaded as Gradle does: + # artifacts first, maven-metadata.xml last. + - name: Upload to GitHub Packages + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd maven + { find . -type f ! -name 'maven-metadata.xml*' | sort; find . -type f -name 'maven-metadata.xml*' | sort; } > "$RUNNER_TEMP/files" + while read -r FILE; do + echo "Uploading ${FILE#./}" + curl --fail-with-body -sS -u "${{ github.actor }}:$GH_TOKEN" --upload-file "$FILE" \ + "https://maven.pkg.github.com/${{ github.repository }}/${FILE#./}" + done < "$RUNNER_TEMP/files" + + publish-central: + name: Publish to Maven Central + runs-on: ubuntu-latest + needs: [version, release] + if: needs.version.outputs.tag_exists != 'true' + env: + VERSION: ${{ needs.version.outputs.version }} + CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + + steps: + - name: Download Maven artifacts + uses: actions/download-artifact@v8 + with: + name: maven-${{ env.VERSION }} + path: maven + + # Central Portal API: https://central.sonatype.org/publish/publish-portal-api/ + - name: Upload to Maven Central + run: | + if [ -z "$CENTRAL_USERNAME" ] || [ -z "$CENTRAL_PASSWORD" ]; then + echo "::warning::MAVEN_CENTRAL_USERNAME and MAVEN_CENTRAL_PASSWORD not configured - skip Maven Central" + exit 0 + fi + if ! find maven -name '*.asc' | grep -q .; then + echo "::error::Maven artifacts are not signed - configure SIGNING_KEY and SIGNING_PASSWORD" + exit 1 + fi + + # Maven Central manages maven-metadata.xml itself + (cd maven && zip -qr "$RUNNER_TEMP/bundle.zip" . -x '*maven-metadata.xml*') + AUTH="Authorization: Bearer $(printf '%s:%s' "$CENTRAL_USERNAME" "$CENTRAL_PASSWORD" | base64 -w0)" + DEPLOYMENT=$(curl --fail-with-body -sS -H "$AUTH" -F "bundle=@$RUNNER_TEMP/bundle.zip" \ + "https://central.sonatype.com/api/v1/publisher/upload?name=${{ github.event.repository.name }}-$VERSION&publishingType=AUTOMATIC") + echo "Deployment $DEPLOYMENT" + + # Wait for the validation, the publication itself can take a while longer + for _ in $(seq 60); do + STATUS=$(curl --fail-with-body -sS -X POST -H "$AUTH" "https://central.sonatype.com/api/v1/publisher/status?id=$DEPLOYMENT") + STATE=$(echo "$STATUS" | jq -r .deploymentState) + echo "State: $STATE" + case "$STATE" in + PUBLISHING|PUBLISHED) + echo "### Maven Central: \`$STATE\`" >> "$GITHUB_STEP_SUMMARY" + exit 0 + ;; + FAILED) + echo "$STATUS" | jq . + echo "::error::Maven Central rejected the deployment $DEPLOYMENT" + exit 1 + ;; + esac + sleep 20 + done + echo "::error::Maven Central did not validate the deployment $DEPLOYMENT in time" + exit 1 diff --git a/README.md b/README.md index 05f8232..ace196a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # FlowLayoutManager [![CI](https://github.com/simonebortolin/FlowLayoutManager/actions/workflows/ci.yml/badge.svg)](https://github.com/simonebortolin/FlowLayoutManager/actions/workflows/ci.yml) +[![Maven Central](https://img.shields.io/maven-central/v/io.github.simonebortolin/FlowLayoutManager)](https://central.sonatype.com/artifact/io.github.simonebortolin/FlowLayoutManager) [![](https://jitpack.io/v/simonebortolin/FlowLayoutManager.svg)](https://jitpack.io/#simonebortolin/FlowLayoutManager) [![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg)](https://developer.android.com/about/versions/lollipop) @@ -15,28 +16,20 @@ FlowLayoutManager is a layoutManager that works with Android ___RecyclerView___ ## Installation - -Add remote maven url (in `settings.gradle`) +The library is published on Maven Central, add the dependency (remember to check for latest release) ```groovy - dependencyResolutionManagement { - repositories { - google() - mavenCentral() - maven { url "https://jitpack.io" } - } + dependencies { + implementation 'io.github.simonebortolin:FlowLayoutManager:1.10.0' } ``` -Every release also ships the `.aar` and the sample app `.apk` on the [Releases page](https://github.com/simonebortolin/FlowLayoutManager/releases). +It is also available on [JitPack](https://jitpack.io/#simonebortolin/FlowLayoutManager) as `com.github.simonebortolin:FlowLayoutManager` +(add `maven { url "https://jitpack.io" }` to the repositories in `settings.gradle`) and on +[GitHub Packages](https://github.com/simonebortolin/FlowLayoutManager/packages), which requires a GitHub token to download. -Now add a library dependency (remember to check for latest release) - -```groovy - dependencies { - implementation 'com.github.simonebortolin:FlowLayoutManager:1.9.0' - } -``` +Every release also ships the `.aar` and the sample app `.apk`, with their build provenance attestation, +on the [Releases page](https://github.com/simonebortolin/FlowLayoutManager/releases). Item per line limitation --- diff --git a/flowlayoutmanager/build.gradle b/flowlayoutmanager/build.gradle index 9a5dcaf..b58627b 100644 --- a/flowlayoutmanager/build.gradle +++ b/flowlayoutmanager/build.gradle @@ -1,9 +1,12 @@ plugins { alias(libs.plugins.android.library) id 'maven-publish' + id 'signing' } -group = 'com.github.simonebortolin' +// Maven Central and GitHub Packages serve the library as io.github.simonebortolin, +// JitPack (which sets JITPACK) as com.github.simonebortolin. +group = System.getenv('JITPACK') ? 'com.github.simonebortolin' : 'io.github.simonebortolin' version = VERSION_NAME android { @@ -33,6 +36,8 @@ android { publishing { singleVariant('release') { withSourcesJar() + // Required by Maven Central + withJavadocJar() } } } @@ -52,7 +57,48 @@ afterEvaluate { release(MavenPublication) { from components.release artifactId = 'FlowLayoutManager' + + // Metadata required by Maven Central + pom { + name = 'FlowLayoutManager' + description = 'A RecyclerView LayoutManager that lays out views in flow style.' + url = 'https://github.com/simonebortolin/FlowLayoutManager' + licenses { + license { + name = 'GNU General Public License v3.0' + url = 'https://www.gnu.org/licenses/gpl-3.0.txt' + } + } + developers { + developer { + id = 'simonebortolin' + name = 'Simone Bortolin' + url = 'https://github.com/simonebortolin' + } + } + scm { + url = 'https://github.com/simonebortolin/FlowLayoutManager' + connection = 'scm:git:https://github.com/simonebortolin/FlowLayoutManager.git' + developerConnection = 'scm:git:ssh://git@github.com/simonebortolin/FlowLayoutManager.git' + } + } + } + } + repositories { + // Maven layout that CI uploads to GitHub Packages and Maven Central + maven { + name = 'staging' + url = layout.buildDirectory.dir('staging-deploy') } } } + + // Signed only when CI provides the key (ORG_GRADLE_PROJECT_signingKey and signingPassword) + def signingKey = findProperty('signingKey') + if (signingKey) { + signing { + useInMemoryPgpKeys(signingKey, findProperty('signingPassword')) + sign publishing.publications.release + } + } } diff --git a/gradle.properties b/gradle.properties index c1a579a..7f32ee0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Version of the library and of the sample app, used by CI to tag and release. -VERSION_NAME=1.9.0 -VERSION_CODE=19 +VERSION_NAME=1.10.0 +VERSION_CODE=20 # Keep it as low as the dependencies allow, library and sample app share it. MIN_SDK=21