diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index 616388d37..1f6477d52 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -15,7 +15,7 @@ jobs: with: java-version: 8 - - name: Cache gradle dependencies + - name: Cache Gradle dependencies uses: actions/cache@v2 with: path: ~/.gradle/caches @@ -32,7 +32,7 @@ jobs: run: | ./gradlew assemble || ./gradlew assemble ./gradlew downloadAssets || ./gradlew downloadAssets - xvfb-run ./gradlew build + ./gradlew build - name: Upload Jar uses: actions/upload-artifact@v2 @@ -40,31 +40,12 @@ jobs: name: CC-Tweaked path: build/libs - - name: Upload Screnshots - uses: actions/upload-artifact@v2 - with: - name: Screenshots - path: test-files/client/screenshots - if-no-files-found: ignore - retention-days: 5 - if: failure() - - - name: Upload Coverage + - name: Upload coverage uses: codecov/codecov-action@v2 - name: Parse test reports run: ./tools/parse-reports.py if: ${{ failure() }} - - name: Cache pre-commit - uses: actions/cache@v2 - with: - path: ~/.cache/pre-commit - key: ${{ runner.os }}-pre-commit-${{ hashFiles('config/pre-commit/config.yml') }} - restore-keys: | - ${{ runner.os }}-pre-commit- - - name: Run linters - run: | - pip install pre-commit - pre-commit run --config config/pre-commit/config.yml --show-diff-on-failure --all --color=always + uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/make-doc.yml b/.github/workflows/make-doc.yml index 4e53e42df..cff59218f 100644 --- a/.github/workflows/make-doc.yml +++ b/.github/workflows/make-doc.yml @@ -26,12 +26,6 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Setup illuaminate - run: | - test -d bin || mkdir bin - test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate - chmod +x bin/illuaminate - - name: Setup node run: npm ci diff --git a/.gitignore b/.gitignore index 808032e6d..8779a2d80 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /classes /logs /build +/buildSrc/build /out /doc/out/ /node_modules diff --git a/.gitpod.yml b/.gitpod.yml index c83ce9f6c..8fadff01b 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -17,6 +17,6 @@ vscode: tasks: - name: Setup pre-commit hool - init: pre-commit install --config config/pre-commit/config.yml --allow-missing-config + init: pre-commit install --allow-missing-config - name: Install npm packages init: npm ci diff --git a/config/pre-commit/config.yml b/.pre-commit-config.yaml similarity index 94% rename from config/pre-commit/config.yml rename to .pre-commit-config.yaml index e14e36ca8..f23c2af20 100644 --- a/config/pre-commit/config.yml +++ b/.pre-commit-config.yaml @@ -41,8 +41,8 @@ repos: - id: illuaminate name: Check Lua code files: ".*\\.(lua|java|md)" - language: script - entry: config/pre-commit/illuaminate-lint.sh + language: system + entry: ./gradlew lintLua -i pass_filenames: false require_serial: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bae4f7b4f..65ee93263 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,40 +39,30 @@ ### Code linters - **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or `./gradle check`. - - **[illuaminate]:** Checks Lua code for semantic and styleistic issues. See [the usage section][illuaminate-usage] for - how to download and run it. You may need to generate the Java documentation stubs (see "Documentation" below) for all - lints to pass. + - **[illuaminate]:** Checks Lua code for semantic and styleistic issues. This can be run with `./gradlew lintLua`. ### Documentation When writing documentation for [CC: Tweaked's documentation website][docs], it may be useful to build the documentation and preview it yourself before submitting a PR. -Building all documentation is, sadly, a multi-stage process (though this is largely hidden by Gradle). First we need to -convert Java doc-comments into Lua ones, we also generate some Javascript to embed. All of this is then finally fed into -illuaminate, which spits out our HTML. +Our documentation generation pipeline is rather complex, and involves invoking several external tools. Most of this +complexity is hidden by Gradle, but you will need to perform some initial setup: -#### Setting up the tooling -For various reasons, getting the environment set up to build documentation can be pretty complex. I'd quite like to -automate this via Docker and/or nix in the future, but this needs to be done manually for now. + - Install [Node/npm][node]. + - Run `npm ci` to install our Node dependencies. -This tooling is only needed if you need to build the whole website. If you just want to generate the Lua stubs, you can -skp this section. - - Install Node/npm and install our Node packages with `npm ci`. - - Install [illuaminate][illuaminate-usage] as described above. - -#### Building documentation -Gradle should be your entrypoint to building most documentation. There's two tasks which are of interest: - - - `./gradlew luaJavadoc` - Generate documentation stubs for Java methods. - - `./gradlew docWebsite` - Generate the whole website (including Javascript pages). The resulting HTML is stored at - `./build/docs/site/`. +You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting +HTML into `./build/docs/site`. #### Writing documentation illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as [ldoc][ldoc]. Documentation comments are written in Markdown, Our markdown engine does _not_ support GitHub flavoured markdown, and so does not support all the features one might -expect (such as tables). It is very much recommended that you build and preview the docs locally first. +expect. It is recommended that you build and preview the docs locally first. + +When iterating on documentation, you can get Gradle to rebuild the website every time a file changes by running +`./gradlew docWebsite -t`. This will take a couple of seconds to run, but definitely beats running it manually! ### Testing Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the @@ -90,11 +80,10 @@ ### Testing These tests are run by the '"Core" Java' test suite, and so are also run with `./gradlew test`. - - In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server and client, - using [the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals. + - In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server, using + the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals. - These are run by `./gradlew testClient` and `./gradlew testServer`. You may want to run the client under `xvfb-run` - or similar when running in a headless environment. + These tests are run with `./gradlew testServer`. ## CraftOS tests CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of @@ -107,9 +96,9 @@ ## CraftOS tests [community]: README.md#Community "Get in touch with the community." [checkstyle]: https://checkstyle.org/ [illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub" -[illuaminate-usage]: https://github.com/SquidDev/illuaminate/blob/master/README.md#usage "Installing Illuaminate" [weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance" [docs]: https://tweaked.cc/ "CC: Tweaked documentation" [ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator." [mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg [busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing." +[node]: https://nodejs.org/en/ "Node.js" diff --git a/build.gradle b/build.gradle index 896a2af2a..a680abdb1 100644 --- a/build.gradle +++ b/build.gradle @@ -11,9 +11,12 @@ id "org.spongepowered.mixin" version "0.7.+" id "org.parchmentmc.librarian.forgegradle" version "1.+" id "com.github.johnrengelman.shadow" version "7.1.2" + id "cc-tweaked.illuaminate" } import org.apache.tools.ant.taskdefs.condition.Os +import cc.tweaked.gradle.IlluaminateExec +import cc.tweaked.gradle.IlluaminateExecToDir version = mod_version @@ -152,6 +155,10 @@ accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg') cctJavadoc 'cc.tweaked:cct-javadoc:1.4.7' } +illuaminate { + version.set("0.1.0-3-g0f40379") +} + // Compile tasks javadoc { @@ -302,32 +309,36 @@ List mkCommand(String command) { commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js') } -def illuaminateDocs = tasks.register("illuaminateDocs", Exec.class) { +def illuaminateDocs = tasks.register("illuaminateDocs", IlluaminateExecToDir.class) { group = "documentation" description = "Generates docs using Illuaminate" - dependsOn(rollup, luaJavadoc) + dependsOn(rollup) - inputs.files(fileTree("doc")).withPropertyName("docs") - inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom") + // Config files inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp") - inputs.dir("$buildDir/docs/luaJavadoc") + // Sources + inputs.files(fileTree("doc")).withPropertyName("docs") + inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom") + inputs.files(luaJavadoc) + // Additional assets inputs.file("$buildDir/rollup/index.js").withPropertyName("scripts") inputs.file("src/web/styles.css").withPropertyName("styles") - outputs.dir("$buildDir/docs/lua") - commandLine mkCommand('"bin/illuaminate" doc-gen') + // Output directory. Also defined in illuaminate.sexp and transform.tsx + output.set(new File(buildDir, "docs/lua")) + + args = ["doc-gen"] } def jsxDocs = tasks.register("jsxDocs", Exec) { group = "documentation" description = "Post-processes documentation to statically render some dynamic content." - dependsOn(illuaminateDocs) inputs.files(fileTree("src/web")).withPropertyName("sources") inputs.file("src/generated/export/index.json").withPropertyName("export") inputs.file("package-lock.json").withPropertyName("package-lock.json") inputs.file("tsconfig.json").withPropertyName("Typescript config") - inputs.files(fileTree("$buildDir/docs/lua")) + inputs.files(illuaminateDocs) outputs.dir("$buildDir/docs/site") commandLine mkCommand('"node_modules/.bin/ts-node" -T --esm src/web/transform.tsx') @@ -391,6 +402,24 @@ commandLine mkCommand('"node_modules/.bin/ts-node" -T --esm src/web/transform.ts check.dependsOn("licenseCheck") +def lintLua = tasks.register("lintLua", IlluaminateExec.class) { + group = JavaBasePlugin.VERIFICATION_GROUP + description = "Lint Lua (and Lua docs) with illuaminate" + + // Config files + inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp") + // Sources + inputs.files(fileTree("doc")).withPropertyName("docs") + inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom") + inputs.files(luaJavadoc) + + args = ["lint"] + + doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") } + doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") } +} + + def setupServer = tasks.register("setupServer", Copy.class) { group "test server" description "Sets up the environment for the test server." diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..fc582ed5b --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `java-gradle-plugin` + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +gradlePlugin { + plugins { + register("cc-tweaked.illuaminate") { + id = "cc-tweaked.illuaminate" + implementationClass = "cc.tweaked.gradle.IlluaminatePlugin" + } + } +} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/ExecTasks.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/ExecTasks.kt new file mode 100644 index 000000000..10cf5af9a --- /dev/null +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/ExecTasks.kt @@ -0,0 +1,11 @@ +package cc.tweaked.gradle + +import org.gradle.api.provider.Property +import org.gradle.api.tasks.AbstractExecTask +import org.gradle.api.tasks.OutputDirectory +import java.io.File + +abstract class ExecToDir : AbstractExecTask(ExecToDir::class.java) { + @get:OutputDirectory + abstract val output: Property +} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/Illuaminate.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/Illuaminate.kt new file mode 100644 index 000000000..5ccc701a7 --- /dev/null +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/Illuaminate.kt @@ -0,0 +1,121 @@ +package cc.tweaked.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Dependency +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.AbstractExecTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import java.io.File + +abstract class IlluaminateExtension { + /** The version of illuaminate to use. */ + abstract val version: Property + + /** The path to illuaminate. If not given, illuaminate will be downloaded automatically. */ + abstract val file: Property +} + +class IlluaminatePlugin : Plugin { + override fun apply(project: Project) { + val extension = project.extensions.create("illuaminate", IlluaminateExtension::class.java) + extension.file.convention(setupDependency(project, extension.version)) + + project.tasks.register(SetupIlluaminate.NAME, SetupIlluaminate::class.java) { + file.set(extension.file.map { it.absolutePath }) + } + } + + /** Set up a repository for illuaminate and download our binary from it. */ + private fun setupDependency(project: Project, version: Provider): Provider { + project.repositories.ivy { + name = "Illuaminate" + setUrl("https://squiddev.cc/illuaminate/bin/") + patternLayout { + artifact("[revision]/[artifact]-[ext]") + } + metadataSources { + artifact() + } + content { + includeModule("cc.squiddev", "illuaminate") + } + } + + return version.map { + val dep = illuaminateArtifact(project, it) + val configuration = project.configurations.detachedConfiguration(dep) + configuration.isTransitive = false + configuration.resolve().single() + } + } + + /** Define a dependency for illuaminate from a version number and the current operating system. */ + private fun illuaminateArtifact(project: Project, version: String): Dependency { + val osName = System.getProperty("os.name").toLowerCase() + val (os, suffix) = when { + osName.contains("windows") -> Pair("windows", ".exe") + osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "") + osName.contains("linux") -> Pair("linux", "") + else -> error("Unsupported OS $osName for illuaminate") + } + + val osArch = System.getProperty("os.arch").toLowerCase() + val arch = when { + osArch == "arm" || osArch.startsWith("aarch") -> error("Unsupported architecture '$osArch' for illuaminate") + osArch.contains("64") -> "x86_64" + else -> error("Unsupported architecture $osArch for illuaminate") + } + + return project.dependencies.create( + mapOf( + "group" to "cc.squiddev", + "name" to "illuaminate", + "version" to version, + "ext" to "$os-$arch$suffix", + ), + ) + } +} + +private val Task.illuaminatePath: String? // "?" needed to avoid overload ambiguity in setExecutable below. + get() = project.extensions.getByType(IlluaminateExtension::class.java).file.get().absolutePath + +/** Prepares illuaminate for being run. This simply requests the dependency and then marks it as executable. */ +abstract class SetupIlluaminate : DefaultTask() { + @get:Input + abstract val file: Property + + @TaskAction + fun setExecutable() { + val file = File(this.file.get()) + if (file.canExecute()) { + didWork = false + return + } + + file.setExecutable(true) + } + + companion object { + const val NAME: String = "setupIlluaminate" + } +} + +abstract class IlluaminateExec : AbstractExecTask(IlluaminateExec::class.java) { + init { + dependsOn(SetupIlluaminate.NAME) + executable = illuaminatePath + } +} + +abstract class IlluaminateExecToDir : ExecToDir() { + init { + dependsOn(SetupIlluaminate.NAME) + executable = illuaminatePath + } +} diff --git a/config/pre-commit/illuaminate-lint.sh b/config/pre-commit/illuaminate-lint.sh deleted file mode 100755 index ac7b762bf..000000000 --- a/config/pre-commit/illuaminate-lint.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env sh -set -e - -test -d bin || mkdir bin -test -f bin/illuaminate || curl -s -obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate -chmod +x bin/illuaminate - -if [ -n ${GITHUB_ACTIONS+x} ]; then - # Register a problem matcher (see https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md) - # for illuaminate. - echo "::add-matcher::.github/matchers/illuaminate.json" - trap 'echo "::remove-matcher owner=illuaminate::"' EXIT -fi - -./gradlew luaJavadoc -bin/illuaminate lint diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fcea..ae04661ee 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists