forked from SteamWar/SteamWar
Only build and deploy changed projects in CI
Every project gets a checkAffected task that decides whether it has to
be rebuilt. A project is affected when one of these changed:
- its own files, or paths it adds with an affected { } block
- the versions of its resolved dependencies
- a project it depends on
- the root project, which owns the shared build and CI files
Projects list what they ship in a deploy { } block, and can add
afterDeploy commands there. Jars are now byte-reproducible. Only files
whose hash differs from the deployed one are uploaded, and only their
afterDeploy commands are run.
The deploy workflow diffs against the last deployed commit, which is
stored on the server with the dependency versions. PR builds diff
against the merge base. Shared setup steps moved into composite actions
under .gitea/actions.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
name: Setup build
|
||||
description: Installs the JDKs and Gradle and configures the SteamWar Maven repository
|
||||
|
||||
inputs:
|
||||
maven-credentials:
|
||||
description: Contents of steamwar.properties
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
# The last version becomes the default
|
||||
java-version: |
|
||||
8
|
||||
11
|
||||
17
|
||||
21
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
- name: Setup Maven Repository
|
||||
shell: bash
|
||||
env:
|
||||
SW_MAVEN_CREDENTIALS: ${{ inputs.maven-credentials }}
|
||||
run: |
|
||||
echo "$SW_MAVEN_CREDENTIALS" > steamwar.properties
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Setup SSH
|
||||
description: Configures the deploy server as the SSH host "deploy" for ssh and scp
|
||||
|
||||
inputs:
|
||||
host:
|
||||
description: Deploy server host name
|
||||
required: true
|
||||
user:
|
||||
description: Deploy server user
|
||||
required: true
|
||||
port:
|
||||
description: Deploy server SSH port
|
||||
required: false
|
||||
default: '22'
|
||||
key:
|
||||
description: Private SSH key of the deploy user
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure SSH
|
||||
shell: bash
|
||||
env:
|
||||
DEPLOY_HOST: ${{ inputs.host }}
|
||||
DEPLOY_USER: ${{ inputs.user }}
|
||||
DEPLOY_PORT: ${{ inputs.port }}
|
||||
DEPLOY_SSH_KEY: ${{ inputs.key }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
: "${DEPLOY_HOST:?Missing DEPLOY_HOST secret}"
|
||||
: "${DEPLOY_USER:?Missing DEPLOY_USER secret}"
|
||||
: "${DEPLOY_SSH_KEY:?Missing DEPLOY_SSH_KEY secret}"
|
||||
|
||||
port="${DEPLOY_PORT:-22}"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
echo "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
ssh-keyscan -p "$port" "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||
|
||||
cat >> ~/.ssh/config <<EOF
|
||||
Host deploy
|
||||
HostName ${DEPLOY_HOST}
|
||||
User ${DEPLOY_USER}
|
||||
Port ${port}
|
||||
IdentityFile ~/.ssh/deploy_key
|
||||
EOF
|
||||
chmod 600 ~/.ssh/config
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Usage: affected-projects.sh <state-dir> [base-commit]
|
||||
#
|
||||
# Runs the checkAffected task of every project for the changes between the base commit and HEAD
|
||||
# and writes the result to $GITHUB_OUTPUT:
|
||||
# projects - space separated paths of the affected projects
|
||||
# tasks - space separated Gradle tasks to run (empty if nothing is affected)
|
||||
#
|
||||
# The projects exchange files with this script through the state directory. If it contains
|
||||
# previous-dependency-versions (deploys only), projects whose resolved dependency versions changed are
|
||||
# affected as well and the collected dependency-versions, deploy-artifacts and deploy-commands of all
|
||||
# projects are written to it. Without a known base commit everything is affected.
|
||||
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
mkdir -p "$1"
|
||||
state="$(realpath "$1")"
|
||||
base="${2:-}"
|
||||
|
||||
deploy=false
|
||||
if [[ -f "${state}/previous-dependency-versions" ]]; then
|
||||
deploy=true
|
||||
fi
|
||||
|
||||
# Whether the diff of settings.gradle.kts only touches library versions of the version catalog
|
||||
catalog_only() {
|
||||
! git diff -U0 --no-renames "$base" HEAD -- settings.gradle.kts \
|
||||
| grep -E '^[+-]' \
|
||||
| grep -vE '^(\+\+\+|---) ' \
|
||||
| grep -qvE '^[+-][[:space:]]*(library\("|val [A-Za-z]+Version = "|$)'
|
||||
}
|
||||
|
||||
# All lines the projects wrote for the given kind
|
||||
collect() {
|
||||
local files=("${state}/projects/$1"/*)
|
||||
if (( ${#files[@]} )); then
|
||||
sort "${files[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
rm -f "${state}/changed-files"
|
||||
|
||||
if [[ -z "$base" ]]; then
|
||||
echo "No base commit, everything is affected"
|
||||
elif ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
|
||||
echo "Base commit ${base} is unknown, everything is affected"
|
||||
else
|
||||
echo "Changed files since ${base}:"
|
||||
git diff --name-only --no-renames "$base" HEAD | tee "${state}/changed-files"
|
||||
|
||||
# Library version changes are detected through the resolved dependency versions of the projects using them
|
||||
if [[ "$deploy" == "true" ]] && grep -qx 'settings.gradle.kts' "${state}/changed-files" && catalog_only; then
|
||||
echo "settings.gradle.kts only changed library versions, relying on the dependency versions"
|
||||
grep -vx 'settings.gradle.kts' "${state}/changed-files" > "${state}/changed-files.filtered" || true
|
||||
mv "${state}/changed-files.filtered" "${state}/changed-files"
|
||||
fi
|
||||
fi
|
||||
|
||||
args=(-q --no-daemon checkAffected -PciDir="$state")
|
||||
if [[ "$deploy" == "true" ]]; then
|
||||
args+=(deployInfo --refresh-dependencies)
|
||||
fi
|
||||
./gradlew "${args[@]}"
|
||||
|
||||
if [[ "$deploy" == "true" ]]; then
|
||||
collect dependency-versions > "${state}/dependency-versions"
|
||||
collect deploy-artifacts > "${state}/deploy-artifacts"
|
||||
collect deploy-commands > "${state}/deploy-commands"
|
||||
fi
|
||||
|
||||
projects="$(collect affected | cut -d ' ' -f 1 | sort -u | paste -sd ' ' -)"
|
||||
tasks="$(collect affected | cut -d ' ' -f 1 | sort -u | sed 's/$/:build/' | paste -sd ' ' -)"
|
||||
|
||||
echo "projects=${projects}"
|
||||
echo "tasks=${tasks}"
|
||||
|
||||
{
|
||||
echo "projects=${projects}"
|
||||
echo "tasks=${tasks}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Usage: stage-artifacts.sh <state-dir> <output-dir> [projects]
|
||||
#
|
||||
# Copies the deployed files of the given projects to the output directory under their deployed name,
|
||||
# skipping those identical to the deployed file, and lists the projects with copied files in
|
||||
# state-dir/deployed-projects. Reads state-dir/deploy-artifacts ("<project> <build output> <deployed name>"
|
||||
# per line, written by affected-projects.sh) and state-dir/deployed-hashes (sha256sum output of the deployed files).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
state="$1"
|
||||
output="$2"
|
||||
projects=" ${3:-} "
|
||||
|
||||
mkdir -p "$output"
|
||||
: > "${state}/deployed-projects"
|
||||
|
||||
while read -r project source name; do
|
||||
if [[ "$projects" != *" ${project} "* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
hash="$(sha256sum "$source" | cut -d ' ' -f 1)"
|
||||
if grep -qxF "${hash} ${name}" "${state}/deployed-hashes"; then
|
||||
echo "${name} from ${project} is unchanged"
|
||||
else
|
||||
echo "Staging ${name} from ${project}"
|
||||
cp "$source" "${output}/${name}"
|
||||
echo "$project" >> "${state}/deployed-projects"
|
||||
fi
|
||||
done < "${state}/deploy-artifacts"
|
||||
+76
-100
@@ -7,84 +7,25 @@ on:
|
||||
- version/*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
deploy:
|
||||
name: Build and deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Java 8
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 8
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v5
|
||||
fetch-depth: 0
|
||||
- name: Setup build
|
||||
uses: ./.gitea/actions/setup-build
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 11
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v5
|
||||
maven-credentials: ${{ secrets.SW_MAVEN_CREDENTIALS }}
|
||||
- name: Setup SSH
|
||||
uses: ./.gitea/actions/setup-ssh
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
- name: Setup Maven Repository
|
||||
env:
|
||||
SW_MAVEN_CREDENTIALS: ${{ secrets.SW_MAVEN_CREDENTIALS }}
|
||||
run: |
|
||||
echo "$SW_MAVEN_CREDENTIALS" > steamwar.properties
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean build --no-daemon --no-build-cache --refresh-dependencies
|
||||
|
||||
- name: Stage deploy artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
rm -rf deploy
|
||||
mkdir -p deploy
|
||||
|
||||
cp "AccessWidener/build/libs/AccessWidener-all.jar" "deploy/AccessWidener.jar"
|
||||
cp "BauSystem/build/libs/BauSystem-all.jar" "deploy/BauSystem.jar"
|
||||
cp "FightSystem/build/libs/FightSystem-all.jar" "deploy/FightSystem.jar"
|
||||
cp "KotlinCore/build/libs/KotlinCore-all.jar" "deploy/KotlinCore.jar"
|
||||
cp "TNTLeague/build/libs/TNTLeague.jar" "deploy/TNTLeague.jar"
|
||||
cp "LobbySystem/build/libs/LobbySystem.jar" "deploy/LobbySystem.jar"
|
||||
cp "MissileWars/build/libs/MissileWars.jar" "deploy/MissileWars.jar"
|
||||
cp "Realtime/build/libs/Realtime.jar" "deploy/RealTime.jar"
|
||||
cp "SchematicSystem/build/libs/SchematicSystem-all.jar" "deploy/SchematicSystem.jar"
|
||||
cp "SpigotCore/build/libs/SpigotCore-all.jar" "deploy/SpigotCore.jar"
|
||||
cp "Teamserver/build/libs/Teamserver.jar" "deploy/Builder.jar"
|
||||
cp "TowerRun/build/libs/TowerRun.jar" "deploy/TowerRun.jar"
|
||||
cp "VelocityCore/Persistent/build/libs/Persistent.jar" "deploy/PersistentVelocityCore.jar"
|
||||
cp "VelocityCore/Dependencies/build/libs/Dependencies-all.jar" "deploy/DependenciesVelocityCore.jar"
|
||||
cp "VelocityCore/build/libs/VelocityCore-all.jar" "deploy/VelocityCore.jar"
|
||||
cp "WebsiteBackend/build/libs/WebsiteBackend-all.jar" "deploy/website-api.jar"
|
||||
cp "CLI/build/distributions/sw.zip" "deploy/sw.zip"
|
||||
|
||||
- name: Upload deploy artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: steamwar-jars
|
||||
path: deploy/
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Download deploy artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: steamwar-jars
|
||||
path: deploy
|
||||
host: ${{ secrets.DEPLOY_HOST }}
|
||||
user: ${{ secrets.DEPLOY_USER }}
|
||||
port: ${{ secrets.DEPLOY_PORT }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
|
||||
- name: Resolve deploy target
|
||||
id: target
|
||||
@@ -106,45 +47,80 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload jars with scp
|
||||
- name: Read deployed state
|
||||
id: deployed
|
||||
shell: bash
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
DEPLOY_PATH: ${{ steps.target.outputs.path }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
: "${DEPLOY_HOST:?Missing DEPLOY_HOST secret}"
|
||||
: "${DEPLOY_USER:?Missing DEPLOY_USER secret}"
|
||||
: "${DEPLOY_SSH_KEY:?Missing DEPLOY_SSH_KEY secret}"
|
||||
mkdir -p .ci
|
||||
ssh deploy "cat '$DEPLOY_PATH/.dependency-versions' 2>/dev/null || true" > .ci/previous-dependency-versions
|
||||
ssh deploy "cd '$DEPLOY_PATH' 2>/dev/null && sha256sum -- *.jar *.zip 2>/dev/null || true" > .ci/deployed-hashes
|
||||
|
||||
port="${DEPLOY_PORT:-22}"
|
||||
commit="$(ssh deploy "cat '$DEPLOY_PATH/.deployed-commit' 2>/dev/null || true")"
|
||||
if [[ ! "$commit" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "No valid deployed commit found in ${DEPLOY_PATH}"
|
||||
commit=""
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
echo "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
ssh-keyscan -p "$port" "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||
echo "Deployed commit: ${commit:-none}"
|
||||
echo "commit=${commit}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p '$DEPLOY_PATH'"
|
||||
ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p '$DEPLOY_PATH/upload'"
|
||||
scp -i ~/.ssh/deploy_key -P "$port" deploy/* "${DEPLOY_USER}@${DEPLOY_HOST}:$DEPLOY_PATH/upload"
|
||||
ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "rm -f '$DEPLOY_PATH'/*.jar '$DEPLOY_PATH'/*.zip"
|
||||
ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "mv '$DEPLOY_PATH'/upload/* '$DEPLOY_PATH'"
|
||||
ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "rm -r '$DEPLOY_PATH/upload'"
|
||||
ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "chmod o-w '$DEPLOY_PATH'/*"
|
||||
- name: Restart Services
|
||||
- name: Determine affected projects
|
||||
id: affected
|
||||
shell: bash
|
||||
run: bash .gitea/scripts/affected-projects.sh .ci "${{ steps.deployed.outputs.commit }}"
|
||||
|
||||
- name: Build with Gradle
|
||||
if: steps.affected.outputs.tasks != ''
|
||||
shell: bash
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
TASKS: ${{ steps.affected.outputs.tasks }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "${DEPLOY_USER}@${DEPLOY_HOST}" "sudo systemctl restart api.service"
|
||||
ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "${DEPLOY_USER}@${DEPLOY_HOST}" "unzip -o /jars/current/sw.zip -d /jars"
|
||||
|
||||
read -ra tasks <<< "$TASKS"
|
||||
./gradlew clean "${tasks[@]}" --no-daemon --no-build-cache --refresh-dependencies
|
||||
|
||||
# Only jars whose content differs from the deployed one, so dependents that merely compile against a change stay untouched
|
||||
- name: Stage changed artifacts
|
||||
shell: bash
|
||||
run: bash .gitea/scripts/stage-artifacts.sh .ci .ci/jars "${{ steps.affected.outputs.projects }}"
|
||||
|
||||
- name: Deploy
|
||||
shell: bash
|
||||
env:
|
||||
DEPLOY_PATH: ${{ steps.target.outputs.path }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
ssh deploy "mkdir -p '$DEPLOY_PATH'"
|
||||
if compgen -G ".ci/jars/*" > /dev/null; then
|
||||
ssh deploy "rm -rf '$DEPLOY_PATH/upload' && mkdir -p '$DEPLOY_PATH/upload'"
|
||||
scp .ci/jars/* "deploy:$DEPLOY_PATH/upload"
|
||||
ssh deploy "mv -f '$DEPLOY_PATH'/upload/* '$DEPLOY_PATH' && rm -r '$DEPLOY_PATH/upload' && chmod o-w '$DEPLOY_PATH'/*"
|
||||
else
|
||||
echo "No changed artifacts to deploy"
|
||||
fi
|
||||
|
||||
# The next run only builds what changed since this commit
|
||||
scp .ci/dependency-versions "deploy:$DEPLOY_PATH/.dependency-versions"
|
||||
ssh deploy "echo '$GITHUB_SHA' > '$DEPLOY_PATH/.deployed-commit'"
|
||||
|
||||
# The afterDeploy commands of the deploy blocks of all projects with changed artifacts
|
||||
- name: Run after deploy commands
|
||||
if: github.ref_name == 'main'
|
||||
shell: bash
|
||||
env:
|
||||
DEPLOY_PATH: ${{ steps.target.outputs.path }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
while read -r project command; do
|
||||
if grep -qxF "$project" .ci/deployed-projects; then
|
||||
echo "Running '${command}' for ${project}"
|
||||
ssh -n deploy "cd '$DEPLOY_PATH' && ${command}"
|
||||
fi
|
||||
done < .ci/deploy-commands
|
||||
|
||||
@@ -14,35 +14,31 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Java 8
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 8
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v5
|
||||
fetch-depth: 0
|
||||
- name: Setup build
|
||||
uses: ./.gitea/actions/setup-build
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 11
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
- name: Setup Maven Repository
|
||||
env:
|
||||
SW_MAVEN_CREDENTIALS: ${{ secrets.SW_MAVEN_CREDENTIALS }}
|
||||
maven-credentials: ${{ secrets.SW_MAVEN_CREDENTIALS }}
|
||||
- name: Determine affected projects
|
||||
id: affected
|
||||
shell: bash
|
||||
run: |
|
||||
echo "$SW_MAVEN_CREDENTIALS" > steamwar.properties
|
||||
set -euo pipefail
|
||||
|
||||
base_branch="$(jq -r '.pull_request.base.ref' "$GITHUB_EVENT_PATH")"
|
||||
base="$(git merge-base "origin/${base_branch}" HEAD || true)"
|
||||
bash .gitea/scripts/affected-projects.sh .ci "$base"
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon
|
||||
if: steps.affected.outputs.tasks != ''
|
||||
shell: bash
|
||||
env:
|
||||
TASKS: ${{ steps.affected.outputs.tasks }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
read -ra tasks <<< "$TASKS"
|
||||
./gradlew "${tasks[@]}" --no-daemon
|
||||
|
||||
- name: Merge successful backport PR
|
||||
shell: bash
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
plugins {
|
||||
steamwar.kotlin
|
||||
application
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.distZip, "sw.zip")
|
||||
afterDeploy("unzip -o sw.zip -d /jars")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -22,6 +22,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
plugins {
|
||||
steamwar.kotlin
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
plugins {
|
||||
steamwar.java
|
||||
widener
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
plugins {
|
||||
steamwar.java
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
plugins {
|
||||
steamwar.java
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar, "RealTime.jar")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -21,6 +21,11 @@ plugins {
|
||||
`java-library`
|
||||
steamwar.java
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
plugins {
|
||||
steamwar.kotlin
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
plugins {
|
||||
steamwar.java
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar, "Builder.jar")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
plugins {
|
||||
steamwar.java
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -21,6 +21,11 @@ plugins {
|
||||
steamwar.java
|
||||
steamwar.kotlin
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar, "DependenciesVelocityCore.jar")
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
plugins {
|
||||
steamwar.java
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.jar, "PersistentVelocityCore.jar")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
plugins {
|
||||
steamwar.java
|
||||
alias(libs.plugins.shadow)
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -22,6 +22,12 @@ plugins {
|
||||
id("io.ktor.plugin") version "2.3.12"
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.10"
|
||||
application
|
||||
deploy
|
||||
}
|
||||
|
||||
deploy {
|
||||
artifact(tasks.shadowJar, "website-api.jar")
|
||||
afterDeploy("sudo systemctl restart api.service")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -19,11 +19,40 @@
|
||||
|
||||
plugins {
|
||||
base
|
||||
affected
|
||||
}
|
||||
|
||||
// Files shared by all projects, every project is affected when the root project is
|
||||
affected {
|
||||
files.set(
|
||||
listOf(
|
||||
"build.gradle.kts",
|
||||
"settings.gradle.kts",
|
||||
"gradle.properties",
|
||||
"gradlew",
|
||||
"gradlew.bat",
|
||||
"gradle/",
|
||||
"buildSrc/",
|
||||
".gitea/workflows/",
|
||||
".gitea/actions/",
|
||||
".gitea/scripts/",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "de.steamwar"
|
||||
version = ""
|
||||
|
||||
// Byte identical archives for identical inputs, so CI only deploys jars whose content changed
|
||||
tasks.withType<AbstractArchiveTask>().configureEach {
|
||||
isPreserveFileTimestamps = false
|
||||
isReproducibleFileOrder = true
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "affected")
|
||||
}
|
||||
|
||||
tasks.register("0_ClearAllGradle") {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.gradle.api.provider.SetProperty
|
||||
|
||||
abstract class AffectedExtension {
|
||||
|
||||
/**
|
||||
* Paths relative to the root directory whose changes affect this project, a trailing / matches everything
|
||||
* below a directory. Defaults to the project directory, files of subprojects never affect this project.
|
||||
*/
|
||||
abstract val files: SetProperty<String>
|
||||
|
||||
/**
|
||||
* Projects whose changes affect this project in addition to its project dependencies
|
||||
*/
|
||||
abstract val projects: SetProperty<String>
|
||||
|
||||
fun whenChanged(vararg paths: String) {
|
||||
files.addAll(*paths)
|
||||
}
|
||||
|
||||
fun withProject(vararg paths: String) {
|
||||
projects.addAll(*paths)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Directory for exchanging files with the CI scripts, set with -PciDir
|
||||
*/
|
||||
val Project.ciDir: File?
|
||||
get() = findProperty("ciDir")?.let { rootProject.file(it) }
|
||||
|
||||
/**
|
||||
* Writes the lines prefixed with the project path to the CI directory, where the CI scripts collect them from all projects.
|
||||
* Removes the file if there are no lines.
|
||||
*/
|
||||
fun Project.writeCiFile(kind: String, lines: Collection<String>) {
|
||||
val name = if (this == rootProject) "root" else path.removePrefix(":").replace(':', '.')
|
||||
val file = ciDir?.resolve("projects/$kind/$name") ?: return
|
||||
if (lines.isEmpty()) {
|
||||
file.delete()
|
||||
} else {
|
||||
file.parentFile.mkdirs()
|
||||
file.writeText(lines.joinToString("") { "$path $it\n" })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.RegularFile
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.MapProperty
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class DeployExtension {
|
||||
|
||||
@get:Inject
|
||||
abstract val project: Project
|
||||
|
||||
/**
|
||||
* Deployed file name -> build output, the CI deploys every file whose content changed.
|
||||
* The producing task has to run as part of the build task.
|
||||
*/
|
||||
abstract val artifacts: MapProperty<String, RegularFile>
|
||||
|
||||
/**
|
||||
* Shell commands run in the deploy directory on the deploy server after one of the artifacts changed,
|
||||
* only for deployments of main
|
||||
*/
|
||||
abstract val commands: ListProperty<String>
|
||||
|
||||
fun artifact(task: TaskProvider<out AbstractArchiveTask>, name: String = "${project.name}.jar") {
|
||||
artifacts.put(name, task.flatMap { it.archiveFile })
|
||||
}
|
||||
|
||||
fun afterDeploy(command: String) {
|
||||
commands.add(command)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
val affected = extensions.create<AffectedExtension>("affected")
|
||||
|
||||
// By default a project watches its own directory, whenChanged adds to it and files.set replaces it
|
||||
affected.files.add(projectDir.relativeTo(rootDir).invariantSeparatorsPath + "/")
|
||||
|
||||
// Reads changed-files and previous-dependency-versions from the CI directory, without changed-files everything is affected.
|
||||
// Writes the reasons this project is affected and its dependency versions back for the CI scripts.
|
||||
tasks.register("checkAffected") {
|
||||
group = "help"
|
||||
description = "Checks whether this project has to be rebuilt because of the changes described in -PciDir"
|
||||
notCompatibleWithConfigurationCache("Inspects the project model and resolves configurations")
|
||||
|
||||
val upstreamProjects = provider {
|
||||
val projectDependencies = project.configurations
|
||||
.flatMap { it.dependencies.withType<ProjectDependency>() }
|
||||
.map { it.path }
|
||||
// The root project watches the files shared by all projects
|
||||
val rootProject = listOfNotNull(project.rootProject.path.takeIf { project.rootProject.plugins.hasPlugin("affected") })
|
||||
(projectDependencies + affected.projects.get() + rootProject).toSet() - project.path
|
||||
}
|
||||
dependsOn(upstreamProjects.map { paths -> paths.map { "$it:checkAffected" } })
|
||||
|
||||
doLast {
|
||||
val ciDir = project.ciDir ?: throw GradleException("checkAffected requires -PciDir")
|
||||
fun resultFile(target: Project) = target.layout.buildDirectory.file("affected/result").get().asFile
|
||||
|
||||
val reasons = mutableListOf<String>()
|
||||
|
||||
val changedFiles = ciDir.resolve("changed-files")
|
||||
if (changedFiles.exists()) {
|
||||
val subprojectDirs = project.subprojects.map { it.projectDir.relativeTo(project.rootDir).invariantSeparatorsPath + "/" }
|
||||
val watched = affected.files.get()
|
||||
for (changedFile in changedFiles.readLines().filter { it.isNotBlank() }) {
|
||||
if (watched.any { changedFile == it || (it.endsWith("/") && changedFile.startsWith(it)) } && subprojectDirs.none { changedFile.startsWith(it) }) {
|
||||
reasons += "$changedFile changed"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reasons += "no changed files known"
|
||||
}
|
||||
|
||||
val previousVersionsFile = ciDir.resolve("previous-dependency-versions")
|
||||
if (previousVersionsFile.exists()) {
|
||||
val versions = project.configurations
|
||||
.filter { it.isCanBeResolved && (it.name.endsWith("Classpath") || it.name.endsWith("annotationProcessor", ignoreCase = true)) }
|
||||
.flatMap { it.incoming.resolutionResult.allComponents }
|
||||
.mapNotNull { it.id as? ModuleComponentIdentifier }
|
||||
.map { "${it.group}:${it.module}:${it.version}" }
|
||||
.toSortedSet()
|
||||
project.writeCiFile("dependency-versions", versions)
|
||||
|
||||
val previousLines = previousVersionsFile.readLines()
|
||||
if (previousLines.none { it.isNotBlank() }) {
|
||||
reasons += "no previous dependency versions"
|
||||
} else {
|
||||
val previousVersions = previousLines
|
||||
.filter { it.startsWith("${project.path} ") }
|
||||
.map { it.substringAfter(' ') }
|
||||
.toSet()
|
||||
(versions - previousVersions).forEach { reasons += "dependency $it added" }
|
||||
(previousVersions - versions).forEach { reasons += "dependency $it removed" }
|
||||
}
|
||||
}
|
||||
|
||||
for (upstream in upstreamProjects.get()) {
|
||||
if (resultFile(project.project(upstream)).readText().trim() == "affected") {
|
||||
reasons += if (upstream == project.rootProject.path) "root project affected" else "depends on $upstream"
|
||||
}
|
||||
}
|
||||
|
||||
resultFile(project).apply {
|
||||
parentFile.mkdirs()
|
||||
writeText(if (reasons.isEmpty()) "unaffected" else "affected")
|
||||
}
|
||||
// Projects without anything to build only pass their state on to their dependents
|
||||
project.writeCiFile("affected", if (project != project.rootProject && project.tasks.findByName("build") != null) reasons else emptyList())
|
||||
if (reasons.isNotEmpty()) {
|
||||
logger.quiet("${if (project == project.rootProject) "Root project" else project.path}: ${reasons.joinToString(", ")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
val deploy = extensions.create<DeployExtension>("deploy")
|
||||
|
||||
tasks.register("deployInfo") {
|
||||
group = "help"
|
||||
description = "Writes the deployed files and after deploy commands of this project to -PciDir"
|
||||
notCompatibleWithConfigurationCache("Inspects the project model")
|
||||
|
||||
doLast {
|
||||
project.writeCiFile("deploy-artifacts", deploy.artifacts.get().map { (name, file) ->
|
||||
"${file.asFile.relativeTo(project.rootDir).invariantSeparatorsPath} $name"
|
||||
})
|
||||
project.writeCiFile("deploy-commands", deploy.commands.get())
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("affected")
|
||||
}
|
||||
|
||||
// The AccessWidener jar is only used through a task dependency
|
||||
the<AffectedExtension>().withProject(":AccessWidener")
|
||||
|
||||
val widener = extensions.create<WidenerExtension>("widener")
|
||||
|
||||
widener.accessWidenerFiles.setFrom(
|
||||
|
||||
Reference in New Issue
Block a user