diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d38f66a..2bc3581 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,5 +1,8 @@ # Contributing +## Commit messages +Before writing a commit message read [this article](https://chris.beams.io/posts/git-commit/). + ## Build Before pushing any changes make sure project builds without errors with: `./gradlew build` @@ -15,20 +18,11 @@ It can be also generated in IDE or via command line with `./gradlew build covera ## Validate changes locally Before submitting a pull request test your changes locally on a sample project. -You can test locally by publishing this library to maven local repository with -`./gradlew publishToMavenLocal -Pversion=...`. +There are few ways for local testing: -## Formatting -There are no enforced code style rules for Java and Groovy sources. -Just please use IntelliJ code styles from "Project scheme" (`.idea/codeStyles`). - -Kotlin codestyle is enforced by [Ktlint](https://pinterest.github.io/ktlint/). -Ktlint rules are already propagated to `.idea/codeStyles`. -You can validate Kotlin code style in command line with -`./gradlew ktlintCheck`. - -## Commit messages -Before writing a commit message read [this article](https://chris.beams.io/posts/git-commit/). +- simply use one of the [sample subprojects](https://github.com/coditory/quark-context/tree/master/samples) +- or publish library to maven local repository with `./gradlew publishToMavenLocal` and use it in any project + via [`mavenLocal()`](https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:maven_local) repository ## Validating with snapshot release Snapshot release is triggered manually by code owners. @@ -42,4 +36,14 @@ repositories { url = URI("https://oss.sonatype.org/content/repositories/snapshots") } } -``` \ No newline at end of file +``` + +The snapshot version can be found in GitHub Action build log. + +## Formatting +There are no enforced code style rules for Java and Groovy sources. +Just please use IntelliJ code styles from "Project scheme" (`.idea/codeStyles`). + +## Documentation +If change adds new feature or modifies a new one +update [documentation](https://github.com/coditory/quark-context/tree/master/samples). \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 9ff1854..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [coditory] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5a4fd1d..6ea7936 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,8 +22,7 @@ labels: bug ## Your Environment + * Library version: * Java (and/or Kotlin) version: -* Gradle version: -* Gradle scan link (add `--scan` option when running the gradle task): * Link to your project (if it's a public repository): diff --git a/.github/workflows/build-report.yml b/.github/workflows/build-report.yml deleted file mode 100644 index 1980698..0000000 --- a/.github/workflows/build-report.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Build report - -on: - workflow_run: - workflows: [build] - types: - - completed - -jobs: - report: - runs-on: ubuntu-latest - if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ github.event.workflow_run.head_sha }} - - - name: Download build reports - uses: actions/github-script@v6 - with: - script: | - let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { - return artifact.name == "build-reports" - })[0]; - let download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - let fs = require('fs'); - fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/build-reports.zip`, Buffer.from(download.data)); - - - name: Unzip build reports - run: unzip build-reports.zip - - - name: Publish test report - uses: mikepenz/action-junit-report@v3 - continue-on-error: true - with: - commit: ${{ github.event.workflow_run.head_sha }} - annotations_limit: 10 - report_paths: '**/build/test-results/**/TEST*.xml' - - - name: Publish coverage report - uses: codecov/codecov-action@v3 - continue-on-error: true - with: - token: ${{ secrets.CODECOV_TOKEN }} - override_commit: ${{ github.event.workflow_run.head_sha }} - override_branch: ${{ github.event.workflow_run.head_branch }} - override_build: ${{ github.event.workflow_run.id }} - files: 'build/reports/jacoco/coverage/coverage.xml' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ec1281..2d9a04c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,37 +1,113 @@ name: Build -on: [push, pull_request, workflow_dispatch] +on: + pull_request: + workflow_dispatch: + push: + tags: + - 'v*' + branches-ignore: + - 'dependabot/**' + - 'gh-pages' jobs: build: runs-on: ubuntu-latest timeout-minutes: 15 if: | - github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + (github.event_name != 'pull_request' && ! github.event.pull_request.head.repo.fork) + || (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.fork || startsWith(github.head_ref, 'dependabot/'))) steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - - name: Setup jdk - uses: actions/setup-java@v3 + - name: Setup JDK + uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 cache: gradle distribution: temurin - - name: Build with gradle + - name: Build run: ./gradlew build coverage - - name: Upload build reports - uses: actions/upload-artifact@v3 - if: always() + - name: Publish Test Report + if: success() || failure() # run this step even if previous step failed + continue-on-error: true + uses: dorny/test-reporter@v1 + with: + name: test report + path: ./**/build/test-results/test/*.xml + reporter: java-junit + + - name: Publish Coverage Report + uses: codecov/codecov-action@v3 + if: github.repository == 'coditory/quark-context' && github.ref == 'refs/heads/main' + continue-on-error: true + with: + token: ${{ secrets.CODECOV_TOKEN }} + override_commit: ${{ github.event.workflow_run.head_sha }} + override_branch: ${{ github.event.workflow_run.head_branch }} + override_build: ${{ github.event.workflow_run.id }} + files: 'build/reports/kover/report.xml' + + - name: Import GPG Key + id: gpg + uses: crazy-max/ghaction-import-gpg@v6 + if: | + github.repository == 'coditory/quark-context' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + git_committer_name: Coditory Bot + git_committer_email: bot@coditory.com + + - name: Publish Snapshot + if: github.repository == 'coditory/quark-context' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + env: + SIGNING_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + run: ./gradlew publishToSonatype version + + - name: Publish Release + id: publish-release + if: github.repository == 'coditory/quark-context' && startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch') + env: + SIGNING_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + OSSRH_STAGING_PROFILE_ID: ${{ secrets.OSSRH_STAGING_PROFILE_ID }} + run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository version -Pversion=${GITHUB_REF_NAME:1} + + - name: Generate Release Notes + id: generate-release-notes + if: steps.publish-release.conclusion == 'success' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + declare -r NOTES="$(gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/releases/generate-notes \ + -f target_commitish='main' \ + | jq -r '.body')" + declare -r ESCAPED="${NOTES//$'\n'/'%0A'}" + echo "notes=$ESCAPED" >> $GITHUB_OUTPUT + + - name: Create github release + if: steps.generate-release-notes.conclusion == 'success' + uses: ncipollo/release-action@v1 with: - name: build-reports - retention-days: 7 - path: | - **/build/test-results/*/TEST-*.xml - **/build/reports/jacoco/*/*.xml + allowUpdates: true + body: ${{ steps.notes.outputs.notes }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 73d56db..b2167c9 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,6 +9,6 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 97b2d59..80eeb22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,48 +3,29 @@ name: Release on: workflow_dispatch: inputs: + section: + type: choice + description: Create release tag by incrementing sem-ver section. + options: + - PATCH + - MINOR + - MAJOR + - RERUN + - MANUAL + required: true + default: PATCH version: type: string description: | - Release version in semantic format (like: 1.2.3). - Default: a version with incremented patch number. + Manually setup version (like: 1.2.3). required: false - publish: - type: choice - description: Artifact publication. - options: - - AUTO - - SKIP - - RELEASE - - SNAPSHOT - required: true - default: AUTO - release: - types: [published] jobs: release: runs-on: ubuntu-latest steps: - - name: Validate build succeeded - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - declare -r BUILD_SUCCESS="$(gh api \ - -H "Accept: application/vnd.github+json" \ - /repos/${{ github.repository }}/actions/runs?status=success\&head_sha=${{ github.sha }} \ - | jq 'limit(1; .workflow_runs[] | select(.name == "Build" and .conclusion == "success"))')" - declare -r LAST_AUTHOR="$(gh api \ - -H "Accept: application/vnd.github+json" \ - /repos/${{ github.repository }}/commits/${{ github.sha }} \ - | jq -r '.author.login')" - if [ "$LAST_AUTHOR" != "coditory-bot" ] && [ -z "$BUILD_SUCCESS" ]; then - echo "Last commit did not pass Build!" - exit 1 - fi - - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.CI_TOKEN }} @@ -52,127 +33,43 @@ jobs: - name: Get versions id: versions env: - NEXT_INPUT_VERSION: ${{ inputs.version }} - TAG_NAME: ${{ github.event.release.tag_name }} + MANUAL_VERSION: ${{ inputs.version }} + INCREMENT_SECTION: ${{ inputs.section }} run: | - declare -r GIT_VERSION="$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | sort -V | tail -n 1 | cut -c2-)" + declare -r GIT_VERSION="$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1 | cut -c2-)" declare -r VERSION=${GIT_VERSION:-0.0.0} declare -r MAJOR="$(echo "$VERSION" | cut -d. -f1)" declare -r MINOR="$(echo "$VERSION" | cut -d. -f2)" declare -r PATCH="$(echo "$VERSION" | cut -d. -f3)" - declare -r NEXT_TAG_VERSION="$([[ "$TAG_NAME" =~ v.* ]] \ - && (echo "$TAG_NAME" | cut -c2-) \ - || echo "$TAG_NAME")" - declare -r NEXT_MANUAL_VERSION="${NEXT_INPUT_VERSION:-$NEXT_TAG_VERSION}" - declare -r NEXT_PATCH_VERSION="$MAJOR.$MINOR.$(( $PATCH + 1 ))" - declare -r NEXT_VERSION="${NEXT_MANUAL_VERSION:-$NEXT_PATCH_VERSION}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT - echo -e "VERSION: $VERSION\nNEXT_VERSION: $NEXT_VERSION" - - - name: Import gpg key (release only) - id: gpg - uses: crazy-max/ghaction-import-gpg@v5 - if: | - github.event_name != 'release' - && github.ref == 'refs/heads/master' - && inputs.publish != 'SNAPSHOT' - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - git_user_signingkey: true - git_commit_gpgsign: true - git_committer_name: Coditory Bot - git_committer_email: bot@coditory.com - - - name: Update version in readme (release only) - if: steps.gpg.conclusion == 'success' - env: - PREV_VERSION: ${{ steps.versions.outputs.version }} - NEXT_VERSION: ${{ steps.versions.outputs.next_version }} - run: | - declare -r ESC_PREV_VERSION="${PREV_VERSION//./\\.}" - echo "Changing: $PREV_VERSION -> $NEXT_VERSION" - sed -i "s|${ESC_PREV_VERSION}|${NEXT_VERSION}|" README.md - sed -i "s|${ESC_PREV_VERSION}|${NEXT_VERSION}|" gradle.properties - if [ -n "$(git status --porcelain)" ]; then - git add -A - git commit -a -m "Update version $PREV_VERSION -> $NEXT_VERSION" -m "[ci skip]" - git push origin master + declare NEXT_VERSION="" + if [ "$INCREMENT_SECTION" == "MANUAL" ]; then + if [ "$MANUAL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]; then + NEXT_VERSION="$MANUAL_VERSION" + else + echo "Invalid manual version: $MANUAL_VERSION" >&2 + return 1 + fi + elif [ "$INCREMENT_SECTION" == "PATCH" ]; then + NEXT_VERSION="$MAJOR.$MINOR.$(( PATCH + 1 ))" + elif [ "$INCREMENT_SECTION" == "MINOR" ]; then + NEXT_VERSION="$MAJOR.$(( MINOR + 1 )).0" + elif [ "$INCREMENT_SECTION" == "MAJOR" ]; then + NEXT_VERSION="$(( MAJOR + 1 )).0.0" + elif [ "$INCREMENT_SECTION" == "RERUN" ]; then + NEXT_VERSION="$VERSION" + git tag -d "v$NEXT_VERSION" || echo "Local tag does not exist: v$NEXT_VERSION" + git push --delete origin "v$NEXT_VERSION" || echo "Remote tag does not exist: v$NEXT_VERSION" else - echo "Nothing changed. Skipping commit." + echo "Unrecognized option: $INCREMENT_SECTION" >&2 + return 1 fi + echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo -e "VERSION: $VERSION\nNEXT_VERSION: $NEXT_VERSION" - - name: Setup jdk - if: inputs.publish != 'SKIP' - uses: actions/setup-java@v3 - with: - java-version: 17 - cache: gradle - distribution: temurin - - - name: Publish release (release only) - if: | - github.event_name == 'release' - || inputs.publish == 'RELEASE' - || (inputs.publish == 'AUTO' && github.ref == 'refs/heads/master') - env: - NEXT_VERSION: ${{ steps.versions.outputs.next_version }} - SIGNING_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }} - NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} - NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} - run: | - ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -Pversion=$NEXT_VERSION -Ppublish --info - echo "Published release: $NEXT_VERSION" - - - name: Generate release notes (release only) - id: notes - if: | - github.event_name != 'release' - && github.ref == 'refs/heads/master' - && inputs.publish != 'SNAPSHOT' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PREV_VERSION: ${{ steps.versions.outputs.version }} - NEXT_VERSION: ${{ steps.versions.outputs.next_version }} - run: | - declare -r NOTES="$(gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - /repos/${{ github.repository }}/releases/generate-notes \ - -f tag_name="v$NEXT_VERSION" \ - -f target_commitish='master' \ - -f previous_tag_name="v$PREV_VERSION" \ - | jq -r '.body')" - declare -r ESCAPED="${NOTES//$'\n'/'%0A'}" - echo "notes=$ESCAPED" >> $GITHUB_OUTPUT - - - name: Create github release (release only) - if: steps.notes.conclusion == 'success' - uses: ncipollo/release-action@v1 - with: - allowUpdates: true - body: ${{ steps.notes.outputs.notes }} - draft: ${{ inputs.publish == 'SKIP' }} - tag: v${{ steps.versions.outputs.next_version }} - token: ${{ secrets.GITHUB_TOKEN }} - artifacts: "build/libs/*.jar" - - - name: Publish snapshot (snapshot only) - if: | - github.event_name == 'workflow_dispatch' - && ( - inputs.publish == 'SNAPSHOT' - || inputs.publish == 'AUTO' && github.ref != 'refs/heads/master' - ) + - name: Create Release Tag env: NEXT_VERSION: ${{ steps.versions.outputs.next_version }} - SIGNING_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }} - NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} - NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} run: | - declare -r MIDDLE="$([ "$GITHUB_REF" == "refs/heads/master" ] && echo "" || echo "-${GITHUB_REF#refs/heads/}")" - ./gradlew publishToSonatype -Pversion="${NEXT_VERSION}${MIDDLE}-SNAPSHOT" -Ppublish - echo "Published snapshot: ${NEXT_VERSION}${MIDDLE}-SNAPSHOT" + git tag "v$NEXT_VERSION" + git push origin "v$NEXT_VERSION" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 369048b..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Stale -on: - schedule: - - cron: '30 1 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v7 - with: - # PRs - stale-pr-message: 'This PR is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 10 days.' - days-before-pr-stale: 120 - close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' - days-before-pr-close: 10 - exempt-all-pr-assignees: true - exempt-pr-labels: 'awaiting-approval,work-in-progress' - stale-pr-label: 'stale' - # Issues - stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days.' - days-before-issue-stale: 60 - close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' - days-before-issue-close: 5 - exempt-issue-assignees: true - exempt-issue-labels: 'awaiting-approval,work-in-progress' - stale-issue-label: 'stale' diff --git a/README.md b/README.md index 9907f5b..d3eb0a2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Add to your `build.gradle`: ```gradle dependencies { - implementation "com.coditory.quark:quark-context:0.1.15" + implementation "com.coditory.quark:quark-context:$version" } ``` diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 0f8339a..4b9236e 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,5 +1,6 @@ plugins { `kotlin-dsl` + kotlin("jvm") version embeddedKotlinVersion } repositories { diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 75f202c..f4b21e6 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1,7 +1,6 @@ rootProject.name = "build-logic" dependencyResolutionManagement { - @Suppress("UnstableApiUsage") versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) diff --git a/build-logic/src/main/kotlin/build.coverage.gradle.kts b/build-logic/src/main/kotlin/build.coverage.gradle.kts index ea99391..a0e1ecf 100644 --- a/build-logic/src/main/kotlin/build.coverage.gradle.kts +++ b/build-logic/src/main/kotlin/build.coverage.gradle.kts @@ -26,7 +26,7 @@ tasks.withType { tasks.register("coverage") { description = "Creates combined coverage report" - executionData(fileTree(project.buildDir).include("jacoco/*.exec")) + executionData(fileTree(project.layout.buildDirectory).include("jacoco/*.exec")) sourceSets(project.extensions.getByType(JavaPluginExtension::class).sourceSets.getByName("main")) dependsOn(tasks.findByName("test"), tasks.findByName("integrationTest")) reports { diff --git a/build-logic/src/main/kotlin/build.java.gradle.kts b/build-logic/src/main/kotlin/build.java.gradle.kts index 350278e..e8934a6 100644 --- a/build-logic/src/main/kotlin/build.java.gradle.kts +++ b/build-logic/src/main/kotlin/build.java.gradle.kts @@ -9,6 +9,8 @@ java { toolchain { languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get())) } + withJavadocJar() + withSourcesJar() } tasks.withType().configureEach { diff --git a/build-logic/src/main/kotlin/build.kotlin.gradle.kts b/build-logic/src/main/kotlin/build.kotlin.gradle.kts new file mode 100644 index 0000000..16794a2 --- /dev/null +++ b/build-logic/src/main/kotlin/build.kotlin.gradle.kts @@ -0,0 +1,20 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("build.java") + kotlin("jvm") +} + +val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) + +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get())) + } +} + +tasks.withType().configureEach { + kotlinOptions { + allWarningsAsErrors = true + } +} diff --git a/build-logic/src/main/kotlin/build.publish.gradle.kts b/build-logic/src/main/kotlin/build.publish.gradle.kts deleted file mode 100644 index 963a0a4..0000000 --- a/build-logic/src/main/kotlin/build.publish.gradle.kts +++ /dev/null @@ -1,93 +0,0 @@ -import java.time.Duration - -plugins { - `java-library` - `maven-publish` - signing - id("io.github.gradle-nexus.publish-plugin") -} - -// creating publishable jar introduces time overhead -// add "publish" property to enable signing and javadoc and sources in the jar -// ./gradlew ... -Ppublish -// ...or with a task -// ./gradlew ... coverage -val publishEnabled = (project.hasProperty("publish") && project.properties["publish"] != "false") || - project.gradle.startParameter.taskNames.contains("publishToSonatype") || - project.gradle.startParameter.taskNames.contains("publishToMavenLocal") - -java { - if (publishEnabled) { - withSourcesJar() - withJavadocJar() - } -} - -group = "com.coditory.quark" -description = "Coditory Quark Context - dependency injection context" - -publishing { - publications { - create("maven") { - groupId = project.group.toString() - artifactId = project.name - from(components["java"]) - - pom { - name.set(project.name) - description.set(project.description) - url.set("https://github.com/coditory/quark-i18n") - organization { - name.set("Coditory") - url.set("https://coditory.com") - } - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - distribution.set("repo") - } - } - developers { - developer { - id.set("coditory") - name.set("Coditory") - email.set("admin@coditory.com") - } - } - scm { - connection.set("scm:git@github.com:coditory/quark-i18n.git") - developerConnection.set("scm:git@github.com:coditory/quark-i18n.git") - url.set("https://github.com/coditory/quark-i18n") - } - issueManagement { - system.set("GitHub") - url.set("https://github.com/coditory/quark-i18n/issues") - } - } - } - } -} - -if (publishEnabled && !project.gradle.startParameter.taskNames.contains("publishToMavenLocal")) { - val signingKey: String? = System.getenv("SIGNING_KEY") - val signingPwd: String? = System.getenv("SIGNING_PASSWORD") - if (signingKey.isNullOrBlank() || signingPwd.isNullOrBlank()) { - logger.info("Signing disabled as the GPG key was not found. Define SIGNING_KEY and SIGNING_PASSWORD to enable.") - } - signing { - useInMemoryPgpKeys(signingKey, signingPwd) - sign(publishing.publications["maven"]) - } -} - -nexusPublishing { - connectTimeout.set(Duration.ofMinutes(5)) - clientTimeout.set(Duration.ofMinutes(5)) - repositories { - sonatype { - System.getenv("NEXUS_USERNAME")?.let { username.set(it) } - System.getenv("NEXUS_PASSWORD")?.let { password.set(it) } - } - } -} diff --git a/build-logic/src/main/kotlin/build.publishing.gradle.kts b/build-logic/src/main/kotlin/build.publishing.gradle.kts new file mode 100644 index 0000000..0e93b23 --- /dev/null +++ b/build-logic/src/main/kotlin/build.publishing.gradle.kts @@ -0,0 +1,79 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName + +plugins { + `java-library` + `maven-publish` + signing + id("io.github.gradle-nexus.publish-plugin") +} + +group = "com.coditory.quark" +description = "Quark internationalization library" + +publishing { + publications.create("jvm") { + artifactId = project.archivesName.get() + from(components["java"]) + versionMapping { + usage("java-api") { + fromResolutionOf("runtimeClasspath") + } + usage("java-runtime") { + fromResolutionResult() + } + } + pom { + name.set(project.archivesName.get()) + description.set(project.description ?: rootProject.description) + url.set("https://github.com/coditory/quark-i18n") + organization { + name = "Coditory" + url = "https://coditory.com" + } + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + id.set("ogesaku") + name.set("ogesaku") + email.set("ogesaku@gmail.com") + } + } + scm { + connection.set("scm:git:git://github.com/coditory/quark-i18n.git") + url.set("https://github.com/coditory/quark-i18n") + } + issueManagement { + system.set("GitHub") + url.set("https://github.com/coditory/quark-i18n/issues") + } + } + } +} + +signing { + if (System.getenv("SIGNING_KEY")?.isNotBlank() == true && System.getenv("SIGNING_PASSWORD")?.isNotBlank() == true) { + useInMemoryPgpKeys(System.getenv("SIGNING_KEY"), System.getenv("SIGNING_PASSWORD")) + } + sign(publishing.publications["jvm"]) +} + +tasks.javadoc { + if (JavaVersion.current().isJava9Compatible) { + (options as StandardJavadocDocletOptions).addBooleanOption("html5", true) + } +} + +nexusPublishing { + repositories { + sonatype { + System.getenv("OSSRH_STAGING_PROFILE_ID")?.let { stagingProfileId = it } + System.getenv("OSSRH_USERNAME")?.let { username.set(it) } + System.getenv("OSSRH_PASSWORD")?.let { password.set(it) } + } + } +} diff --git a/build-logic/src/main/kotlin/build.test.gradle.kts b/build-logic/src/main/kotlin/build.test.gradle.kts index c4c4e1a..77506fa 100644 --- a/build-logic/src/main/kotlin/build.test.gradle.kts +++ b/build-logic/src/main/kotlin/build.test.gradle.kts @@ -1,15 +1,19 @@ -@file:Suppress("UnstableApiUsage", "HasPlatformType", "UNUSED_VARIABLE") +@file:Suppress("UnstableApiUsage", "HasPlatformType") import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent plugins { - id("java-library") + id("build.java") id("groovy") } val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) +dependencies { + testImplementation(libs.groovy) +} + testing { suites { val test by getting(JvmTestSuite::class) diff --git a/build-logic/src/main/kotlin/build.version.gradle.kts b/build-logic/src/main/kotlin/build.version.gradle.kts new file mode 100644 index 0000000..9fce542 --- /dev/null +++ b/build-logic/src/main/kotlin/build.version.gradle.kts @@ -0,0 +1,102 @@ +import java.io.IOException + +require(project == rootProject) { "build.version is applicable to rootProject only" } + +val textVersion = project.version.toString() +if (textVersion == "unspecified") { + version = Semver.fromGitTag().nextPatchSnapshot().toString() +} else { + Semver.parse(textVersion) ?: "Could not parse project version: $textVersion" +} + +allprojects { + version = rootProject.version +} + +tasks.register("version") { + doLast { + println(project.version) + } +} + +data class Semver( + private val major: Int, + private val minor: Int, + private val patch: Int, + private val suffix: String? = null, +) : Comparable { + override fun compareTo(other: Semver): Int { + if (major > other.major) return 1 + if (major < other.major) return -1 + if (minor > other.minor) return 1 + if (minor < other.minor) return -1 + if (patch > other.patch) return 1 + if (patch < other.patch) return -1 + if (suffix != null && other.suffix == null) return 1 + if (suffix == null && other.suffix != null) return -1 + if (suffix != null && other.suffix != null) return suffix.compareTo(other.suffix) + return 0 + } + + fun nextPatchSnapshot(): Semver { + return copy( + patch = patch + 1, + suffix = "SNAPSHOT" + ) + } + + override fun toString(): String { + return if (suffix.isNullOrEmpty()) { + "$major.$minor.$patch" + } else { + "$major.$minor.$patch-$suffix" + } + } + + fun tagName(): String { + return "v${toString()}" + } + + companion object { + private val REGEX = Regex("v?([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.+)?") + + fun parse(text: String, strict: Boolean = true): Semver? { + val groups = REGEX.matchEntire(text)?.groups ?: return null + if (groups.size < 4 || groups.size > 5) return null + if (strict && groups[0]?.value?.startsWith("v") == true) return null + return Semver( + major = groups[1]?.value?.toIntOrNull() ?: return null, + minor = groups[2]?.value?.toIntOrNull() ?: return null, + patch = groups[3]?.value?.toIntOrNull() ?: return null, + suffix = groups[4]?.value?.trimStart('-'), + ) + } + + fun fromGitTag(): Semver { + return runCommand("git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1") + .split('\n') + .mapNotNull { parse(it, strict = false) } + .minOrNull() + ?: Semver(0, 0, 0) + } + + private fun runCommand( + command: String, + workingDir: File = File("."), + timeoutAmount: Long = 60, + timeoutUnit: TimeUnit = TimeUnit.SECONDS + ): String = ProcessBuilder("sh", "-c", command) + .directory(workingDir) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + .apply { waitFor(timeoutAmount, timeoutUnit) } + .run { + val error = errorStream.bufferedReader().readText().trim() + if (error.isNotEmpty()) { + throw IOException(error) + } + inputStream.bufferedReader().readText().trim() + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 39ff8e4..068160f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,10 @@ plugins { + id("build.version") id("build.java") + id("build.kotlin") id("build.test") id("build.coverage") - id("build.publish") + id("build.publishing") } dependencies { diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 1a3b60e..0000000 --- a/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -version=0.1.15 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0372f2..39121e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,18 +1,20 @@ [versions] # build tools -java = "17" -jacoco = "0.8.8" +java = "21" +jacoco = "0.8.11" kotlin = "1.9.22" +groovy = "4.0.18" [libraries] # build tools kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } gradle-nexus-publish = { module = "io.github.gradle-nexus:publish-plugin", version = "1.1.0" } +groovy = { module = "org.apache.groovy:groovy", version.ref = "groovy" } # dependencies slf4j-api = { module = "org.slf4j:slf4j-api", version = "2.0.11" } jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.1.0" } quark-eventbus = { module = "com.coditory.quark:quark-eventbus", version = "0.0.5" } # test dependencies -spock-core = { module = "org.spockframework:spock-core", version = "2.3-groovy-4.0" } +spock-core = { module = "org.spockframework:spock-core", version = "2.4-M1-groovy-4.0" } logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.4.14" } jsonassert = { module = "org.skyscreamer:jsonassert", version = "1.5.1" } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ae83b9e..3cfd35a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,10 +3,11 @@ rootProject.name = "quark-context" includeBuild("build-logic") plugins { - id("com.gradle.enterprise").version("3.12.2") + id("com.gradle.enterprise").version("3.15.1") } dependencyResolutionManagement { + @Suppress("UnstableApiUsage") repositories { mavenCentral() } diff --git a/src/main/java/com/coditory/quark/context/ClassPath.java b/src/main/java/com/coditory/quark/context/ClassPath.java index cf6c08a..11ca3df 100644 --- a/src/main/java/com/coditory/quark/context/ClassPath.java +++ b/src/main/java/com/coditory/quark/context/ClassPath.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; @@ -339,9 +340,11 @@ private static List parseJavaClassPath() { try { urls.add(new File(entry).toURI().toURL()); } catch (SecurityException e) { // File.toURI checks to see if the file is a directory - urls.add(new URL("file", null, new File(entry).getAbsolutePath())); + // File.toURI checks to see if the file is a directory + URI uri = new URI("file", null, new File(entry).getAbsolutePath(), null); + urls.add(uri.toURL()); } - } catch (MalformedURLException e) { + } catch (MalformedURLException|URISyntaxException e) { logger.warn("Malformed classpath entry: " + entry, e); } } @@ -349,7 +352,7 @@ private static List parseJavaClassPath() { } private static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException { - return new URL(jarFile.toURI().toURL(), path); + return jarFile.toURI().resolve(path).toURL(); } private static String getClassName(String filename) {