Build 1.21.11 with Stonecutter from a shared src/

Step 1 of #8. 1.21.11's version-specific code moves from versions/1.21.11/src
to the top-level src/, built by Stonecutter as the project :1.21.11. The other
versions still build from their own copies as :mc-<v> until they're folded in.

- settings.gradle: applies Stonecutter (Groovy controller) with
  stonecutterVersions = ["1.21.11"] and skips those folders when including
  the old mc-<v> projects.
- The old root build.gradle becomes stonecutter.gradle (the root project's
  script, with the shared repositories, common wiring and buildAll), and
  1.21.11's build.gradle becomes the root build.gradle that Stonecutter runs
  for each version. versions/1.21.11 keeps its gradle.properties and run/.
- The jar has the same 415 entries, fabric.mod.json and mixin configs as
  before; the only class differences also appear when rebuilding an
  untouched version today.
- scripts/port.sh treats src/ as the active Stonecutter version, so changes
  port between it and the old copies both ways.
- README and VERSION_GUIDE describe the layout, version comments and how to
  add a version.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-27 04:18:42 +02:00
co-authored by claude
parent 6ed2ac1b60
commit 019e5cfb43
60 changed files with 267 additions and 202 deletions
+46 -18
View File
@@ -1,11 +1,15 @@
#!/usr/bin/env bash
# Port a change made in one Minecraft version's code to the other versions.
#
# Takes the diff under versions/<from>/src and applies it to versions/<to>/src
# Takes the diff under one version's source folder and applies it to the others
# with `git apply --3way`: it applies cleanly where the files match, and leaves
# normal merge conflicts only where a version really differs. Only src/ is
# normal merge conflicts only where a version really differs. Only source is
# ported; build.gradle and gradle.properties are version-specific on purpose.
#
# Versions moved to Stonecutter (stonecutterVersions in settings.gradle) share
# the top-level src/, which counts as one version: the active one set in
# stonecutter.gradle. The others keep their own copy in versions/<v>/src.
#
# Usage:
# scripts/port.sh [--from VERSION] [--to V1,V2,...] [--commit REV] [--dry-run]
#
@@ -33,25 +37,39 @@ while [ $# -gt 0 ]; do
--to) to="$2"; shift 2 ;;
--commit) commit="$2"; shift 2 ;;
--dry-run) dry_run=true; shift ;;
-h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
-h|--help) sed -n '2,23p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "Unknown option: $1 (see --help)" >&2; exit 2 ;;
esac
done
all_versions=$(ls -d versions/*/ | xargs -n1 basename | sort -t. -k1,1n -k2,2n -k3,3n)
# Versions sharing the Stonecutter src/, and the one that stands for them.
stonecutter_versions=$(sed -n 's/^def stonecutterVersions = \[\(.*\)\]/\1/p' settings.gradle | tr -d '" ' | tr ',' ' ')
stonecutter_active=$(sed -n 's/^stonecutter\.active "\(.*\)"/\1/p' stonecutter.gradle 2>/dev/null || true)
is_stonecutter() {
printf '%s\n' $stonecutter_versions | grep -qx "$1"
}
# Where a version's source lives.
src_of() {
if is_stonecutter "$1"; then echo "src"; else echo "versions/$1/src"; fi
}
old_versions=$(for d in versions/*/; do [ -d "$d/src" ] && basename "$d"; done)
all_versions=$(printf '%s\n' $old_versions $stonecutter_active | grep . | sort -t. -k1,1n -k2,2n -k3,3n)
version_exists() {
[ -d "versions/$1/src" ]
[ -d "$(src_of "$1")" ]
}
# Versions with changes, from the working tree or from the given commit.
changed_versions() {
if [ -n "$commit" ]; then
git show --name-only --format= "$commit" -- versions/
git show --name-only --format= "$commit" -- versions/ src/
else
git diff HEAD --name-only -- versions/
git ls-files --others --exclude-standard -- versions/
fi | sed -n 's#^versions/\([^/]*\)/src/.*#\1#p' | sort -u
git diff HEAD --name-only -- versions/ src/
git ls-files --others --exclude-standard -- versions/ src/
fi | sed -n -e 's#^versions/\([^/]*\)/src/.*#\1#p' -e "s#^src/.*#${stonecutter_active}#p" | sort -u
}
if [ -z "$from" ]; then
@@ -65,6 +83,10 @@ if [ -z "$from" ]; then
fi
version_exists "$from" || { echo "No such version: $from" >&2; exit 2; }
if is_stonecutter "$from"; then
from="$stonecutter_active"
fi
from_src=$(src_of "$from")
if [ -z "$to" ]; then
to=$(printf '%s\n' $all_versions | grep -vx "$from" | paste -sd, -)
@@ -72,10 +94,10 @@ fi
# New files are only included in `git diff HEAD` once git knows about them.
if [ -z "$commit" ]; then
untracked=$(git ls-files --others --exclude-standard -- "versions/$from/src")
untracked=$(git ls-files --others --exclude-standard -- "$from_src")
if [ -n "$untracked" ]; then
if $dry_run; then
echo "note: new files in versions/$from/src are untracked and won't be ported in a dry run:" >&2
echo "note: new files in $from_src are untracked and won't be ported in a dry run:" >&2
printf ' %s\n' $untracked >&2
else
printf '%s\n' "$untracked" | xargs git add --intent-to-add --
@@ -85,20 +107,20 @@ fi
# --full-index gives --3way the real blob ids to merge against.
if [ -n "$commit" ]; then
patch=$(git show --full-index --binary --format= "$commit" -- "versions/$from/src")
patch=$(git show --full-index --binary --format= "$commit" -- "$from_src")
else
patch=$(git diff HEAD --full-index --binary -- "versions/$from/src")
patch=$(git diff HEAD --full-index --binary -- "$from_src")
fi
if [ -z "$patch" ]; then
echo "No changes under versions/$from/src to port." >&2
echo "No changes under $from_src to port." >&2
exit 2
fi
files=$(printf '%s\n' "$patch" | grep -c '^diff --git' || true)
echo "Porting $files file(s) from $from${commit:+ (commit $commit)}"
from_re=$(printf '%s' "$from" | sed 's/\./\\./g')
from_re=$(printf '%s' "$from_src" | sed 's/\./\\./g')
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
@@ -109,14 +131,20 @@ for target in "${targets[@]}"; do
if [ "$target" = "$from" ]; then
continue
fi
if is_stonecutter "$target" && [ "$target" != "$stonecutter_active" ]; then
printf ' %-8s skipped: shares src/ with %s\n' "$target" "$stonecutter_active"
[ "$from" = "$stonecutter_active" ] || status=1
continue
fi
if ! version_exists "$target"; then
printf ' %-8s skipped: no such version\n' "$target"
status=1
continue
fi
# Point the patch's file headers at the target version.
printf '%s\n' "$patch" | sed -E "/^(diff --git |--- |\+\+\+ |rename (from|to) |copy (from|to) )/ s#versions/${from_re}/#versions/${target}/#g" > "$tmp/$target.patch"
# Point the patch's file headers at the target version's source folder.
target_src=$(src_of "$target")
printf '%s\n' "$patch" | sed -E "/^(diff --git |--- |\+\+\+ |rename (from|to) |copy (from|to) )/ s#([ab]/|(from|to) )${from_re}/#\1${target_src}/#g" > "$tmp/$target.patch"
if git apply --check --reverse "$tmp/$target.patch" 2>/dev/null; then
printf ' %-8s already has this change\n' "$target"
@@ -135,7 +163,7 @@ for target in "${targets[@]}"; do
if output=$(git apply --3way "$tmp/$target.patch" 2>&1); then
printf ' %-8s applied\n' "$target"
else
conflicts=$(git diff --name-only --diff-filter=U -- "versions/$target")
conflicts=$(git diff --name-only --diff-filter=U -- "$target_src")
if [ -n "$conflicts" ]; then
printf ' %-8s CONFLICTS in:\n' "$target"
printf '%s\n' "$conflicts" | sed 's/^/ /'