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
18 changes: 18 additions & 0 deletions .github/workflows/module-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,21 @@ jobs:
steps:
- uses: actions/checkout@v7
- uses: fsfe/reuse-action@v6

# A Module whose textures scripts/build-textures.py builds, such as those made from third-party
# art (NOTICE), commits what the script writes; --check fails when one differs. Without the
# script there is nothing to check.
textures:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- id: script
run: |
if [ -f scripts/build-textures.py ]; then echo "found=true" >> "$GITHUB_OUTPUT"; fi

- if: steps.script.outputs.found == 'true'
run: pip install pillow

- if: steps.script.outputs.found == 'true'
run: python3 scripts/build-textures.py --check
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Written for Consumers: what a mod building against Example Module can use, or wi

## Unreleased

- `module-ci.yml`, from the next `plugin-v` tag: a `textures` job runs `python3 scripts/build-textures.py --check`, with Pillow, when the Module has that script, and does nothing otherwise, so a Module drops its own texture job when it moves to that tag (#27). The template ships `NOTICE` with the preamble every Module's shares and no entries, and a commented `REUSE.toml` override stanza for third-party art such as Malcolm Riley's unused textures, which needs `LICENSES/CC-BY-4.0.txt` once uncommented.
- `libworks-plugin` 0.4.1: `upload` records each upload a site takes beside the jar, in `<archives_name>-<version>.uploads.json` in `~/.m2` with the site's id for it and when, and refuses a site already recorded before contacting it, so a `-Psite=curseforge` retry no longer uploads a version again while its first file waits in CurseForge's review, which the website's listing doesn't show (#22). The record only knows the machine that uploaded. A site's entry is removed by hand only when that site rejected the file or never showed it, and `-PdryRun` shows what the record holds (`docs/agents/releases.md`).
- `libworks-plugin` 0.4.0, breaking: the plugin declares the Module's `~/.m2` repository, `exclusiveContent` for the whole `io.github.5thlayer` group, so every 5thlayer module resolves from `~/.m2` alone and `~/.m2` serves nothing else (`docs/plugin.md`, #18). A Module that takes it deletes its own `~/.m2` repository, `mavenLocal { content { … } }` or `exclusiveContent { forRepository { mavenLocal() } … }`: the plugin refuses either, naming it. The template's `build.gradle` has none.
- The template's game-test code never ships (libworks ADR 0011, #17): `ExampleLibGameTests`, `CodeGameTest`, the test classes and `structure/gametest/platform.nbt` are in the `gametest` source set, and the jar holds none of them. `ExampleLibGameTests` is an `@EventBusSubscriber` that registers from `FMLConstructModEvent`, so `ExampleLib` names no game-test code, and nothing gates it: it registers in every one of the Module's own runs.
Expand Down
7 changes: 7 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Third-party material committed to this repository
==================================================

Each entry names the files, their author, where they came from and the licence they are under. A
file listed here is NOT covered by the licence this repository states for its own work; its own
licence governs it. Which licence covers which file is REUSE.toml's to say; this list is the
credits for what was copied in.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ libworks is becoming a dependency (ADR-0128, #8). It carries `runtime/`, a small
- A GameTest harness: `runGameTestServer` runs the Module's game tests headless and fails if it ran none. `buildGametestStructures` writes the stone platform they stand on.
- JUnit tests on a plain JVM, with no Minecraft.
- `sh ./gradlew release -PreleaseVersion=<version>`, which releases a version to `~/.m2` and tags it; a published version never changes. The `skillworks:quicklaunch` skill opens the dev client into the most recent save in `run/saves`, one client per checkout.
- CI on every push, through libworks' `module-ci.yml` at the plugin's tag: the build, the JUnit tests, the game tests, and a REUSE lint. It never publishes.
- MIT under REUSE, `CLAUDE.md`, a `GLOSSARY.md` stub, `docs/agents/`, conventional commits.
- CI on every push, through libworks' `module-ci.yml` at the plugin's tag: the build, the JUnit tests, the game tests, a REUSE lint, and `scripts/build-textures.py --check` when the Module has that script. It never publishes.
- MIT under REUSE, with a `NOTICE` for the credits of third-party material and a commented `REUSE.toml` stanza for its licence, `CLAUDE.md`, a `GLOSSARY.md` stub, `docs/agents/`, conventional commits.
- The engineering skills from [mattpocock/skills](https://github.com/mattpocock/skills), a submodule at `vendor/mattpocock-skills` pinned to a release and linked into `.claude/skills/`.
- ADR 0001, the versioning rule every Module inherits: below 1.0 an addition bumps the patch.

Expand All @@ -23,7 +23,7 @@ libworks is becoming a dependency (ADR-0128, #8). It carries `runtime/`, a small

## Adopting it in an existing Module

A Module that predates the template is not regenerated. Diff it against the template and take the template's version of each shared piece: `settings.gradle` and `build.gradle` applying the plugin in place of its own build logic and `scripts/`, the game test harness (`CodeGameTest` and the `*GameTests` registrar), `REUSE.toml`, `docs/agents/`, `.github/workflows/`, and the skills submodule with its links. Its own versioning ADR becomes a pointer to libworks' ADR 0001.
A Module that predates the template is not regenerated. Diff it against the template and take the template's version of each shared piece: `settings.gradle` and `build.gradle` applying the plugin in place of its own build logic and `scripts/`, the game test harness (`CodeGameTest` and the `*GameTests` registrar), `REUSE.toml` and `NOTICE`'s preamble, `docs/agents/`, `.github/workflows/`, and the skills submodule with its links. Its own versioning ADR becomes a pointer to libworks' ADR 0001.

## Changing the template

Expand Down
23 changes: 23 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,34 @@ path = [
"**/src/gametest/resources/**/*.nbt",
"**/src/main/resources/**/*.nbt",
"LICENSE",
"NOTICE",
".github/**",
]
SPDX-FileCopyrightText = "2026 5thlayer"
SPDX-License-Identifier = "MIT"

# Third-party art, such as Malcolm Riley's unused textures
# (https://github.com/malcolmriley/unused-textures), takes an override stanza of its own and an
# entry in NOTICE that credits it. Unmodified copies, those a texture is built from under data/art/
# included, are the author's; a texture scripts/build-textures.py builds from them is both. Module
# CI runs that script's --check when it exists. Uncommenting a stanza needs the licence's text in
# LICENSES/ (reuse download CC-BY-4.0), which reuse lint refuses while no file uses it.
#
# [[annotations]]
# path = [
# "data/art/*.png",
# "src/main/resources/assets/examplelib/textures/block/plating/*.png",
# ]
# precedence = "override"
# SPDX-FileCopyrightText = "Malcolm Riley"
# SPDX-License-Identifier = "CC-BY-4.0"
#
# [[annotations]]
# path = ["src/main/resources/assets/examplelib/textures/block/machine/front.png"]
# precedence = "override"
# SPDX-FileCopyrightText = ["Malcolm Riley", "2026 5thlayer"]
# SPDX-License-Identifier = "CC-BY-4.0"

# The Gradle wrapper is Gradle's.
[[annotations]]
path = ["gradlew", "gradlew.bat", "gradle/wrapper/**"]
Expand Down
2 changes: 2 additions & 0 deletions docs/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ plugins { id 'io.github.5thlayer.libworks.module' version '0.1.2' }

Its CI is `.github/workflows/module-ci.yml`, called at the same tag: `uses: 5thlayer/libworks/.github/workflows/module-ci.yml@plugin-v<version>`. `fill-template.sh` writes both at the version `plugin/gradle.properties` holds. `module-ci.yml` reads the version from that `uses:` ref and fails when `build.gradle` applies another, so the two move together.

Beside the build and `reuse lint`, it runs `python3 scripts/build-textures.py --check`, with Pillow, when the Module has that script, and skips it otherwise. The script is the Module's own: it builds the textures it commits, typically from third-party art kept unmodified under `data/art/`, and `--check` fails when a committed one differs from what it builds. The art is credited in `NOTICE` and licensed by an override stanza in `REUSE.toml`, whose commented example the template ships.

An upstream is declared as `upstream('<name>') { range = '[0.5.4,0.6)' }`, with `tagPrefix` when its tags are not `<name>-v<version>`. The range's lower bound is the version the Module prefers and the one CI builds from its tag, so it must be a released version.

An upstream with `optional = true` goes on `localRuntime`, a configuration the plugin creates (or takes over, when the Module declares one) and `runtimeClasspath` extends: the dev runs load it, and the published jar, pom and module metadata do not depend on it. `libworksUpstreams` still lists it, so CI builds its tag, and `-PsiblingBuilds` still checks its range.
Expand Down
6 changes: 6 additions & 0 deletions scripts/tests/test_fill_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ def test_reuse_names_no_path_the_fill_removes(self):
self.assertNotIn("plugin/", reuse)
self.assertNotIn('"scripts/', reuse)

def test_the_module_keeps_notice_with_no_entries_and_reuse_covers_it(self):
notice = self.text("NOTICE")
self.assertTrue(notice.startswith("Third-party material committed to this repository\n"), notice)
self.assertNotIn("Files:", notice)
self.assertRegex(self.text("REUSE.toml"), r'(?m)^ "NOTICE",$')

def test_the_changelog_keeps_its_header_and_an_empty_unreleased(self):
changelog = self.text("CHANGELOG.md")
self.assertTrue(changelog.startswith("# Changelog\n"), changelog)
Expand Down
Loading