Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 1 addition & 42 deletions .devtools/Makefile
Original file line number Diff line number Diff line change
@@ -1,43 +1,2 @@
include base.mk

GRADLE := cd $(ROOT) && JAVA_HOME="$$(/usr/libexec/java_home -v $$(cat .java-version) 2>/dev/null || echo $$JAVA_HOME)" ./gradlew

.PHONY: fmt test build check client-test lock stray kill-stray run-fabric run-neoforge run-fabric-server run-neoforge-server clean

fmt: ## Format Java sources
$(GRADLE) spotlessApply

test: ## Unit tests with coverage gate
$(GRADLE) :common:check

build: ## Compile, lint (Error Prone), test and package both loaders
$(GRADLE) build

check: lint build ## Everything CI runs

client-test: ## Screenshot the GUI in a real client (opens a window, so it is not part of check)
$(GRADLE) :fabric:runClientGameTest

lock: lock-tools ## Refresh every lockfile and the sha256 dependency verification after a version change
$(GRADLE) --write-verification-metadata sha256 --refresh-dependencies resolveAndLockAll buildEnvironment build --write-locks

stray: ## List JVMs a dev run or GameTest left behind
@$(CURDIR)/scripts/stray-java.sh || true

kill-stray: ## Stop JVMs a dev run or GameTest left behind
@$(CURDIR)/scripts/stray-java.sh --kill

run-fabric: ## Launch the Fabric dev client
$(GRADLE) :fabric:runClient

run-neoforge: ## Launch the NeoForge dev client
$(GRADLE) :neoforge:runClient

run-fabric-server: ## Launch the Fabric dev server (headless)
$(GRADLE) :fabric:runServer

run-neoforge-server: ## Launch the NeoForge dev server (headless)
$(GRADLE) :neoforge:runServer

clean: ## Remove build outputs
$(GRADLE) clean
include gradle.mk
44 changes: 44 additions & 0 deletions .devtools/gradle.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Managed by studiobimo/project-template (profile java-gradle); change it there. Include it
# from Makefile, after base.mk, and add the project's own targets (and their .PHONY) there.

GRADLE := cd $(ROOT) && JAVA_HOME="$$(/usr/libexec/java_home -v $$(cat .java-version) 2>/dev/null || echo $$JAVA_HOME)" ./gradlew

.PHONY: fmt test build check client-test lock stray kill-stray run-fabric run-neoforge run-fabric-server run-neoforge-server clean

fmt: ## Format Java sources
$(GRADLE) spotlessApply

test: ## Unit tests
$(GRADLE) :common:check

build: ## Compile, lint (Error Prone), test and package both loaders
$(GRADLE) build

check: lint build ## Everything CI runs

client-test: ## Run the client GameTest in a real client (opens a window, so it is not part of check)
$(GRADLE) :fabric:runClientGameTest

lock: lock-tools ## Refresh every lockfile and the sha256 dependency verification after a version change
$(GRADLE) --write-verification-metadata sha256 --refresh-dependencies resolveAndLockAll buildEnvironment build --write-locks

stray: ## List JVMs a dev run or GameTest left behind
@$(CURDIR)/scripts/stray-java.sh || true

kill-stray: ## Stop JVMs a dev run or GameTest left behind
@$(CURDIR)/scripts/stray-java.sh --kill

run-fabric: ## Launch the Fabric dev client
$(GRADLE) :fabric:runClient

run-neoforge: ## Launch the NeoForge dev client
$(GRADLE) :neoforge:runClient

run-fabric-server: ## Launch the Fabric dev server (headless)
$(GRADLE) :fabric:runServer

run-neoforge-server: ## Launch the NeoForge dev server (headless)
$(GRADLE) :neoforge:runServer

clean: ## Remove build outputs
$(GRADLE) clean
66 changes: 47 additions & 19 deletions .devtools/scripts/template-sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
# blocks inside files the project otherwise owns. Paths listed in this repo's
# .template-ignore are left alone.
#
# A project can also opt in to profiles, one per line in its .template-profiles. A
# profile is a second manifest, .template/manifest.<name>, whose files and blocks are
# read from .template/profiles/<name>/ instead of the template's root, so a language's
# files do not sit in every new project.
#
# Exit codes: 0 in step (or synced), 1 drift found by --check, 2 anything else.
# The template-drift workflow depends on that split, so a failure of this script is
# never mistaken for "no drift".
Expand All @@ -39,7 +44,7 @@ while [[ $# -gt 0 ]]; do
--check) mode=check; shift ;;
--from) from="${2:?--from needs a directory}"; shift 2 ;;
--ref) ref="${2:?--ref needs a tag}"; shift 2 ;;
-h | --help) sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
-h | --help) sed -n '2,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
Expand All @@ -63,8 +68,7 @@ if [[ -z "${from}" ]]; then
|| die "cannot clone ${url} at ${ref}"
fi

