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:
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"
|
||||
Reference in New Issue
Block a user