build: make the conformance audit part of check, and gate the branch on it
Phase 8, the first two items. The audit has existed since phase 0 and has been run by hand at the end of every phase since, which is exactly the arrangement it was written to end: a budget nobody checks at the moment the number moves is a number that drifts. **`:composeApp:m3Audit`, wired into `check`.** It shells out to `docs/scripts/m3-audit.sh --check` and fails the build when a budget is exceeded or a floor is undercut. Verified to bite: adding one `Color(0xFFAABBCC)` to `LoadingScreen.kt` reports `hardcoded Color outside theme/ 1 over budget 0` and takes the build down with it. The task declares the script and the ui source tree as inputs and a marker file as its output, so it is up-to-date-able rather than re-running on every `check`. On a machine with no bash it warns and skips instead of failing, because a build that dies for a reason unrelated to the change under it teaches people to pass `-x`. **A Gitea Actions workflow**, since the remote is a Gitea 1.25 instance. Two jobs, deliberately: - `budgets` is grep over the source tree -- no gradle, no android SDK, no submodules, no network. This job is the reason the audit is a shell script rather than a gradle plugin, and it should stay runnable on a bare container. - `tests` needs a compiler and therefore the whole composite chain: four levels of submodule and a cross-compile of secp256k1's C sources, so a cold run is minutes rather than seconds. Split out so a runner can be pointed at `budgets` alone where that is all the capacity there is. Its two non-obvious steps carry the reasons at the site -- `submodules: recursive` or configuration fails with `Project with path ':library' not found`, and the android SDK is needed even for a jvm-only test run because `:secp256k1-kmp:jni:android` is in the graph. **The workflow is unverified**, and that is worth saying plainly: this repository has had no CI of any kind, so there is no runner registered to try it against. The syntax is valid and the commands are the ones used by hand throughout this work. The gradle task is the half that is proven, and it is the half that runs on every developer machine regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
62
.gitea/workflows/material-design-conformance.yml
Normal file
62
.gitea/workflows/material-design-conformance.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
# Material Design conformance, as a gate rather than a habit.
|
||||
#
|
||||
# docs/material-design-conformance.md drove nine classes of defect to zero across eight
|
||||
# phases. Every one of them is the kind that comes back one call site at a time -- a
|
||||
# hardcoded colour on a screen somebody was in a hurry on, a `10.dp` typed rather than
|
||||
# reached for -- and none of them is visible in a diff unless a reviewer is looking for it.
|
||||
# The budgets in docs/scripts/m3-audit.sh are what look.
|
||||
#
|
||||
# Gitea Actions, because the remote is a Gitea instance. The syntax is GitHub Actions'; a
|
||||
# runner has to be registered against the repository for either job to run at all.
|
||||
name: Material Design conformance
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [mantra]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
# Grep over the source tree. No gradle, no android SDK, no submodules, and no network:
|
||||
# this job is why the audit is a shell script rather than a gradle plugin, and it should
|
||||
# stay runnable on a bare container.
|
||||
budgets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Check the conformance budgets
|
||||
run: docs/scripts/m3-audit.sh --check
|
||||
|
||||
# The assertions that need a compiler. Much heavier than the job above: the composite
|
||||
# build reaches four levels of submodule and cross-compiles secp256k1's C sources, so a
|
||||
# cold run is minutes rather than seconds. Split out so a runner can be pointed at
|
||||
# `budgets` alone where that is all the capacity there is for.
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# The chain is lightning-kmp-app -> experimental/lightning-kmp ->
|
||||
# experimental/bitcoin-kmp -> experimental/secp256k1-kmp -> native/secp256k1.
|
||||
# Without every level, gradle fails during configuration with
|
||||
# "Project with path ':library' not found", which reads like a build script
|
||||
# error and is not one.
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
|
||||
# :secp256k1-kmp:jni:android is an android library module, so the SDK has to be
|
||||
# present even for a jvm-only test run -- the failure otherwise is
|
||||
# "SDK location not found" during configuration.
|
||||
- uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Theme, layout and motion tests
|
||||
run: ./gradlew :composeApp:jvmTest --no-daemon
|
||||
|
||||
- name: Android compilation
|
||||
run: ./gradlew :composeApp:compileDebugKotlinAndroid --no-daemon
|
||||
@@ -221,6 +221,69 @@ room3 {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
/**
|
||||
* The Material Design conformance audit, as a build task.
|
||||
*
|
||||
* `docs/scripts/m3-audit.sh --check` counts what the phases in
|
||||
* docs/material-design-conformance.md drove to zero -- hardcoded colours, dp literals in
|
||||
* spacing positions, bare `.clickable`, title case, untriaged `contentDescription = null`
|
||||
* -- and exits 1 when one of them has come back. It also holds two floors, for the
|
||||
* adaptive and navigation work, which regress by being *removed*.
|
||||
*
|
||||
* Wired into `check` rather than left as a script somebody remembers to run: the whole
|
||||
* point of a budget is that it is enforced at the moment the number moves, and a number
|
||||
* that is only checked when a person thinks to look is a number that drifts.
|
||||
*
|
||||
* It reads the source tree with grep and needs no gradle, no android SDK and no
|
||||
* submodules, so it is also the one part of this build that a bare CI runner can do.
|
||||
*/
|
||||
val m3Audit = tasks.register("m3Audit") {
|
||||
group = "verification"
|
||||
description = "Checks the Material Design conformance budgets in docs/material-design-conformance.md."
|
||||
|
||||
val script = rootProject.layout.projectDirectory.file("docs/scripts/m3-audit.sh").asFile
|
||||
val uiSources = rootProject.layout.projectDirectory
|
||||
.dir("composeApp/src/commonMain/kotlin/press/mantra/compose/ui")
|
||||
val projectDirectory = rootProject.layout.projectDirectory.asFile
|
||||
|
||||
inputs.file(script).withPropertyName("auditScript")
|
||||
inputs.dir(uiSources).withPropertyName("uiSources")
|
||||
// No outputs, so this would run every time. A marker file is what makes it
|
||||
// up-to-date-able, and it is the only thing the task writes.
|
||||
val marker = layout.buildDirectory.file("m3-audit/passed.txt")
|
||||
outputs.file(marker)
|
||||
|
||||
doLast {
|
||||
// Windows has no bash unless somebody installed one. Skipping loudly beats
|
||||
// failing a build for a reason that has nothing to do with the change under it;
|
||||
// the CI runner and every developer machine here are unix.
|
||||
val bash = listOf("/bin/bash", "/usr/bin/bash").firstOrNull { File(it).canExecute() }
|
||||
if (bash == null) {
|
||||
logger.warn("m3Audit: no bash found, skipping. Run docs/scripts/m3-audit.sh --check by hand.")
|
||||
marker.get().asFile.apply { parentFile.mkdirs() }.writeText("skipped: no bash\n")
|
||||
return@doLast
|
||||
}
|
||||
|
||||
val result = providers.exec {
|
||||
commandLine(bash, script.absolutePath, "--check")
|
||||
workingDir = projectDirectory
|
||||
isIgnoreExitValue = true
|
||||
}
|
||||
val text = result.standardOutput.asText.get()
|
||||
val exit = result.result.get().exitValue
|
||||
logger.lifecycle(text)
|
||||
if (exit != 0) {
|
||||
throw GradleException(
|
||||
"Material Design conformance budgets exceeded. See the report above and " +
|
||||
"docs/material-design-conformance.md."
|
||||
)
|
||||
}
|
||||
marker.get().asFile.apply { parentFile.mkdirs() }.writeText(text)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("check") { dependsOn(m3Audit) }
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "press.mantra.desktop.MainKt"
|
||||
|
||||
Reference in New Issue
Block a user