buildscript { def mcVersion if (project.name != 'core') { def (major, minor, patch) = project.name.tokenize('.') mcVersion = "${major}${minor.padLeft(2, '0')}${(patch ?: '').padLeft(2, '0')}" as int } else { def f = file('mcVersion') mcVersion = f.exists() ? f.readLines().first() as int : 11302 } project.ext.mcVersion = mcVersion repositories { jcenter() mavenCentral() maven { name = "forge" url = "https://files.minecraftforge.net/maven" } maven { name = "sonatype" url = "https://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { classpath 'org.ow2.asm:asm:6.0' classpath('com.github.SpongePowered:MixinGradle:dcfaf61'){ // 0.6 // Because forgegradle requires 6.0 (not -debug-all) while mixingradle depends on 5.0 // and putting mixin right here will place it before forge in the class loader exclude group: 'org.ow2.asm', module: 'asm-debug-all' } classpath 'net.minecraftforge.gradle:ForgeGradle:' + ( mcVersion >= 11300 ? '3.+' : mcVersion >= 11200 ? '2.3-SNAPSHOT' : mcVersion >= 10904 ? '2.2-SNAPSHOT' : mcVersion >= 10809 ? '2.1-SNAPSHOT' : mcVersion >= 10800 ? '2.0-SNAPSHOT' : mcVersion >= 10710 ? '1.2-SNAPSHOT' : 'invalid' ) } } def mcVersionStr = "${(int)(mcVersion/10000)}.${(int)(mcVersion/100)%100}" + (mcVersion%100==0 ? '' : ".${mcVersion%100}") def FG3 = mcVersion >= 11300 if (mcVersion >= 10800) { if (FG3) { apply plugin: 'net.minecraftforge.gradle' // FIXME use mixin plugin once updated compileJava { options.compilerArgs += ['-AreobfSrgFile=' + new File(project.buildDir, 'mcp-srg.srg').getCanonicalPath()] } } else { apply plugin: 'net.minecraftforge.gradle.forge' apply plugin: 'org.spongepowered.mixin' } } else { apply plugin: 'forge' ext { mixinSrg = new File(project.buildDir, 'tmp/mixins/mixins.srg') mixinRefMap = new File(project.buildDir, 'tmp/mixins/mixins.replaymod.refmap.json') } } sourceCompatibility = 1.8 targetCompatibility = 1.8 afterEvaluate { version = mcVersionStr + '-' + rootProject.version } group= "com.replaymod" archivesBaseName = "replaymod" minecraft { if (FG3) { runs { client { workingDirectory rootProject.file('eclipse') property 'forge.logging.console.level', 'info' mods { replaymod { source sourceSets.main } } } } } else { if (mcVersion >= 10800) { coreMod = 'com.replaymod.core.LoadingPlugin' } runDir = "../../eclipse" } if (!FG3) { version = [ 11202: '1.12.2-14.23.0.2486', 11201: '1.12.1-14.22.0.2444', 11200: '1.12-14.21.1.2387', 11102: '1.11.2-13.20.0.2216', 11100: '1.11-13.19.1.2188', 11002: '1.10.2-12.18.2.2099', 10904: '1.9.4-12.17.0.1976', 10809: '1.8.9-11.15.1.1722', 10800: '1.8-11.14.4.1563', 10710: '1.7.10-10.13.4.1558-1.7.10', ][mcVersion] } mappings = [ 11302: "snapshot_20180921-1.13", 11202: "snapshot_20170615", 11201: "snapshot_20170615", 11200: "snapshot_20170615", 11102: "snapshot_20161220", 11100: "snapshot_20161111", 11002: "snapshot_20160518", 10904: "snapshot_20160518", 10809: "stable_22", 10800: "snapshot_nodoc_20141130", 10710: "stable_12", ][mcVersion] } afterEvaluate { if (mcVersion >= 11300) { // FIXME } else { // Note cannot use minecraft.replace because that has already been forwarded to the task by FG by now tasks.sourceMainJava.replace '@MOD_VERSION@', project.version // Includes intentional whitespace to stop Forge from declaring the mod to be compatible with // a newer srg-compatible MC version (that may be using a different protocol version) tasks.sourceMainJava.replace '@MC_VERSION@', "[ $mcVersionStr ]" } } repositories { mavenLocal() maven { name = "SpongePowered Repo" url = "http://repo.spongepowered.org/maven/" } maven { url 'https://jitpack.io' } } configurations { shade compile.extendsFrom shade shade.exclude group: 'com.google.code.gson', module: 'gson' // provided by MC } dependencies { if (FG3) { minecraft 'net.minecraftforge:forge:' + [ 11302: '1.13.2-25.0.63', ][mcVersion] } annotationProcessor group: 'com.google.code.gson', name: 'gson', version: '2.8.5' compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5' // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod annotationProcessor 'org.spongepowered:mixin:0.7.8-SNAPSHOT' compileOnly 'org.spongepowered:mixin:0.7.8-SNAPSHOT' compile 'org.spongepowered:mixin:0.7.8-SNAPSHOT' shade 'com.googlecode.mp4parser:isoparser:1.1.7' shade 'org.apache.commons:commons-exec:1.3' def withoutGuava = { exclude group: 'com.google.guava', module: 'guava-jdk5' } shade 'com.google.apis:google-api-services-youtube:v3-rev178-1.22.0', withoutGuava shade 'com.google.api-client:google-api-client-gson:1.20.0', withoutGuava shade 'com.google.api-client:google-api-client-java6:1.20.0', withoutGuava shade 'com.google.oauth-client:google-oauth-client-jetty:1.20.0' shade 'org.aspectj:aspectjrt:1.8.2' def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' shade("com.replaymod.replaystudio:$studioVersion:98ab667"){ // FIXME exclude group: 'com.google.guava', module: 'guava' // provided by MC exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } def jGuiVersion = project.name if (['1.10.2', '1.11', '1.11.2'].contains(jGuiVersion)) jGuiVersion = '1.9.4' if (['1.12.1', '1.12.2'].contains(jGuiVersion)) jGuiVersion = '1.12' shade(project(":jGui:$jGuiVersion")){ transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } annotationProcessor 'org.projectlombok:lombok:1.16.6' compile 'org.projectlombok:lombok:1.16.6' // runtime only for @SneakyThrows which isn't used testCompile 'junit:junit:4.11' } if (mcVersion <= 10710) { // FG 1.2 adds all MC deps to the compile configuration which we don't want afterEvaluate { // Remove them from the compile and runtime configurations configurations.compile.extendsFrom -= [configurations.minecraft, configurations.minecraftDeps] configurations.runtime.extendsFrom -= [configurations.forgeGradleStartClass] // And add them to the source sets instead sourceSets.main.with { compileClasspath += configurations.minecraft + configurations.minecraftDeps runtimeClasspath += configurations.minecraft + configurations.minecraftDeps + configurations.forgeGradleStartClass } // Also need to reconfigure the reobf task, so it can properly re-obfuscates inherited members tasks.reobf.obfOutput.all { artifact -> artifact.getFile() // force resolve artifact.classpath += configurations.minecraft + configurations.minecraftDeps } } } if (project.name != 'core') { apply from: '../preprocessor.gradle' def preprocessedSrc = 'build/preprocessed/src' def preprocessedRes = 'build/preprocessed/res' def preprocessedAT = 'build/preprocessed/at.cfg' def originalSrc = '../../src/main/java' def originalRes = '../../src/main/resources' def originalAT = originalRes + '/META-INF/replaymod_at.cfg' def vars = [MC: mcVersion as int] sourceSets { main.java.srcDir preprocessedSrc main.resources.srcDir preprocessedRes } task preprocessJava { inputs.dir(originalSrc) outputs.dir(preprocessedSrc) doLast { project.convertTree(vars, originalSrc, preprocessedSrc) } } task preprocessResources { inputs.dir(originalRes) outputs.dir(preprocessedRes) doLast { project.convertTree(vars, originalRes, preprocessedRes) } } compileJava.dependsOn preprocessJava processResources.dependsOn preprocessResources if (FG3) { // FG3 reads the AT file right after project evaluation, so we need to convert it right now project.convertTree(vars, originalAT, preprocessedAT) minecraft.accessTransformers << file(preprocessedAT) } else { (mcVersion >= 10800 ? [deobfMcMCP, deobfMcSRG] : [deobfuscateJar, deobfBinJar]).each { task -> task.dependsOn preprocessResources } minecraft.accessTransformer preprocessedRes + '/META-INF/replaymod_at.cfg' } } else { if (FG3) { minecraft.accessTransformers << file('../../src/main/resources/META-INF/replaymod_at.cfg') } sourceSets { main.java.srcDirs = ['../../src/main/java'] main.resources.srcDirs = ['../../src/main/resources'] } } sourceSets { main { java { exclude 'com/replaymod/compat' exclude 'com/replaymod/restrictions' } } } if (!FG3) { // FIXME def reobfTask = mcVersion >= 10800 ? tasks.reobfJar : tasks.reobf task configureRelocation() { dependsOn tasks.jar dependsOn configurations.shade doLast { def extraSrg = mcVersion >= 10800 ? reobfTask.extraSrgLines : reobfTask.extraSrg files(configurations.shade).filter { it.exists() }.collect { def tree = it.isDirectory() ? fileTree(it) : zipTree(it) def pkgs = [].toSet() tree.visit { file -> if (!file.directory && file.name.endsWith('.class') && file.path.contains('/')) { def pkg = file.path.substring(0, file.path.lastIndexOf('/')) if (!pkg.startsWith('com/replaymod') && !pkg.startsWith('javax/')) { pkgs << pkg } } } pkgs }.flatten().unique().each { pkg -> extraSrg << "PK: $pkg com/replaymod/lib/$pkg".toString() } } } reobfTask.dependsOn configureRelocation } if (mcVersion <= 10710) { // Dummy task so "./gradlew reobfJar" also builds 1.7.10 task reobfJar() { dependsOn reobf } } jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE dependsOn configurations.compile dependsOn configurations.shade if (mcVersion <= 10710) { from project.mixinRefMap } def shade = {files( configurations.compile.findAll {it.name.startsWith 'mixin-'} + configurations.shade )} def noticeDir = file("$buildDir/NOTICE") doFirst { noticeDir.deleteDir() noticeDir.mkdirs() shade().collect { it.isDirectory() ? fileTree(it) : zipTree(it) }.each { it.matching { include '**/NOTICE*' }.each { new File(noticeDir, 'NOTICE.txt') << it.getText('UTF-8') + '\n' } } } from noticeDir from ({shade().collect { it.isDirectory() ? it : zipTree(it) }}) { exclude '**/NOTICE*' eachFile { if (getName() == 'LICENSE.txt') { setName(getFile().getParentFile().getName().split('.jar_')[0].tokenize('-')[0] + '-LICENSE.txt') } } } manifest { attributes 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker', 'TweakOrder': '0', 'FMLCorePluginContainsFMLMod': 'true', 'FMLCorePlugin': 'com.replaymod.core.LoadingPlugin', 'FMLAT': 'replaymod_at.cfg' } } processResources { // this will ensure that this task is redone when the versions change. inputs.property 'version', { project.version } inputs.property 'mcversion', { mcVersionStr } // replace stuff in mcmod.info (pre 1.13) and mods.toml (1.13+), nothing else from(sourceSets.main.resources.srcDirs) { spec -> include 'mcmod.info' include 'mods.toml' // replace version and mcversion afterEvaluate { spec.expand 'version': project.version, 'mcversion': mcVersionStr } } // copy everything else, thats not the mcmod.info (or mods.toml) from(sourceSets.main.resources.srcDirs) { exclude 'mcmod.info' exclude 'mods.toml' } } sourceSets { main { ext.refMap = "mixins.replaymod.refmap.json" } integrationTest { compileClasspath += main.runtimeClasspath + main.output java { srcDir file('src/integration-test/java') } resources.srcDir file('src/integration-test/resources') } } if (FG3) { task copySrg(dependsOn: 'createMcpToSrg') { doLast { def tsrg = file(project.tasks.createMcpToSrg.output).readLines() def srg = [] def cls = '' for (def line : tsrg) { if (line[0] != '\t') { srg.add('CL: ' + line) cls = line.split(' ')[0] } else { def parts = line.substring(1).split(' ') if (line.contains('(')) { srg.add('MD: ' + cls + '/' + parts[0] + ' ' + parts[1] + ' ' + cls + '/' + parts[2] + ' ' + parts[1]) } else { srg.add('FD: ' + cls + '/' + parts[0] + ' ' + cls + '/' + parts[1]) } } } new File(project.buildDir, 'mcp-srg.srg').write(srg.join('\n')) } } } else { task copySrg(type: Copy, dependsOn: 'genSrgs') { from {project.tasks.genSrgs.mcpToSrg} into 'build' } } if (FG3) { // FIXME project.tasks.idea.dependsOn copySrg compileJava.dependsOn copySrg // FIXME can probably remove this one once mixingradle is updated } else { setupDecompWorkspace.dependsOn copySrg setupDevWorkspace.dependsOn copySrg } if (mcVersion <= 10710) { reobf.doFirst { if (project.mixinSrg.exists()) { addExtraSrgFile project.mixinSrg } } compileJava.dependsOn copySrg compileJava { options.compilerArgs += [ "-AoutSrgFile=${project.mixinSrg.canonicalPath}", "-AoutRefMapFile=${project.mixinRefMap.canonicalPath}", "-AreobfSrgFile=${project.file('build/mcp-srg.srg').canonicalPath}" ] } } /* FIXME // Mixin uses multiple HashMaps to generate the refmap. // HashMaps are unordered collections and as such do not produce deterministic output. // To fix that, we simply sort the refmap json file. import groovy.json.JsonSlurper import groovy.json.JsonOutput compileJava.doLast { File refmapFile = mcVersion >= 10800 ? compileJava.ext.refMapFile : project.mixinRefMap if (refmapFile.exists()) { def ordered ordered = { if (it instanceof Map) { def sorted = new TreeMap(it) sorted.replaceAll { k, v -> ordered(v) } sorted } else if (it instanceof List) { it.replaceAll { v -> ordered(v) } } else { it } } def json = JsonOutput.toJson(ordered(new JsonSlurper().parse(refmapFile))) refmapFile.withWriter { it.write json } } } */ import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream import org.objectweb.asm.* import static org.objectweb.asm.Opcodes.ASM5 // MC binaries were complied with a java version that produces invalid class files under certain circumstances // This causes setupCIWorkspace to be insufficient for compiling. // Related JDK bug: https://bugs.openjdk.java.net/browse/JDK-8066725 // As a workaround, to use setupCIWorkspace on Drone, we modify the bin jar in-place and remove all parameter annotations. // WARNING: This piece of code ignores any and all gradle conventions and will probably fail horribly when run outside // of a single-use environment (e.g. Drone). Use setupDecompWorkspace for normal use. def annotationWorkaround = { println "Applying RuntimeInvisibleParameterAnnotations workaround..." File jar = getOutJar() File tmp = new File((File) getTemporaryDir(), "workaround.jar") tmp.withOutputStream { new ZipOutputStream(it).withStream { dst -> new ZipFile(jar).withCloseable { src -> src.entries().each { if (it.name.startsWith("net/minecraft/") && it.name.endsWith(".class")) { def cw = new ClassWriter(0) def cv = new ClassVisitor(ASM5, cw) { @Override MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return new MethodVisitor(ASM5, cv.visitMethod(access, name, desc, signature, exceptions)) { @Override AnnotationVisitor visitParameterAnnotation(int parameter, String pdesc, boolean visible) { return null // Strip all parameter annotations } } } } new ClassReader(src.getInputStream(it)).accept(cv, 0) dst.putNextEntry(new ZipEntry(it.name)) dst.write(cw.toByteArray()) } else { dst.putNextEntry(it) dst.write(src.getInputStream(it).bytes) } } } } } jar.delete() tmp.renameTo(jar) } if (mcVersion >= 10809 && !FG3) { tasks.deobfMcMCP.doLast annotationWorkaround } if (!FG3) { // FIXME task runIntegrationTest(type: JavaExec, dependsOn: ["makeStart", "jar"]) { main = 'GradleStart' standardOutput = System.out errorOutput = System.err workingDir file(minecraft.runDir) def testDir = new File(minecraft.runDir, "integration-test") doFirst { testDir.deleteDir() testDir.mkdirs() } doLast { testDir.deleteDir() } afterEvaluate { def runClient = tasks.getByName("runClient") runIntegrationTest.jvmArgs = runClient.jvmArgs + "-Dfml.noGrab=true" runIntegrationTest.args = runClient.args + "--gameDir" + testDir.canonicalPath runIntegrationTest.classpath runClient.classpath + sourceSets.integrationTest.output } } } defaultTasks 'build'