Introduce new CI/CD pipeline using GitHub Actions

A lot is happening in this release!

tl;dr: GitHub Actions runs tests, compiles the project, signs the jar files, deploys them to the Maven repo; Pushing a git tag issues a release instead of snapshot deployment; -SNAPSHOT is always added to the version otherwise; Core Version is now injected by maven instead of manually updating it in one of the classes


We now use GitHub Actions to run automated tests, compile the project, sign the resulting jar files, and always deploy a version to the Maven repo.
By default, a snapshot release is published but by creating a git tag, a release deploy can be triggered.

Additionally the Core version is not manually updated in one of the classes but injected after compiling it.
I think I found the most stable and easiest way to do this in maven,
although I'd have wished for it to be easier and maybe not after the class file has already been created.
This commit is contained in:
Christian Koop 2022-08-07 19:33:38 +02:00
parent 84515e7004
commit 5e1f1b802c
No known key found for this signature in database
GPG Key ID: 89A8181384E010A3
13 changed files with 306 additions and 116 deletions

10
.github/FUNDING.yml vendored
View File

@ -1,12 +1,2 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: songoda
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: [ 'https://songoda.com/songoda+' ]

View File

@ -0,0 +1,23 @@
name: Prepare Workspace
description: Prepares the workspace for compilation
inputs:
maven_username:
required: false
description: The username to use for the Maven server
maven_password:
required: false
description: The password to use for the Maven server
runs:
using: composite
steps:
- uses: songoda/GH-Commons/.github/actions/setup_workspace@master
with:
maven_username: ${{ inputs.maven_username }}
maven_password: ${{ inputs.maven_password }}
- uses: SpraxDev/Action-SpigotMC@v4
with:
versions: 1.18.2, 1.19
remapped: true

14
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,14 @@
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

