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:
2026-11-01 09:36:19 +01:00
parent 937905f296
commit cbb2987504
30 changed files with 683 additions and 125 deletions
+76 -100
View File
@@ -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
+21 -25
View File
@@ -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