manifest="${from}/.template/manifest"
[[ -f "${manifest}" ]] || die "${from} has no .template/manifest; is it the template?"
[[ -f "${from}/.template/manifest" ]] || die "${from} has no .template/manifest; is it the template?"
if [[ -z "${ref}" ]]; then
ref="$(git -C "${from}" describe --tags --always 2>/dev/null || echo unknown)"
fi
Expand Down Expand Up @@ -107,8 +111,12 @@ report() {
fi
}

# Where the manifest being applied reads its files from: the template's root for the base
# manifest, a profile's directory for a profile.
src_root="${from}"

sync_file() {
local path="$1" src="${from}/$1"
local path="$1" src="${src_root}/$1"
[[ -f "${src}" ]] || die "manifest lists ${path}, but the template does not have it"

local same=true
Expand Down Expand Up @@ -140,7 +148,7 @@ sync_file() {
}

sync_block() {
local path="$1" name="$2" src="${from}/$1"
local path="$1" name="$2" src="${src_root}/$1"
[[ -f "${src}" ]] || die "manifest lists ${path}, but the template does not have it"

block_of "${src}" "${name}" >"${work}/theirs"
Expand Down Expand Up @@ -180,20 +188,40 @@ sync_block() {
changed=$((changed + 1))
}

while read -r kind path name _; do
case "${kind}" in
'' | '#'*) continue ;;
esac
if ignored "${path}"; then
echo "– ${path}: ignored by .template-ignore"
continue
fi
case "${kind}" in
file) sync_file "${path}" ;;
block) sync_block "${path}" "${name:?manifest: block ${path} needs a name}" ;;
*) die "manifest: unknown entry '${kind} ${path}'" ;;
esac
done <"${manifest}"
# apply_manifest <manifest>: sync, or check, every entry in it.
apply_manifest() {
local kind path name _
while read -r kind path name _; do
case "${kind}" in
'' | '#'*) continue ;;
esac
if ignored "${path}"; then
echo "– ${path}: ignored by .template-ignore"
continue
fi
case "${kind}" in
file) sync_file "${path}" ;;
block) sync_block "${path}" "${name:?manifest: block ${path} needs a name}" ;;
*) die "manifest: unknown entry '${kind} ${path}'" ;;
esac
done <"$1"
}

apply_manifest "${from}/.template/manifest"

# Profiles come after the base manifest, in the order the project lists them.
if [[ -f .template-profiles ]]; then
while read -r profile _; do
case "${profile}" in
'' | '#'*) continue ;;
esac
[[ "${profile}" =~ ^[a-z0-9][a-z0-9-]*$ ]] || die ".template-profiles: '${profile}' is not a profile name"
[[ -f "${from}/.template/manifest.${profile}" ]] \
|| die ".template-profiles lists '${profile}', but the template has no .template/manifest.${profile}"
src_root="${from}/.template/profiles/${profile}"
apply_manifest "${from}/.template/manifest.${profile}"
done <.template-profiles
fi

recorded="$(cat .template-version 2>/dev/null || true)"

Expand Down
9 changes: 7 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
*.zip binary
# <<< template:base

*.bat text eol=crlf
*.cmd text eol=crlf

gradlew text eol=lf
# >>> template:gradle
# The wrapper scripts are stored exactly as Gradle ships them: gradlew.bat with CRLF, gradlew with
# LF. If git converted them, the blob Dependabot commits on a wrapper bump would never match the
# repository's, and every check that compares the tree would report gradlew.bat as changed.
*.bat -text
gradlew text eol=lf
# <<< template:gradle

*.jar binary
*.ogg binary
Expand Down
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
version: 2
updates:
# >>> template:gradle
# Minecraft, Fabric and NeoForge versions are ported deliberately, so Dependabot ignores them.
- package-ecosystem: gradle
directory: /
schedule:
Expand Down Expand Up @@ -27,6 +29,7 @@ updates:
update-types: [version-update:semver-major]
- dependency-name: net.neoforged.moddev
update-types: [version-update:semver-major]
# <<< template:gradle

# >>> template:base
- package-ecosystem: github-actions
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: CI

# Thin wrapper around the org's Java/Gradle toolchain workflow. `check` is the
# same target `make -C .devtools check` runs, so local and CI results match.

on:
push:
branches: [main]
Expand All @@ -10,6 +13,8 @@ permissions: {}