93
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,93 @@
name: Build
on:
push:
branches: [ master, development ]
tags:
- 'v*'
pull_request:
types: [ opened, synchronize, reopened ]
permissions: read-all
env:
DEPLOYMENT_POM_PATH: ./Core/dependency-reduced-pom.xml
DEPLOYMENT_ARTIFACT_DIR: ./Core/target
DEPLOYMENT_ARTIFACT_SELECTOR: SongodaCore-*.jar
jobs:
Tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Prepare Workspace
uses: ./.github/actions/setup_project_workspace
- name: Run tests
run: mvn -B clean test
Build:
name: Build + Deploy
runs-on: ubuntu-latest
needs: [ Tests ]
steps:
- uses: actions/checkout@v2
- name: Prepare Workspace
uses: ./.github/actions/setup_project_workspace
with:
maven_username: ${{ secrets.MAVEN_REPO_USERNAME }}
maven_password: ${{ secrets.MAVEN_REPO_PASSWORD }}
- name: Set project version
uses: songoda/GH-Commons/.github/actions/maven_set_project_version@master
with:
append_snapshot: ${{ github.ref_type == 'tag' && 'false' || 'true' }}
version: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
- name: Build with Maven
run: mvn -B -Duser.name="GitHub Actions on $GITHUB_REPOSITORY (id=$GITHUB_RUN_ID)" -DskipTests clean package
- name: Sign jar archives
uses: songoda/GH-Commons/.github/actions/sign_jars@master
with:
jar_file_selector: ${{ env.DEPLOYMENT_ARTIFACT_DIR }}/${{ env.DEPLOYMENT_ARTIFACT_SELECTOR }}
keystore_gpg_encrypted: ${{ secrets.JARSIGNER_KEYSTORE_ENCRYPTED }}
keystore_gpg_password: ${{ secrets.JARSIGNER_KEYSTORE_ENCRYPTED_PASSWORD }}
keystore_password: ${{ secrets.JARSIGNER_KEYSTORE_PASSWORD }}
- name: 'Upload Build Artifacts'
uses: actions/upload-artifact@v3
with:
name: ${{ github.event.repository.name }}
path: ${{ env.DEPLOYMENT_ARTIFACT_DIR }}/${{ env.DEPLOYMENT_ARTIFACT_SELECTOR }}
- name: Deploy to Maven repo
uses: songoda/GH-Commons/.github/actions/maven_deploy@master
with:
repository_url: ${{ secrets.MAVEN_REPO_URL_RELEASES }}
repository_url_snapshots: ${{ secrets.MAVEN_REPO_URL_SNAPSHOTS }}
maven_pom_path: ${{ env.DEPLOYMENT_POM_PATH }}
maven_out_dir: ${{ env.DEPLOYMENT_ARTIFACT_DIR }}
- name: Deploy parent pom.xml to Maven repo
uses: songoda/GH-Commons/.github/actions/maven_deploy@master
with:
repository_url: ${{ secrets.MAVEN_REPO_URL_RELEASES }}
repository_url_snapshots: ${{ secrets.MAVEN_REPO_URL_SNAPSHOTS }}
only_deploy_pom: true
maven_out_dir: ${{ env.DEPLOYMENT_ARTIFACT_DIR }}
discord_webhook:
name: Send Discord Webhook
runs-on: ubuntu-latest
needs: [ Tests, Build ]
if: ${{ always() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development' || github.ref_type == 'tag') }}
steps:
- uses: actions/checkout@v2
- name: Notify Webhook
uses: songoda/GH-Commons/.github/actions/discord_send_job_results@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}

36
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: CodeQL
on:
push:
branches: [ master, development ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master, development ]
schedule:
- cron: 30 18 * * 4
permissions:
actions: read
contents: read
security-events: write
jobs:
Analyze:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: java
- name: Prepare Workspace
uses: ./.github/actions/setup_project_workspace
- name: Build with Maven
run: mvn -B -DskipTests clean package
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@ -1,61 +0,0 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Build SongodaCore
on:
push:
branches: [ master, development ]
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
# Checkout project files
- uses: actions/checkout@v2
# Setup Java
- uses: actions/setup-java@v2
with:
java-version: 17
distribution: adopt
cache: maven
# Build Spigot 1.18.2 (remapped)
- uses: SpraxDev/Action-SpigotMC@v4
with:
versions: 1.18.2
remapped: true
# Build project
- name: Build with Maven
run: 'mvn -B -Duser.name="GitHub Runner on $GITHUB_REPOSITORY (id=$GITHUB_RUN_ID)" clean package'
# Upload build artifacts
- name: 'Upload Build Artifact: SongodaCore-*.jar'
uses: actions/upload-artifact@v2
with:
name: SongodaCore-artifacts
path: ./Core/target/SongodaCore-*.jar
##
# Discord Webhook
# TODO: Extract into external Action for better reusability (and readability)
##
- name: 'Discord Webhook (Success)'
if: ${{ success() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development') }}
continue-on-error: true
run: |
curl -X POST --data "{\"content\":null,\"embeds\":[{\"title\":\"Build succeeded!\",\"description\":\"The build with the ID #$GITHUB_RUN_NUMBER has succeeded!\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",\"color\":5490477,\"fields\":[{\"name\":\"Branch\",\"value\":\"$GITHUB_REF\",\"inline\":true}],\"author\":{\"name\":\"$GITHUB_REPOSITORY\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY\",\"icon_url\":\"$GITHUB_SERVER_URL/songoda.png\"},\"footer\":{\"text\":\"Initiated by $GITHUB_ACTOR\",\"icon_url\":\"$GITHUB_SERVER_URL/$GITHUB_ACTOR.png\"}}],\"username\":\"OctoAgent\",\"avatar_url\":\"https://github.githubassets.com/images/modules/logos_page/Octocat.png\"}" --header 'Content-Type: application/json' $DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_BUILD_STATUS_WEBHOOK }}
- name: 'Discord Webhook (Failure)'
if: ${{ failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development') }}
continue-on-error: true
run: |
curl -X POST --data "{\"content\":null,\"embeds\":[{\"title\":\"Build failed!\",\"description\":\"The build with the ID #$GITHUB_RUN_NUMBER has failed!\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",\"color\":15611419,\"fields\":[{\"name\":\"Branch\",\"value\":\"$GITHUB_REF\",\"inline\":true}],\"author\":{\"name\":\"$GITHUB_REPOSITORY\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY\",\"icon_url\":\"$GITHUB_SERVER_URL/songoda.png\"},\"footer\":{\"text\":\"Initiated by $GITHUB_ACTOR\",\"icon_url\":\"$GITHUB_SERVER_URL/$GITHUB_ACTOR.png\"}}],\"username\":\"OctoAgent\",\"avatar_url\":\"https://github.githubassets.com/images/modules/logos_page/Octocat.png\"}" --header "Content-Type:application/json" $DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_BUILD_STATUS_WEBHOOK }}

View File

@ -6,45 +6,38 @@ on:
pull_request:
types: [ opened, synchronize, reopened ]
permissions: read-all
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_PROJECT_KEY: songoda_SongodaCore
jobs:
build:
name: Build
Analyze:
runs-on: ubuntu-latest
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
steps:
# Checkout project files
- uses: actions/checkout@v2
- uses: actions/checkout@v3
if: ${{ env.SONAR_TOKEN != null }}
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
fetch-depth: 0
# Setup Java
- uses: actions/setup-java@v2
- name: Prepare Workspace
if: ${{ env.SONAR_TOKEN != null }}
with:
java-version: 17
distribution: adopt
cache: maven
uses: ./.github/actions/setup_project_workspace
# Cache
- name: 'Cache: SonarCloud'
if: ${{ env.SONAR_TOKEN != null }}
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
# Build Spigot 1.18.2 (remapped)
- uses: SpraxDev/Action-SpigotMC@v4
if: ${{ env.SONAR_TOKEN != null }}
with:
versions: 1.18.2
remapped: true
- name: Analyze project
if: ${{ env.SONAR_TOKEN != null }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=songoda_SongodaCore
run: >
mvn -B \
verify \
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
"-Dsonar.projectKey=$SONAR_PROJECT_KEY"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -13,6 +13,9 @@
<artifactId>SongodaCore-Compatibility</artifactId>
<packaging>jar</packaging>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>

View File

@ -21,7 +21,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
@ -31,6 +31,31 @@
</configuration>
</plugin>
<plugin>
<groupId>de.m3y.maven</groupId>
<artifactId>inject-maven-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>inject</goal>
</goals>
</execution>
</executions>
<configuration>
<injections>
<injection>
<value>${project.version}</value>
<pointCut>com.songoda.core.SongodaCoreConstants.getCoreVersion</pointCut>
</injection>
</injections>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
@ -63,7 +88,7 @@
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<useDependencyReducedPomInJar>true</useDependencyReducedPomInJar>
<artifactSet>
<includes>
@ -91,16 +116,21 @@
</relocation>
</relocations>
<artifactSet>
<includes>
<include>com.songoda:*</include>
<include>com.zaxxer:HikariCP</include>
<include>org.apache.commons:commons-text</include>
<include>org.apache.commons:commons-lang3</include>
<include>de.tr7zw:item-nbt-api</include>
</includes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<includes>
<include>com/</include>
<include>de/tr7zw/</include>
<include>net/kyori/</include>
<include>META-INF/MANIFEST.MF</include>
<include>META-INF/maven/com.songoda/SongodaCore/</include>
<include>**/*.class</include>
</includes>
</filter>
</filters>

View File

@ -47,13 +47,18 @@ public class SongodaCore {
/**
* Whenever we make a major change to the core GUI, updater,
* or other function used by the core, increment this number
*
* @deprecated The Core's version should be used instead as it uses Semantic Versioning
*/
@Deprecated
private final static int coreRevision = 9;
/**
* @since coreRevision 6
* @deprecated Is being replaced by {@link SongodaCoreConstants#getCoreVersion()} which is automatically kept up to date.
*/
private final static String coreVersion = "2.6.12";
@Deprecated
private final static String coreVersion = SongodaCoreConstants.getCoreVersion();
/**
* This is specific to the website api
@ -74,7 +79,7 @@ public class SongodaCore {
}
public static void registerPlugin(JavaPlugin plugin, int pluginID, CompatibleMaterial icon) {
registerPlugin(plugin, pluginID, icon == null ? "STONE" : icon.name(), coreVersion);
registerPlugin(plugin, pluginID, icon == null ? "STONE" : icon.name(), SongodaCoreConstants.getCoreVersion());
}
public static void registerPlugin(JavaPlugin plugin, int pluginID, String icon) {
@ -89,13 +94,23 @@ public class SongodaCore {
try {
// test to see if we're up-to-date
int otherVersion;
int ownVersion;
try {
otherVersion = (int) clazz.getMethod("getCoreVersion").invoke(null);
otherVersion = (int) clazz.getMethod("getCoreMajorVersion").invoke(null);
ownVersion = getCoreMajorVersion();
} catch (Exception ignore) {
otherVersion = -1;
try {
otherVersion = (int) clazz.getMethod("getCoreVersion").invoke(null);
} catch (Exception ignore2) {
otherVersion = -1;
}
ownVersion = getCoreVersion();
}
if (otherVersion >= getCoreVersion()) {
if (otherVersion >= ownVersion) {
// use the active service
// assuming that the other is greater than R6 if we get here ;)
clazz.getMethod("registerPlugin", JavaPlugin.class, int.class, String.class, String.class).invoke(null, plugin, pluginID, icon, coreVersion);
@ -264,12 +279,25 @@ public class SongodaCore {
return new ArrayList<>(registeredPlugins);
}
/**
* @deprecated Use {@link #getCoreMajorVersion()} instead, but careful, coreRevision is at 9 while major version is at 2
*/
@Deprecated
public static int getCoreVersion() {
return coreRevision;
}
public static String getCoreLibraryVersion() {
return coreVersion;
return SongodaCoreConstants.getCoreVersion();
}
public static int getCoreMajorVersion() {
String fullVersion = getCoreLibraryVersion();
if (fullVersion.contains(".")) {
return Integer.parseInt(fullVersion.substring(0, fullVersion.indexOf(".")));
}
return -1;
}
public static int getUpdaterVersion() {

View File

@ -0,0 +1,12 @@
package com.songoda.core;
/*
* Return values in this class are automatically replaced by a maven plugin after the project has been compiled.
* This allows for properties to be defined at one place without relying on a text file
* that needs to be inside the final jar (might get lost when this lib is shaded into other projects).
*/
public class SongodaCoreConstants {
public static String getCoreVersion() {
return "UNKNOWN_VESION";
}
}

View File

@ -0,0 +1,25 @@
package com.songoda.core;
import org.junit.jupiter.api.Test;
import org.opentest4j.TestSkippedException;
import java.util.Objects;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SongodaCoreConstantsTest {
// Pattern is from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
private static final Pattern VERSION_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
@Test
void getCoreVersion() {
if (!Objects.equals(System.getenv("TESTS_RUN_WITH_MAVEN"), "true")) {
throw new TestSkippedException("Skipping test because it requires the TESTS_RUN_WITH_MAVEN environment variable to be set to true");
}
String coreVersion = SongodaCoreConstants.getCoreVersion();
assertTrue(VERSION_PATTERN.matcher(coreVersion).matches(), "Version string is not a valid semver string: " + coreVersion);
}
}

16
pom.xml
View File

@ -10,14 +10,12 @@
<packaging>pom</packaging>
<!-- Run 'mvn versions:set -DgenerateBackupPoms=false -DnewVersion=X.Y.Z' to update version recursively -->
<!-- Change version in com.songoda.core.SongodaCore too -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<java.release>8</java.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.organization>songoda</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.junit.reportPaths>target/surefire-reports/*.xml</sonar.junit.reportPaths>
@ -65,7 +63,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
@ -79,6 +77,12 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<environmentVariables>
<TESTS_RUN_WITH_MAVEN>true</TESTS_RUN_WITH_MAVEN>
</environmentVariables>
</configuration>
</plugin>
<plugin>
@ -138,8 +142,8 @@
<repositories>
<repository>
<id>songoda-public</id>
<url>https://repo.songoda.com/repository/public/</url>
<id>songoda-minecraft-plugins</id>
<url>https://repo.songoda.com/repository/minecraft-plugins/</url>
</repository>
<repository>