diff --git a/.gitattributes b/.gitattributes index 6225757..d03a971 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,8 @@ *.css text eol=lf *.js text eol=lf *.sql text eol=lf +*.kts text eol=lf +*.gradle.kts text eol=lf ############################### # Git Large File System (LFS) # diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..994b42b --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,9 @@ +name: Build and Test + +on: + pull_request: + branches: [main] + +jobs: + build-and-test: + uses: eclipse-keyple/keyple-actions/.github/workflows/reusable-build-and-test.yml@build-and-test-v1 diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..37809e4 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,10 @@ +name: Publish Release package + +on: + release: + types: [published] + +jobs: + publish-release: + uses: eclipse-keyple/keyple-actions/.github/workflows/reusable-publish-release.yml@publish-release-v1 + secrets: inherit diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml new file mode 100644 index 0000000..cb1a7ce --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,10 @@ +name: Publish Snapshot package + +on: + push: + branches: [main] + +jobs: + publish-snapshot: + uses: eclipse-keyple/keyple-actions/.github/workflows/reusable-publish-snapshot.yml@publish-snapshot-v1 + secrets: inherit diff --git a/.github/workflows/publish-to-keyple-doc.yml b/.github/workflows/publish-to-keyple-doc.yml deleted file mode 100644 index 2ffed0b..0000000 --- a/.github/workflows/publish-to-keyple-doc.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Check Doc Updates and Trigger Documentation Update - -on: - schedule: - - cron: '0 * * * *' # Every hour - workflow_dispatch: - -permissions: - checks: write - -jobs: - check-secret: - runs-on: ubuntu-latest - outputs: - has-token: ${{ steps.check.outputs.has-token }} - steps: - - id: check - run: | - if [ "${{ secrets.ORG_GITHUB_BOT_TOKEN }}" != "" ]; then - echo "has-token=true" >> "$GITHUB_OUTPUT" - else - echo "has-token=false" >> "$GITHUB_OUTPUT" - fi - - check-and-notify: - needs: check-secret - if: needs.check-secret.outputs.has-token == 'true' - runs-on: ubuntu-latest - steps: - - name: Check commits dates - id: check-updates - run: | - # Get last commit date from current repo's doc branch - DOC_RESPONSE=$(curl -s \ - -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${{ github.repository }}/commits/doc") - - DOC_COMMIT_DATE=$(echo $DOC_RESPONSE | jq -r '.commit.author.date') - DOC_COMMIT_TIME=$(date -d "$DOC_COMMIT_DATE" +%s) - - # Get last commit date from keypop-api-docs gh-pages branch - DOCS_RESPONSE=$(curl -s \ - -H "Authorization: token ${{ secrets.ORG_GITHUB_BOT_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/eclipse-keyple/keyple-api-docs/commits/gh-pages") - - DOCS_COMMIT_DATE=$(echo $DOCS_RESPONSE | jq -r '.commit.author.date') - DOCS_COMMIT_TIME=$(date -d "$DOCS_COMMIT_DATE" +%s) - - # Compare dates - if [ $DOC_COMMIT_TIME -gt $DOCS_COMMIT_TIME ]; then - echo "Doc branch has newer changes" - echo "Doc commit time: $(date -d @$DOC_COMMIT_TIME)" - echo "Docs commit time: $(date -d @$DOCS_COMMIT_TIME)" - echo "should-update=true" >> "$GITHUB_OUTPUT" - else - echo "No new changes to publish" - echo "Doc commit time: $(date -d @$DOC_COMMIT_TIME)" - echo "Docs commit time: $(date -d @$DOCS_COMMIT_TIME)" - echo "should-update=false" >> "$GITHUB_OUTPUT" - fi - - - name: Repository Dispatch Event - if: steps.check-updates.outputs.should-update == 'true' - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.ORG_GITHUB_BOT_TOKEN }} - repository: eclipse-keyple/keyple-api-docs - event-type: update-submodules \ No newline at end of file diff --git a/.gitignore b/.gitignore index 931858b..9dfd420 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ release/ # Gradle .gradle/ build*/ -LICENSE_HEADER # Eclipse .classpath diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c1819..19fecf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Migrated the CI pipeline from Jenkins to GitHub Actions. ## [2.3.2] - 2025-04-18 ### Changed diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index eb61336..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,94 +0,0 @@ -#!groovy -pipeline { - environment { - PROJECT_NAME = "keyple-plugin-java-api" - PROJECT_BOT_NAME = "Eclipse Keyple Bot" - } - agent { kubernetes { yaml javaBuilder('2.0') } } - stages { - stage('Import keyring') { - when { expression { env.GIT_URL.startsWith('https://github.com/eclipse-keyple/keyple-') && env.CHANGE_ID == null } } - steps { container('java-builder') { - withCredentials([file(credentialsId: 'secret-subkeys.asc', variable: 'KEYRING')]) { sh 'import_gpg "${KEYRING}"' } - } } - } - stage('Prepare settings') { steps { container('java-builder') { - script { - env.KEYPLE_VERSION = sh(script: 'grep version gradle.properties | cut -d= -f2 | tr -d "[:space:]"', returnStdout: true).trim() - env.GIT_COMMIT_MESSAGE = sh(script: 'git log --format=%B -1 | head -1 | tr -d "\n"', returnStdout: true) - env.SONAR_USER_HOME = '/home/jenkins' - echo "Building version ${env.KEYPLE_VERSION} in branch ${env.GIT_BRANCH}" - deployRelease = env.GIT_URL == "https://github.com/eclipse-keyple/${env.PROJECT_NAME}.git" && (env.GIT_BRANCH == "main" || env.GIT_BRANCH == "release-${env.KEYPLE_VERSION}") && env.CHANGE_ID == null && env.GIT_COMMIT_MESSAGE.startsWith("Release ${env.KEYPLE_VERSION}") - deploySnapshot = !deployRelease && env.GIT_URL == "https://github.com/eclipse-keyple/${env.PROJECT_NAME}.git" && (env.GIT_BRANCH == "main" || env.GIT_BRANCH == "release-${env.KEYPLE_VERSION}") && env.CHANGE_ID == null - } - sh "chmod +x ./gradlew ./scripts/*.sh" - } } } - stage('Check version') { - steps { container('java-builder') { - sh "./scripts/check_version.sh ${env.KEYPLE_VERSION}" - } } - } - stage('Build and Test') { - when { expression { !deploySnapshot && !deployRelease } } - steps { container('java-builder') { - sh './gradlew clean build test --no-build-cache --info --stacktrace' - junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true - } } - } - stage('Build and Publish Snapshot') { - when { expression { deploySnapshot } } - steps { container('java-builder') { - configFileProvider([configFile(fileId: 'gradle.properties', targetLocation: '/home/jenkins/agent/gradle.properties')]) { - sh './gradlew clean build test publish --info --stacktrace' - } - junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true - } } - } - stage('Build and Publish Release') { - when { expression { deployRelease } } - steps { container('java-builder') { - configFileProvider([configFile(fileId: 'gradle.properties', targetLocation: '/home/jenkins/agent/gradle.properties')]) { - sh './gradlew clean build test release --info --stacktrace' - } - junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true - } } - } - stage('Update GitHub Pages') { - when { expression { deploySnapshot || deployRelease } } - steps { container('java-builder') { - sh "./scripts/prepare_javadoc.sh ${env.PROJECT_NAME} ${env.KEYPLE_VERSION} ${deploySnapshot}" - dir("${env.PROJECT_NAME}") { - withCredentials([usernamePassword(credentialsId: 'github-bot', passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) { - sh ''' - git add -A - git config user.email "${PROJECT_NAME}-bot@eclipse.org" - git config user.name "${PROJECT_BOT_NAME}" - git commit --allow-empty -m "docs: update documentation ${JOB_NAME}-${BUILD_NUMBER}" - git log --graph --abbrev-commit --date=relative -n 5 - git push "https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/eclipse-keyple/${PROJECT_NAME}.git" HEAD:doc - ''' - } - } - } } - } - stage('Publish Code Quality') { - when { expression { env.GIT_URL.startsWith('https://github.com/eclipse-keyple/keyple-') } } - steps { container('java-builder') { - catchError(buildResult: 'SUCCESS', message: 'Unable to log code quality to Sonar.', stageResult: 'FAILURE') { - withCredentials([string(credentialsId: 'sonarcloud-token', variable: 'SONAR_LOGIN')]) { - sh './gradlew sonarqube --info --stacktrace' - } - } - } } - } - stage('Publish packaging to Eclipse') { - when { expression { deploySnapshot || deployRelease } } - steps { container('java-builder') { sshagent(['projects-storage.eclipse.org-bot-ssh']) { sh 'publish_packaging' } } } - } - } - post { always { container('java-builder') { - archiveArtifacts artifacts: 'build*/libs/**', allowEmptyArchive: true - archiveArtifacts artifacts: 'build*/reports/tests/**', allowEmptyArchive: true - archiveArtifacts artifacts: 'build*/reports/jacoco/test/html/**', allowEmptyArchive: true - } } } -} diff --git a/LICENSE_HEADER b/LICENSE_HEADER new file mode 100644 index 0000000..efc0ba1 --- /dev/null +++ b/LICENSE_HEADER @@ -0,0 +1,11 @@ +/* ************************************************************************************** + * Copyright (c) $YEAR Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT + * + * SPDX-License-Identifier: MIT + ************************************************************************************** */ \ No newline at end of file diff --git a/PUBLISHERS.yml b/PUBLISHERS.yml deleted file mode 100644 index b170dcb..0000000 --- a/PUBLISHERS.yml +++ /dev/null @@ -1,18 +0,0 @@ -url: https://github.com/eclipse-keyple/keyple-plugin-java-api -organization: - name: Eclipse Keyple - url: https://keyple.org/ -licenses: - - name: Eclipse Public License - v 2.0 - url: https://www.eclipse.org/legal/epl-2.0/ - distribution: repo -developers: - - name: Keyple Contributors - email: keyple-dev@eclipse.org -scm: - connection: scm:git:git://github.com/eclipse-keyple/keyple-plugin-java-api.git - developerConnection: scm:git:https://github.com/eclipse-keyple/keyple-plugin-java-api.git - url: https://github.com/eclipse-keyple/keyple-plugin-java-api -ciManagement: - system: Jenkins - url: https://ci.eclipse.org/keyple/job/Keyple/job/keyple-plugin-java-api/ diff --git a/README.md b/README.md index cea9d45..ee7d833 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,9 @@ API documentation & class diagram is available online: [docs.keyple.org/keyple-p ## About the source code -The code is built with **Gradle** and is compliant with **Java 1.8** in order to address a wide range of applications. \ No newline at end of file +The code is built with **Gradle** and is compliant with **Java 1.8** in order to address a wide range of applications. + +## Continuous Integration + +This project uses **GitHub Actions** for continuous integration. Every push and pull request triggers automated builds +and checks to ensure code quality and maintain compatibility with the defined specifications. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 87fb3d6..4eb3f56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,85 +1,193 @@ /////////////////////////////////////////////////////////////////////////////// // GRADLE CONFIGURATION /////////////////////////////////////////////////////////////////////////////// + plugins { - java - id("com.diffplug.spotless") version "6.25.0" - id("org.sonarqube") version "3.1" - jacoco -} -buildscript { - repositories { - mavenLocal() - mavenCentral() - } - dependencies { - classpath("org.eclipse.keyple:keyple-gradle:0.2.+") { isChanging = true } - } + java + `maven-publish` + signing + id("com.diffplug.spotless") version "6.25.0" } -apply(plugin = "org.eclipse.keyple") /////////////////////////////////////////////////////////////////////////////// // APP CONFIGURATION /////////////////////////////////////////////////////////////////////////////// -repositories { - mavenLocal() - mavenCentral() -} + dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.2")) - testImplementation("org.junit.jupiter:junit-jupiter-api") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") - testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation(platform("org.junit:junit-bom:5.10.2")) + testImplementation("org.junit.jupiter:junit-jupiter-api") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testImplementation("org.assertj:assertj-core:3.25.3") +} + +/////////////////////////////////////////////////////////////////////////////// +// STANDARD CONFIGURATION FOR JAVA PROJECTS +/////////////////////////////////////////////////////////////////////////////// + +if (project.hasProperty("releaseTag")) { + project.version = project.property("releaseTag") as String + println("Release mode: version set to ${project.version}") +} else { + println("Development mode: version is ${project.version}") } val javaSourceLevel: String by project val javaTargetLevel: String by project + java { - sourceCompatibility = JavaVersion.toVersion(javaSourceLevel) - targetCompatibility = JavaVersion.toVersion(javaTargetLevel) - println("Compiling Java $sourceCompatibility to Java $targetCompatibility.") - withJavadocJar() - withSourcesJar() + sourceCompatibility = JavaVersion.toVersion(javaSourceLevel) + targetCompatibility = JavaVersion.toVersion(javaTargetLevel) + println("Compiling Java $sourceCompatibility to Java $targetCompatibility.") + withJavadocJar() + withSourcesJar() +} + +fun copyLicenseFiles() { + val metaInfDir = File(layout.buildDirectory.get().asFile, "resources/main/META-INF") + val licenseFile = File(project.rootDir, "LICENSE") + val noticeFile = File(project.rootDir, "NOTICE.md") + metaInfDir.mkdirs() + licenseFile.copyTo(File(metaInfDir, "LICENSE"), overwrite = true) + noticeFile.copyTo(File(metaInfDir, "NOTICE.md"), overwrite = true) } -/////////////////////////////////////////////////////////////////////////////// -// TASKS CONFIGURATION -/////////////////////////////////////////////////////////////////////////////// tasks { - spotless { - java { - target("src/**/*.java") - licenseHeaderFile("${project.rootDir}/LICENSE_HEADER") - importOrder("java", "javax", "org", "com", "") - removeUnusedImports() - googleJavaFormat() - } + spotless { + java { + target("src/**/*.java") + licenseHeaderFile("${project.rootDir}/LICENSE_HEADER") + importOrder("java", "javax", "org", "com", "") + removeUnusedImports() + googleJavaFormat() } - test { - testLogging { - events("passed", "skipped", "failed") - } - finalizedBy("jacocoTestReport") + kotlinGradle { + target("**/*.kts") + ktfmt() + } + } + test { + useJUnitPlatform() + testLogging { events("passed", "skipped", "failed") } + } + javadoc { + dependsOn(processResources) + val javadocLogo = project.findProperty("javadoc.logo") as String + val javadocCopyright = project.findProperty("javadoc.copyright") as String + val titleProperty = project.findProperty("title") as String + (options as StandardJavadocDocletOptions).apply { + overview = "src/main/javadoc/overview.html" + windowTitle = "$titleProperty - ${project.version}" + header( + "