buildscript { repositories { jcenter() mavenCentral() maven { name = "forge" url = "https://files.minecraftforge.net/maven" } } dependencies { classpath 'com.google.code.gson:gson:2.8.1' classpath 'net.minecraftforge.gradle:ForgeGradle:4.0.16' classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2' } } plugins { id "checkstyle" id "jacoco" id "com.github.hierynomus.license" version "0.15.0" id "com.matthewprenger.cursegradle" version "1.4.0" id "com.github.breadmoirai.github-release" version "2.2.12" id "org.jetbrains.kotlin.jvm" version "1.3.72" } apply plugin: 'net.minecraftforge.gradle' apply plugin: 'maven-publish' apply plugin: 'maven' version = mod_version group = "org.squiddev" archivesBaseName = "cc-tweaked-${mc_version}" java { toolchain { languageVersion = JavaLanguageVersion.of(8) } } minecraft { runs { client { workingDirectory project.file('run') property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' mods { computercraft { source sourceSets.main } } } server { workingDirectory project.file("run/server") property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' arg "--nogui" mods { computercraft { source sourceSets.main } } } data { workingDirectory project.file('run') property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' args '--mod', 'computercraft', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') mods { computercraft { source sourceSets.main } } } testServer { workingDirectory project.file('test-files/server') parent runs.server mods { cctest { source sourceSets.test } } } } mappings channel: 'official', version: project.mc_version accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg') } sourceSets { main.resources { srcDir 'src/generated/resources' } } repositories { mavenCentral() maven { name "SquidDev" url "https://squiddev.cc/maven" } } configurations { shade compile.extendsFrom shade deployerJars cctJavadoc } dependencies { checkstyle "com.puppycrawl.tools:checkstyle:8.25" minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}" compileOnly fg.deobf("mezz.jei:jei-1.15.2:6.0.0.3:api") compileOnly fg.deobf("com.blamejared.crafttweaker:CraftTweaker-1.15.2:6.0.0.9") runtimeOnly fg.deobf("mezz.jei:jei-1.15.2:6.0.0.3") compileOnly 'com.google.auto.service:auto-service:1.0-rc7' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72' testImplementation 'org.jetbrains.kotlin:kotlin-reflect:1.3.72' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8' deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0" cctJavadoc 'cc.tweaked:cct-javadoc:1.3.0' } // Compile tasks javadoc { include "dan200/computercraft/api/**/*.java" } task luaJavadoc(type: Javadoc) { description "Generates documentation for Java-side Lua functions." group "documentation" source = sourceSets.main.allJava destinationDir = file("${project.docsDir}/luaJavadoc") classpath = sourceSets.main.compileClasspath options.docletpath = configurations.cctJavadoc.files as List options.doclet = "cc.tweaked.javadoc.LuaDoclet" options.noTimestamp = false javadocTool = javaToolchains.javadocToolFor { languageVersion = JavaLanguageVersion.of(11) } } jar { dependsOn javadoc manifest { attributes(["Specification-Title": "computercraft", "Specification-Vendor": "SquidDev", "Specification-Version": "1", "Implementation-Title": "CC: Tweaked", "Implementation-Version": "${mod_version}", "Implementation-Vendor" :"SquidDev", "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")]) } from (sourceSets.main.allSource) { include "dan200/computercraft/api/**/*.java" } from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) } } [compileJava, compileTestJava].forEach { it.configure { options.compilerArgs << "-Xlint" << "-Xlint:-processing" } } import java.nio.charset.StandardCharsets import java.nio.file.* import java.util.zip.* import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.hierynomus.gradle.license.tasks.LicenseCheck import com.hierynomus.gradle.license.tasks.LicenseFormat import proguard.gradle.ProGuardTask task proguard(type: ProGuardTask, dependsOn: jar) { description "Removes unused shadowed classes from the jar" group "compact" injars jar.archivePath outjars "${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar" // Add the main runtime jar and all non-shadowed dependencies libraryjars "${System.getProperty('java.home')}/lib/rt.jar" libraryjars "${System.getProperty('java.home')}/lib/jce.jar" doFirst { sourceSets.main.compileClasspath .filter { !it.name.contains("Cobalt") } .each { libraryjars it } } // We want to avoid as much obfuscation as possible. We're only doing this to shrink code size. dontobfuscate; dontoptimize; keepattributes; keepparameternames // Proguard will remove directories by default, but that breaks JarMount. keepdirectories 'data/computercraft/lua**' // Preserve ComputerCraft classes - we only want to strip shadowed files. keep 'class dan200.computercraft.** { *; }' // LWJGL and Apache bundle Java 9 versions, which is great, but rather breaks Proguard dontwarn 'module-info' dontwarn 'org.apache.**,org.lwjgl.**' } task proguardMove(dependsOn: proguard) { description "Replace the original jar with the minified version" group "compact" doLast { Files.move( file("${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar").toPath(), file(jar.archivePath).toPath(), StandardCopyOption.REPLACE_EXISTING ) } } processResources { inputs.property "version", mod_version inputs.property "mcversion", mc_version def hash = 'none' Set contributors = [] try { hash = ["git", "-C", projectDir, "rev-parse", "HEAD"].execute().text.trim() def blacklist = ['GitHub', 'dan200', 'Daniel Ratcliffe'] ["git", "-C", projectDir, "log", "--format=tformat:%an%n%cn"].execute().text.split('\n').each { if (!blacklist.contains(it)) contributors.add(it) } } catch(Exception e) { e.printStackTrace() } inputs.property "commithash", hash from(sourceSets.main.resources.srcDirs) { include 'META-INF/mods.toml' include 'data/computercraft/lua/rom/help/credits.txt' expand 'version': mod_version, 'mcversion': mc_version, 'gitcontributors': contributors.sort(false, String.CASE_INSENSITIVE_ORDER).join('\n') } from(sourceSets.main.resources.srcDirs) { exclude 'META-INF/mods.toml' exclude 'data/computercraft/lua/rom/help/credits.txt' } } task compressJson(dependsOn: jar) { group "compact" description "Minifies all JSON files, stripping whitespace" def jarPath = file(jar.archivePath) def tempPath = File.createTempFile("input", ".jar", temporaryDir) tempPath.deleteOnExit() def gson = new GsonBuilder().create() doLast { // Copy over all files in the current jar to the new one, running json files from GSON. As pretty printing // is turned off, they should be minified. new ZipFile(jarPath).withCloseable { inJar -> tempPath.getParentFile().mkdirs() new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempPath))).withCloseable { outJar -> inJar.entries().each { entry -> if(entry.directory) { outJar.putNextEntry(entry) } else if(!entry.name.endsWith(".json")) { outJar.putNextEntry(entry) inJar.getInputStream(entry).withCloseable { outJar << it } } else { ZipEntry newEntry = new ZipEntry(entry.name) newEntry.setTime(entry.time) outJar.putNextEntry(newEntry) def element = inJar.getInputStream(entry).withCloseable { gson.fromJson(it.newReader("UTF8"), JsonElement.class) } outJar.write(gson.toJson(element).getBytes(StandardCharsets.UTF_8)) } } } } // And replace the original jar again Files.move(tempPath.toPath(), jarPath.toPath(), StandardCopyOption.REPLACE_EXISTING) } } assemble.dependsOn compressJson // Web tasks import org.apache.tools.ant.taskdefs.condition.Os List mkCommand(String command) { return Os.isFamily(Os.FAMILY_WINDOWS) ? ["cmd", "/c", command] : ["sh", "-c", command] } task rollup(type: Exec) { group = "build" description = "Bundles JS into rollup" inputs.files(fileTree("src/web")).withPropertyName("sources") inputs.file("package-lock.json").withPropertyName("package-lock.json") inputs.file("tsconfig.json").withPropertyName("Typescript config") inputs.file("rollup.config.js").withPropertyName("Rollup config") outputs.file("$buildDir/rollup/index.js").withPropertyName("output") commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js') } task minifyWeb(type: Exec, dependsOn: rollup) { group = "build" description = "Bundles JS into rollup" inputs.file("$buildDir/rollup/index.js").withPropertyName("sources") inputs.file("package-lock.json").withPropertyName("package-lock.json") outputs.file("$buildDir/rollup/index.min.js").withPropertyName("output") commandLine mkCommand('"node_modules/.bin/terser"' + " -o '$buildDir/rollup/index.min.js' '$buildDir/rollup/index.js'") } task illuaminateDocs(type: Exec, dependsOn: [minifyWeb, luaJavadoc]) { group = "build" description = "Bundles JS into rollup" inputs.files(fileTree("doc")).withPropertyName("docs") inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom") inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp") inputs.dir("$buildDir/docs/luaJavadoc") inputs.file("$buildDir/rollup/index.min.js").withPropertyName("scripts") inputs.file("src/web/styles.css").withPropertyName("styles") outputs.dir("$buildDir/docs/lua") commandLine mkCommand('"bin/illuaminate" doc-gen') } task docWebsite(type: Copy, dependsOn: [illuaminateDocs]) { from 'doc/logo.png' into "${project.docsDir}/lua" } // Check tasks test { useJUnitPlatform() testLogging { events "skipped", "failed" } } jacocoTestReport { dependsOn('test') reports { xml.enabled true html.enabled true } } check.dependsOn jacocoTestReport license { mapping("java", "SLASHSTAR_STYLE") strictCheck true ext.year = Calendar.getInstance().get(Calendar.YEAR) } [licenseMain, licenseFormatMain].forEach { it.configure { include("**/*.java") exclude("dan200/computercraft/api/**") header file('config/license/main.txt') } } [licenseTest, licenseFormatTest].forEach { it.configure { include("**/*.java") header file('config/license/main.txt') } } gradle.projectsEvaluated { tasks.withType(LicenseFormat) { outputs.upToDateWhen { false } } } task licenseAPI(type: LicenseCheck); task licenseFormatAPI(type: LicenseFormat); [licenseAPI, licenseFormatAPI].forEach { it.configure { source = sourceSets.main.java include("dan200/computercraft/api/**") header file('config/license/api.txt') } } task setupServer(type: Copy) { group "test server" description "Sets up the environment for the test server." from("src/test/server-files") { include "eula.txt" include "server.properties" } into "test-files/server" } tasks.register('testInGame', JavaExec.class).configure { it.group('test server') it.description("Runs tests on a temporary Minecraft server.") it.dependsOn(setupServer, 'prepareRunTestServer') // Copy from runTestServer. We do it in this slightly odd way as runTestServer // isn't created until the task is configured (which is no good for us). JavaExec exec = tasks.getByName('runTestServer') it.setWorkingDir(exec.getWorkingDir()) it.setSystemProperties(exec.getSystemProperties()) it.setBootstrapClasspath(exec.getBootstrapClasspath()) it.setClasspath(exec.getClasspath()) it.setMain(exec.getMain()) it.setEnvironment(exec.getEnvironment()) it.setArgs(exec.getArgs()) it.setJvmArgs(exec.getJvmArgs()) it.systemProperty('forge.logging.console.level', 'info') it.systemProperty('cctest.run', 'true') // Jacoco and modlauncher don't play well together as the classes loaded in-game don't // match up with those written to disk. We get Jacoco to dump all classes to disk, and // use that when generating the report. def coverageOut = new File(buildDir, 'jacocoClassDump/testInGame') jacoco.applyTo(it) it.jacoco.setIncludes(["dan200.computercraft.*"]) it.jacoco.setClassDumpDir(coverageOut) it.outputs.dir(coverageOut) // Older versions of modlauncher don't include a protection domain (and thus no code // source). Jacoco skips such classes by default, so we need to explicitly include them. it.jacoco.setIncludeNoLocationClasses(true) } tasks.register('jacocoTestInGameReport', JacocoReport.class).configure { it.group('test server') it.description('Generate coverage reports for in-game tests (testInGame)') it.dependsOn('testInGame') it.executionData(new File(buildDir, 'jacoco/testInGame.exec')) it.sourceDirectories.from(sourceSets.main.allJava.srcDirs) it.classDirectories.from(new File(buildDir, 'jacocoClassDump/testInGame')) it.reports { xml.enabled true html.enabled true } } check.dependsOn('jacocoTestInGameReport') // Upload tasks task checkRelease { group "upload" description "Verifies that everything is ready for a release" inputs.property "version", mod_version inputs.file("src/main/resources/data/computercraft/lua/rom/help/changelog.txt") inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt") doLast { def ok = true // Check we're targetting the current version def whatsnew = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt").readLines() if (whatsnew[0] != "New features in CC: Tweaked $mod_version") { ok = false project.logger.error("Expected `whatsnew.txt' to target $mod_version.") } // Check "read more" exists and trim it def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' } if (idx == -1) { ok = false project.logger.error("Must mention the changelog in whatsnew.txt") } else { whatsnew = whatsnew.getAt(0 ..< idx) } // Check whatsnew and changelog match. def versionChangelog = "# " + whatsnew.join("\n") def changelog = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/changelog.txt").getText() if (!changelog.startsWith(versionChangelog)) { ok = false project.logger.error("whatsnew and changelog are not in sync") } if (!ok) throw new IllegalStateException("Could not check release") } } check.dependsOn checkRelease curseforge { apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : '' project { id = '282001' releaseType = 'release' changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})." relations { incompatible "computercraft" } } } publishing { publications { mavenJava(MavenPublication) { from components.java // artifact sourceJar } } } uploadArchives { repositories { if(project.hasProperty('mavenUploadUrl')) { mavenDeployer { configuration = configurations.deployerJars repository(url: project.property('mavenUploadUrl')) { authentication( userName: project.property('mavenUploadUser'), privateKey: project.property('mavenUploadKey')) } pom.project { name 'CC: Tweaked' packaging 'jar' description 'CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.' url 'https://github.com/SquidDev-CC/CC-Tweaked' scm { url 'https://github.com/SquidDev-CC/CC-Tweaked.git' } issueManagement { system 'github' url 'https://github.com/SquidDev-CC/CC-Tweaked/issues' } licenses { license { name 'ComputerCraft Public License, Version 1.0' url 'https://github.com/SquidDev-CC/CC-Tweaked/blob/master/LICENSE' distribution 'repo' } } } pom.whenConfigured { pom -> pom.dependencies.clear() } } } } } githubRelease { token project.hasProperty('githubApiKey') ? project.githubApiKey : '' owner 'SquidDev-CC' repo 'CC-Tweaked' targetCommitish.set(project.provider({ try { return ["git", "-C", projectDir, "rev-parse", "--abbrev-ref", "HEAD"].execute().text.trim() } catch (Exception e) { e.printStackTrace() } return "master" })) tagName "v${mc_version}-${mod_version}" releaseName "[${mc_version}] ${mod_version}" body.set(project.provider({ "## " + new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt") .readLines() .takeWhile { it != 'Type "help changelog" to see the full version history.' } .join("\n").trim() })) prerelease false } def uploadTasks = ["uploadArchives", "curseforge", "githubRelease"] uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease } task uploadAll(dependsOn: uploadTasks) { group "upload" description "Uploads to all repositories (Maven, Curse, GitHub release)" }