buildscript { repositories { jcenter() maven { name "Modmuss50 Repository" url 'https://maven.modmuss50.me' } } dependencies { classpath "net.fabricmc:weave:0.3.1.17+" classpath "net.fabricmc:stitch:0.1.2.49" classpath "commons-io:commons-io:1.4" classpath "com.google.guava:guava:19.0" classpath 'de.undercouch:gradle-download-task:3.4.3' classpath 'net.fabricmc:tiny-remapper:+' } } plugins { id 'de.undercouch.download' version '3.4.3' id 'maven' id 'maven-publish' } def minecraft_version = "1.14" def ENV = System.getenv() // Fetch build number from Jenkins def build_number = ENV.BUILD_NUMBER ?: "local" def yarnVersion = "${minecraft_version}+build.$build_number" repositories { mavenCentral() maven { name "Modmuss50 Repository" url 'https://maven.modmuss50.me' } } configurations { enigmaRuntime { resolutionStrategy { cacheDynamicVersionsFor 0, "seconds" cacheChangingModulesFor 0, "seconds" } } } dependencies { enigmaRuntime "net.fabricmc:stitch:0.1.1.39+" enigmaRuntime "cuchaz:enigma:0.13.1.+:all" } def mappingsDir = file("mappings") def cacheFilesMinecraft = file(".gradle/minecraft") def tempDir = file(".gradle/temp") def mergedFile = file("${minecraft_version}-merged.jar") def intermediaryJar = file("${minecraft_version}-intermediary.jar") def namedJar = file("${minecraft_version}-named.jar") def versionFile = new File(cacheFilesMinecraft, "${minecraft_version}.json") def clientJar = new File(cacheFilesMinecraft, "${minecraft_version}-client.jar") def serverJar = new File(cacheFilesMinecraft, "${minecraft_version}-server.jar") def libraries = new File(cacheFilesMinecraft, "${minecraft_version}-libraries") def libs = new File("build/libs/") import groovy.json.JsonSlurper import org.apache.commons.io.FileUtils import com.google.common.hash.Hashing import com.google.common.io.Files import net.fabricmc.stitch.commands.CommandMergeTiny import net.fabricmc.stitch.commands.CommandRewriteIntermediary import net.fabricmc.stitch.merge.JarMerger import net.fabricmc.tinyremapper.OutputConsumerPath import net.fabricmc.tinyremapper.TinyRemapper import net.fabricmc.tinyremapper.TinyUtils import net.fabricmc.weave.CommandFindMappingErrors import net.fabricmc.weave.CommandTinyify import groovy.io.FileType import java.util.zip.GZIPOutputStream boolean validateChecksum(File file, String checksum) { if (file != null) { def hash = Files.hash(file, Hashing.sha1()) def builder = new StringBuilder() hash.asBytes().each { builder.append(Integer.toString((it & 0xFF) + 0x100, 16).substring(1)) } return builder.toString().equals(checksum) } return false } task downloadVersionsManifest { //inputs.property "mc_ver", minecraft_version inputs.property "currenttime", new Date() def manifestFile = new File(cacheFilesMinecraft, "version_manifest.json") outputs.file(manifestFile) doLast { logger.lifecycle(":downloading minecraft versions manifest") FileUtils.copyURLToFile(new URL("https://launchermeta.mojang.com/mc/game/version_manifest.json"), manifestFile) } } def getManifestVersion(File manifestFile, String minecraft_version) { def manifest = manifestFile.exists() ? new JsonSlurper().parseText(FileUtils.readFileToString(manifestFile)) : null return manifest != null ? manifest.versions.stream().filter({it.id.equals(minecraft_version)}).findFirst() : java.util.Optional.empty() } task downloadWantedVersionManifest(dependsOn: downloadVersionsManifest) { def manifestFile = downloadVersionsManifest.outputs.files.singleFile def manifestVersion = getManifestVersion(manifestFile, minecraft_version) //have to grab the release time as there's a current timestamp on each element?! inputs.property "releaseTime", manifestVersion.isPresent() ? manifestVersion.get().releaseTime : -1 outputs.file versionFile doLast { manifestVersion = getManifestVersion(manifestFile, minecraft_version) //nb need to re-read here in case it didn't exist before if (manifestVersion.isPresent() || versionFile.exists()) { if (manifestVersion.isPresent()) { FileUtils.copyURLToFile(new URL(manifestVersion.get().url), versionFile) } } else { throw new RuntimeException("No version data for Minecraft version ${minecraft_version}") } } } task downloadMcJars(dependsOn: downloadWantedVersionManifest) { inputs.files versionFile outputs.files(clientJar, serverJar) outputs.upToDateWhen { def version = new JsonSlurper().parseText(FileUtils.readFileToString(versionFile)) return clientJar.exists() && serverJar.exists() && validateChecksum(clientJar, version.downloads.client.sha1) && validateChecksum(serverJar, version.downloads.server.sha1) } doLast { if (!versionFile.exists()) { throw new RuntimeException("Can't download the jars without the ${versionFile.name} file!") } //reload in case it changed def version = new JsonSlurper().parseText(FileUtils.readFileToString(versionFile)) logger.lifecycle(":downloading minecraft jars") if (!clientJar.exists() || !validateChecksum(clientJar, version.downloads.client.sha1)) { logger.lifecycle(":downloading minecraft client") FileUtils.copyURLToFile(new URL(version.downloads.client.url), clientJar) } if (!serverJar.exists() || !validateChecksum(serverJar, version.downloads.server.sha1)) { logger.lifecycle(":downloading minecraft server") FileUtils.copyURLToFile(new URL(version.downloads.server.url), serverJar) } } } task mergeJars(dependsOn: downloadMcJars) { inputs.files downloadMcJars.outputs.files.files outputs.file(mergedFile) doLast { logger.lifecycle(":merging jars") def client = inputs.files.files.find {it.name.endsWith("-client.jar")} def server = inputs.files.files.find {it.name.endsWith("-server.jar")} def merged = mergedFile if(merged.exists()){ return } def jarMerger = new JarMerger(client, server, merged) jarMerger.merge() jarMerger.close() } } task setupYarn(dependsOn: mergeJars) { } task yarn(dependsOn: setupYarn) { doLast { ant.java( classname: 'cuchaz.enigma.Main', classpath: configurations.enigmaRuntime.asPath, fork: true, spawn: true ) { jvmarg(value: "-Xmx2048m") arg(value: mergedFile.getAbsolutePath()) arg(value: mappingsDir.getAbsolutePath()) } } } task buildEnigma(type: Zip) { from mappingsDir include "**/*" archiveName "yarn-enigma-${yarnVersion}.zip" destinationDir(file("build/libs")) } task checkMappings { inputs.dir mappingsDir doLast { logger.lifecycle(":checking mappings") String[] args = [ mergedFile.getAbsolutePath(), mappingsDir.getAbsolutePath() ] new CommandFindMappingErrors().run(args) } } task downloadIntermediary(type: Download){ def url = "https://github.com/FabricMC/intermediary/raw/master/mappings/${minecraft_version}.tiny" src url.replaceAll(" ", "%20") dest new File(cacheFilesMinecraft, "${minecraft_version}-intermediary.tiny") } task patchIntermediary(dependsOn: ["mergeJars", "downloadIntermediary"], type: FileOutput) { def intermediaryTinyInput = downloadIntermediary.dest def outputFile = new File(cacheFilesMinecraft, "${minecraft_version}-intermediary-full.tiny") outputs.file(outputFile) fileOutput = outputFile outputs.upToDateWhen {return false} doLast { logger.lifecycle(":patching intermediary") String[] args = [ mergedFile.getAbsolutePath(), intermediaryTinyInput.getAbsolutePath(), outputFile.getAbsolutePath(), "--writeAll" ] new CommandRewriteIntermediary().run(args) } } task buildYarnTiny(dependsOn: "mergeJars",type: FileOutput) { inputs.dir mappingsDir if (!libs.exists()) { libs.mkdirs() } def yarnTiny = new File(tempDir, "yarn-mappings.tiny") fileOutput = yarnTiny outputs.upToDateWhen {return false} doLast { logger.lifecycle(":generating tiny mappings") String[] args = [ mergedFile.getAbsolutePath(), mappingsDir.getAbsolutePath(), yarnTiny.getAbsolutePath(), "official", "named" ] new CommandTinyify().run(args) } } task mergeTiny(dependsOn: ["buildYarnTiny", "patchIntermediary"], type: FileOutput) { def yarnTinyInput = buildYarnTiny.fileOutput def intermediaryTinyInput = patchIntermediary.fileOutput def outputFile = new File(tempDir, "mappings.tiny") outputs.file(outputFile) fileOutput = outputFile outputs.upToDateWhen {return false} doLast { logger.lifecycle(":merging yarn and intermediary") String[] args = [ yarnTinyInput.getAbsolutePath(), intermediaryTinyInput.getAbsolutePath(), outputFile.getAbsolutePath(), "intermediary", "official" ] new CommandMergeTiny().run(args) } } task tinyJar(type: Jar, dependsOn: "mergeTiny") { outputs.upToDateWhen {return false} archiveName = "yarn-${yarnVersion}.jar" destinationDir(file("build/libs")) classifier = "" from (mergeTiny.fileOutput) { rename { "mappings/mappings.tiny" } } } task compressTiny(dependsOn: ["tinyJar", "mergeTiny"], type: FileOutput){ def outputFile = new File(libs, "yarn-tiny-${yarnVersion}.gz") outputs.file(outputFile) fileOutput = outputFile def inputFile = mergeTiny.fileOutput outputs.upToDateWhen {return false} doLast { logger.lifecycle(":compressing tiny mappings") def buffer = new byte[1024] def fileOutputStream = new FileOutputStream(outputFile) def outputStream = new GZIPOutputStream(fileOutputStream) def fileInputStream = new FileInputStream(inputFile) def length while ((length = fileInputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length) } fileInputStream.close() outputStream.finish() outputStream.close() inputFile.delete() } } clean.doFirst { delete tempDir, cacheFilesMinecraft } tasks.build.dependsOn "compressTiny" tasks.build.dependsOn "buildEnigma" tasks.build.dependsOn "tinyJar" task downloadMcLibs(dependsOn: downloadWantedVersionManifest) { inputs.files versionFile outputs.file(libraries) outputs.upToDateWhen { return false } doLast { if (!versionFile.exists()) { throw new RuntimeException("Can't download the jars without the ${versionFile.name} file!") } def version = new JsonSlurper().parseText(FileUtils.readFileToString(versionFile)) logger.lifecycle(":downloading minecraft libraries") if (!libraries.exists()) { libraries.mkdirs() } version.libraries.each { def downloadUrl = it.downloads.artifact.url download { src downloadUrl dest new File(libraries, downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1)) overwrite false } } } } task mapIntermediaryJar(dependsOn: [downloadMcLibs, build]) { inputs.files downloadMcLibs.outputs.files.files outputs.file(intermediaryJar) //Force the task to always run outputs.upToDateWhen { return false } doLast { logger.lifecycle(":mapping minecraft to intermdiary") def tinyInput = new File("build/libs/yarn-tiny-${yarnVersion}.gz") mapJar(intermediaryJar, mergedFile, tinyInput, libraries, "official", "intermediary") } } task mapNamedJar(dependsOn: mapIntermediaryJar) { inputs.files downloadMcLibs.outputs.files.files outputs.file(namedJar) //Force the task to always run outputs.upToDateWhen { return false } doLast { logger.lifecycle(":mapping minecraft to named") def tinyInput = new File("build/libs/yarn-tiny-${yarnVersion}.gz") mapJar(namedJar, intermediaryJar, tinyInput, libraries, "intermediary", "named") } } publishing { publications { maven(MavenPublication) { groupId 'net.fabricmc' artifactId "yarn" version yarnVersion artifact (compressTiny.fileOutput) { classifier "tiny" builtBy compressTiny } artifact (buildEnigma) { classifier "enigma" } artifact (tinyJar) } } repositories { maven { url "http://mavenupload.modmuss50.me/" if (project.hasProperty('mavenPass')) { credentials { username 'buildslave' password project.getProperty('mavenPass') } } } } } void mapJar(File output, File input, File mappings, File libraries, String from, String to){ if (output.exists()) { output.delete() } def remapper = TinyRemapper.newRemapper() .withMappings(TinyUtils.createTinyMappingProvider(mappings.toPath(), from, to)) .renameInvalidLocals(true) .rebuildSourceFilenames(true) .build() try { def outputConsumer = new OutputConsumerPath(output.toPath()) outputConsumer.addNonClassFiles(input.toPath()) remapper.readInputs(input.toPath()) libraries.eachFileRecurse(FileType.FILES) {file -> remapper.readClassPath(file.toPath()) } remapper.apply(outputConsumer) outputConsumer.close() remapper.finish() } catch (Exception e) { remapper.finish() throw new RuntimeException("Failed to remap jar", e); } } class FileOutput extends DefaultTask { @OutputFile File fileOutput }