jobs:
check:
# A called workflow can never hold more than its caller, so the caller has to
# grant what it needs. `permissions: {}` above keeps every other job at nothing.
permissions:
contents: read
uses: studiobimo/.github/.github/workflows/ci-java-gradle.yml@v1
Expand Down
1 change: 1 addition & 0 deletions .template-profiles
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
java-gradle
2 changes: 1 addition & 1 deletion .template-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.3.2
v0.4.1
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ weekly workflow reports as an issue.
| CI behaviour (lint, PR checks, release) | `studiobimo/.github`, `.github/workflows/` | the `@v1` tag moving |
| Branch and PR-size rules | `studiobimo/.github`, `.devtools/` | the `@v1` tag moving; lefthook refetches it daily |
| Shared hooks and tool versions | `studiobimo/project-template` | `make -C .devtools sync` |
| Files and blocks listed in the template's `.template/manifest` | `studiobimo/project-template` | `make -C .devtools sync` |
| Files and blocks listed in the template's `.template/manifest`, and in the manifest of each profile in `.template-profiles` | `studiobimo/project-template` | `make -C .devtools sync` |

A managed block sits between `>>> template:<name>` and `<<< template:<name>` marker lines, like
this section. Edit outside the markers freely; inside them, change the template instead. If a
Expand Down
2 changes: 2 additions & 0 deletions build-logic/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ plugins {
}

dependencies {
// Plugins applied by the convention scripts in src/main/groovy
implementation(libs.spotless.gradle)
implementation(libs.errorprone.gradle)
implementation(libs.mod.publish.gradle)
}

dependencyLocking {
Expand Down
10 changes: 8 additions & 2 deletions build-logic/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ com.squareup.okio:okio-jvm:3.6.0=runtimeClasspath,testRuntimeClasspath
com.squareup.okio:okio:3.6.0=runtimeClasspath,testRuntimeClasspath
commons-codec:commons-codec:1.22.1=runtimeClasspath,testRuntimeClasspath
dev.equo.ide:solstice:1.8.2=runtimeClasspath,testRuntimeClasspath
me.modmuss50:mod-publish-plugin:2.2.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.ltgt.gradle:gradle-errorprone-plugin:5.1.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.jgit:org.eclipse.jgit:7.8.0.202609011348-r=runtimeClasspath,testRuntimeClasspath
org.eclipse.platform:org.eclipse.osgi:3.24.300=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:2.3.20=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.9.10=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.3.20=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.11.0=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.11.0=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core:1.11.0=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.11.0=runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0=runtimeClasspath,testRuntimeClasspath
org.jetbrains:annotations:13.0=runtimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.18=runtimeClasspath,testRuntimeClasspath
org.tukaani:xz:1.9=runtimeClasspath,testRuntimeClasspath
Expand Down
19 changes: 17 additions & 2 deletions build-logic/src/main/groovy/multiloader-common.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Conventions shared by every subproject (common, fabric, neoforge).
plugins {
id 'java-library'
id 'com.diffplug.spotless'
Expand All @@ -7,6 +8,17 @@ plugins {
// Precompiled script plugins cannot use the type-safe accessor, so look the catalog up by name.
def libs = versionCatalogs.named('libs')

// Two values differ from one project to the next and live in gradle.properties:
// nullaway_package the package NullAway enforces nullness in
// external_source_path sources copied in from another repository, as a path under a source
// root (dev/bimo/model); Error Prone and Spotless leave them alone.
// Empty or unset when there are none.
def nullawayPackage = findProperty('nullaway_package')
if (!nullawayPackage) {
throw new GradleException('Set nullaway_package in gradle.properties (the package NullAway checks).')
}
def externalSourcePath = (findProperty('external_source_path') ?: '').toString()

// .java-version is read by Gradle, actions/setup-java (java-version-file), jenv, mise and asdf.
def javaVersion = rootProject.layout.projectDirectory.file('.java-version').asFile.text.trim() as int

Expand Down Expand Up @@ -57,9 +69,9 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs.addAll(['-Xlint:all,-processing,-serial,-classfile', '-Werror'])
options.errorprone {
disableWarningsInGeneratedCode = true
excludedPaths = '.*/build/generated/.*'
excludedPaths = externalSourcePath ? ".*/(build/generated|${externalSourcePath})/.*".toString() : '.*/build/generated/.*'
// Minecraft is not null-annotated; enforce nullness only in our packages.
option('NullAway:AnnotatedPackages', 'dev.bimo.tallyhopper')
option('NullAway:AnnotatedPackages', nullawayPackage.toString())
option('NullAway:JSpecifyMode', 'true')
error('NullAway')
}
Expand All @@ -74,6 +86,9 @@ tasks.named('compileTestJava', JavaCompile) {
spotless {
java {
target 'src/*/java/**/*.java'
if (externalSourcePath) {
targetExclude("src/main/java/${externalSourcePath}/**".toString())
}
palantirJavaFormat(libs.findVersion('palantir-java-format').get().requiredVersion)
removeUnusedImports()
formatAnnotations()
Expand Down
Loading
Loading