diff --git a/.gitignore b/.gitignore index eecc796..b26060e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,13 +8,4 @@ #Gradle build/ -/.gradle - -#Forge SDK -/eclipse -/CREDITS-fml.txt -/forge-1.7.2-10.12.0.1057-changelog.txt -/LICENSE-fml.txt -/MinecraftForge-Credits.txt -/MinecraftForge-License.txt -/README.txt +.gradle diff --git a/build.gradle b/build.gradle index 01a701f..0f4fca2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,47 +1,8 @@ -buildscript { - repositories { - mavenCentral() - mavenLocal() - - maven { - name = "forge" - url = "http://files.minecraftforge.net/maven" - - } - maven { - name = "sonatype" - url = "https://oss.sonatype.org/content/repositories/snapshots/" - - } - } - - dependencies { - classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT' - } -} - -import net.minecraftforge.gradle.common.Constants -import net.minecraftforge.gradle.delayed.DelayedFile -import net.minecraftforge.gradle.delayed.DelayedString -import net.minecraftforge.gradle.tasks.user.reobf.ArtifactSpec -import net.minecraftforge.gradle.tasks.user.reobf.ReobfTask -import net.minecraftforge.gradle.user.UserBasePlugin -import net.minecraftforge.gradle.user.UserExtension - -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; -import com.google.common.io.ByteStreams; -import com.google.common.io.Files; -import org.apache.commons.io.FileUtils; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; -import org.objectweb.asm.tree.MethodNode; -import cpw.mods.fml.relauncher.SideOnly; +import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact +import org.gradle.api.internal.java.JavaLibrary +import org.ultramine.gradle.task.ReobfTask +import org.ultramine.gradle.task.SideSplitTask +import org.ultramine.gradle.task.SpeicialClassTransformTask apply plugin: 'java' apply plugin: 'maven-publish' @@ -52,14 +13,8 @@ compileJava.options.encoding = 'UTF-8' -ext.buildnumber = 0 -if (System.getenv('BUILD_NUMBER') != null) - project.buildnumber = System.getenv('BUILD_NUMBER') -else - project.buildnumber = 'indev' - -group = 'org.ultramine' -version = "${minecraft_version}-${buildnumber}" +group = project_group +version = (concat_mc_version_to=='version' ? (minecraft_version+'-') : '') + computeVersion() repositories { maven { @@ -77,61 +32,97 @@ } } -dependencies { - compile 'net.minecraft:launchwrapper:1.11' - compile 'com.google.code.findbugs:jsr305:1.3.9' - compile 'org.ow2.asm:asm-debug-all:5.0.3' - compile 'com.typesafe.akka:akka-actor_2.11:2.3.3' - compile 'com.typesafe:config:1.2.1' - compile 'org.scala-lang:scala-actors-migration_2.11:1.1.0' - compile 'org.scala-lang:scala-compiler:2.11.1' - compile 'org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2' - compile 'org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2' - compile 'org.scala-lang:scala-library:2.11.1' - compile 'org.scala-lang:scala-parser-combinators_2.11:1.0.1' - compile 'org.scala-lang:scala-reflect:2.11.1' - compile 'org.scala-lang:scala-swing_2.11:1.0.1' - compile 'org.scala-lang:scala-xml_2.11:1.0.2' - compile 'net.sf.jopt-simple:jopt-simple:4.5' - compile 'lzma:lzma:0.0.1' - compile 'com.mojang:realms:1.3.5' - compile 'org.apache.commons:commons-compress:1.8.1' - compile 'org.apache.httpcomponents:httpclient:4.3.3' - compile 'commons-logging:commons-logging:1.1.3' - compile 'org.apache.httpcomponents:httpcore:4.3.2' - compile 'java3d:vecmath:1.3.1' - compile 'net.sf.trove4j:trove4j:3.0.3' - compile 'com.ibm.icu:icu4j-core-mojang:51.2' - compile 'com.paulscode:codecjorbis:20101023' - compile 'com.paulscode:codecwav:20101023' - compile 'com.paulscode:libraryjavasound:20101123' - compile 'com.paulscode:librarylwjglopenal:20100824' - compile 'com.paulscode:soundsystem:20120107' - compile 'io.netty:netty-all:4.0.10.Final' - compile 'com.google.guava:guava:17.0' - compile 'org.apache.commons:commons-lang3:3.3.2' - compile 'commons-io:commons-io:2.4' - compile 'commons-codec:commons-codec:1.9' - compile 'net.java.jinput:jinput:2.0.5' - compile 'net.java.jutils:jutils:1.0.0' - compile 'com.google.code.gson:gson:2.2.4' - compile 'com.mojang:authlib:1.5.16' - compile 'org.apache.logging.log4j:log4j-api:2.0-beta9' - compile 'org.apache.logging.log4j:log4j-core:2.0-beta9' - compile 'org.lwjgl.lwjgl:lwjgl:2.9.1' - compile 'org.lwjgl.lwjgl:lwjgl_util:2.9.1' - compile 'tv.twitch:twitch:5.16' +configurations { + compileCommon + compileClient.extendsFrom compileCommon + compileServer.extendsFrom compileCommon + compile.extendsFrom compileClient, compileServer + runtimeCommon.extendsFrom compileCommon + runtimeClient.extendsFrom runtimeCommon, compileClient + runtimeServer.extendsFrom runtimeCommon, compileServer + runtimeAll.extendsFrom runtimeClient, runtimeServer + runtime.setExtendsFrom([runtimeAll]) +} - compile 'org.yaml:snakeyaml:1.16' - compile 'com.lmax:disruptor:3.2.1' - compile 'org.apache.commons:commons-dbcp2:2.1.1' - compile 'net.openhft:koloboke-api-jdk8:0.6.8' - - runtime 'net.openhft:koloboke-impl-jdk8:0.6.8' +dependencies { + compileCommon 'net.minecraft:launchwrapper:1.11' + compileCommon 'com.google.code.findbugs:jsr305:1.3.9' + compileCommon 'org.ow2.asm:asm-debug-all:5.0.3' + compileCommon 'com.typesafe.akka:akka-actor_2.11:2.3.3' + compileCommon 'com.typesafe:config:1.2.1' + compileCommon 'org.scala-lang:scala-actors-migration_2.11:1.1.0' + compileCommon 'org.scala-lang:scala-compiler:2.11.1' + compileCommon 'org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2' + compileCommon 'org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2' + compileCommon 'org.scala-lang:scala-library:2.11.1' + compileCommon 'org.scala-lang:scala-parser-combinators_2.11:1.0.1' + compileCommon 'org.scala-lang:scala-reflect:2.11.1' + compileCommon 'org.scala-lang:scala-swing_2.11:1.0.1' + compileCommon 'org.scala-lang:scala-xml_2.11:1.0.2' + compileCommon 'net.sf.jopt-simple:jopt-simple:4.5' + compileCommon 'lzma:lzma:0.0.1' + compileClient 'com.mojang:realms:1.3.5' + compileCommon 'org.apache.commons:commons-compress:1.8.1' + compileCommon 'org.apache.httpcomponents:httpclient:4.3.3' + compileCommon 'commons-logging:commons-logging:1.1.3' + compileCommon 'org.apache.httpcomponents:httpcore:4.3.2' + compileCommon 'java3d:vecmath:1.3.1' + compileCommon 'net.sf.trove4j:trove4j:3.0.3' + compileCommon 'com.ibm.icu:icu4j-core-mojang:51.2' + compileClient 'com.paulscode:codecjorbis:20101023' + compileClient 'com.paulscode:codecwav:20101023' + compileClient 'com.paulscode:libraryjavasound:20101123' + compileClient 'com.paulscode:librarylwjglopenal:20100824' + compileClient 'com.paulscode:soundsystem:20120107' + compileCommon 'io.netty:netty-all:4.0.10.Final' + compileCommon 'com.google.guava:guava:17.0' + compileCommon 'org.apache.commons:commons-lang3:3.3.2' + compileCommon 'commons-io:commons-io:2.4' + compileCommon 'commons-codec:commons-codec:1.9' + compileCommon 'net.java.jinput:jinput:2.0.5' + compileCommon 'net.java.jutils:jutils:1.0.0' + compileCommon 'com.google.code.gson:gson:2.2.4' + compileCommon 'com.mojang:authlib:1.5.16' + compileCommon 'org.apache.logging.log4j:log4j-api:2.0-beta9' + compileCommon 'org.apache.logging.log4j:log4j-core:2.0-beta9' + compileClient 'org.lwjgl.lwjgl:lwjgl:2.9.1' + compileClient 'org.lwjgl.lwjgl:lwjgl_util:2.9.1' + compileClient 'tv.twitch:twitch:5.16' + + compileCommon 'org.yaml:snakeyaml:1.16' + compileCommon 'com.lmax:disruptor:3.2.1' + compileCommon 'org.apache.commons:commons-dbcp2:2.1.1' + compileCommon 'net.openhft:koloboke-api-jdk8:0.6.8' + + runtimeCommon 'net.openhft:koloboke-impl-jdk8:0.6.8' runtime 'mysql:mysql-connector-java:5.1.31' } +task injectVersion(type: SpeicialClassTransformTask) { + dependsOn tasks.compileJava + inputDir = tasks.compileJava.destinationDir + replace { + replaceIn 'org.ultramine.server.UltramineServerModContainer' + replace '@version@', version + } +} + +task reobf(type: ReobfTask) { + dependsOn tasks.compileJava, tasks.injectVersion + classpath = sourceSets.main.compileClasspath; + srg = 'conf/mcp2notch.srg' + inputDir = tasks.compileJava.destinationDir + overrideInputDir = tasks.injectVersion.outputDir +} + +task sidesplit(type: SideSplitTask) { + dependsOn tasks.reobf + inputDir = tasks.reobf.outputDir +} + task processServerResources(type: ProcessResources) { + from sourceSets.main.resources.srcDirs + into new File(buildDir, 'resources_server') exclude 'assets/minecraft/font' exclude 'assets/minecraft/shaders' exclude 'assets/minecraft/texts' @@ -140,105 +131,83 @@ } task processClientResources(type: ProcessResources) { + from sourceSets.main.resources.srcDirs + into new File(buildDir, 'resources_client') exclude 'org/ultramine/defaults' } task jar_server(type: Jar) { + dependsOn(tasks.sidesplit, tasks.processServerResources) + from fileTree(tasks.sidesplit.getServerClasses()), tasks.processServerResources + classifier = 'server' manifest { attributes( 'Main-Class': 'cpw.mods.fml.relauncher.ServerLaunchWrapper', 'TweakClass': 'cpw.mods.fml.common.launcher.FMLTweaker', - 'Class-Path': configurations.runtime.collect { 'libraries/' + it.getName() }.join(' ') + 'Class-Path': configurations.runtimeServer.collect { 'libraries/' + it.getName() }.join(' ') ) } } -// +task jar_client(type: Jar) { + dependsOn(tasks.sidesplit, tasks.processClientResources) + from fileTree(tasks.sidesplit.getClientClasses()), tasks.processClientResources + classifier = 'client' +} -project.getExtensions().create(Constants.EXT_NAME_MC, UserExtension, { return project } as UserBasePlugin) +task jar_universal(type: Jar) { + dependsOn(tasks.reobf, tasks.processResources) + from tasks.reobf.getOutputDir(), tasks.processResources + classifier = 'universal' +} jar { classifier = 'dev' } -task classesJar(type: Zip) { - from sourceSets.main.output.classesDir - classifier = 'classes' - version = '' - destinationDir = new File(buildDir, getName()) - dependsOn(classes) -} - -task reobf(type: ReobfTask) { - setMcVersion(delayedString(minecraft_version)); - setExceptorCfg(delayedFile('conf/srg.exc')) - setSrg(delayedFile('conf/mcp2notch.srg')) - reobf(classesJar, new Action() { - @Override - public void execute(ArtifactSpec spec) - { - JavaPluginConvention javaConv = (JavaPluginConvention) getConvention().getPlugins().get("java") - spec.setClasspath(javaConv.getSourceSets().getByName("main").getCompileClasspath()) - spec.setArchiveName(null); - spec.setClassifier('reobf'); - } - }) - - mustRunAfter("test") -} - -task sidesplit(type: SideSplitTask) { - sidesplit(reobf) -} - -processServerResources { - from sourceSets.main.resources.srcDirs - into new File(buildDir, 'resources_server') -} - -processClientResources { - from sourceSets.main.resources.srcDirs - into new File(buildDir, 'resources_client') -} - -jar_server { - from fileTree(sidesplit.getServerClasses()), processServerResources - classifier = 'server' - dependsOn(sidesplit, processServerResources) -} - -task jar_client(type: Jar) { - from fileTree(sidesplit.getClientClasses()), processClientResources - classifier = 'client' - dependsOn(sidesplit, processClientResources) -} - task jar_source(type: Jar) { from sourceSets.main.allSource classifier = 'sources' } -assemble.dependsOn(jar_server, jar_client) +artifacts { + if(produce_universal_jar == 'true') archives jar_universal + if(produce_server_jar == 'true') archives jar_server + if(produce_client_jar == 'true') archives jar_client +} publishing { - tasks.publish.dependsOn(jar, jar_source) publications { - mavenJava(MavenPublication) { - from components.java - artifacts.matching({ - it.classifier == "dev" - }).all({ - it.classifier = null - }) - artifact jar_source - } + if(publish_jars.contains('dev')) + mavenDevJar(MavenPublication) { + from new JavaLibrary(new ArchivePublishArtifact(tasks.jar), configurations.runtimeCommon.getAllDependencies()) + artifacts.matching({it.classifier == "dev"}).all({it.classifier = null}) + artifact tasks.jar_source + } + if(publish_jars.contains('universal')) + mavenUnivarsalJar(MavenPublication) { + from new JavaLibrary(new ArchivePublishArtifact(tasks.jar_universal), configurations.runtimeAll.getAllDependencies()) + artifacts.matching({it.classifier == "universal"}).all({it.classifier = null}) + artifactId = project.name + '-universal' + } + if(publish_jars.contains('server')) + mavenServerJar(MavenPublication) { + from new JavaLibrary(new ArchivePublishArtifact(tasks.jar_server), configurations.runtimeServer.getAllDependencies()) + artifacts.matching({it.classifier == "server"}).all({it.classifier = null}) + artifactId = project.name + '-server' + } + if(publish_jars.contains('client')) + mavenClientJar(MavenPublication) { + from new JavaLibrary(new ArchivePublishArtifact(tasks.jar_client), configurations.runtimeClient.getAllDependencies()) + artifacts.matching({it.classifier == "client"}).all({it.classifier = null}) + artifactId = project.name + '-client' + } } repositories { - if (project.hasProperty('mavendir')) { + if(project.hasProperty('publish_url') && !publish_url.isEmpty()) maven { - url mavendir + url publish_url } - } } } @@ -247,130 +216,57 @@ from configurations.runtime } -def delayedFile(String path) { - new DelayedFile(project, path) { - @Override - File resolveDelayed() { - return file(path) - } - } +String getGitDesc() { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags', '--long' + standardOutput = stdout + errorOutput = stdout + }.rethrowFailure().assertNormalExitValue() + return stdout.toString().trim() } -def delayedString(String str) { - new DelayedString(project, str) { - @Override - String resolveDelayed() { - return str - } - } -} +String computeVersion() { + if(project.hasProperty('override_version')) + return override_version -class SideSplitTask extends DefaultTask { - private static final String SIDEONLY_DESK = Type.getDescriptor(SideOnly.class); - private ReobfTask reobf; - private File taskDir = new File(getProject().buildDir, getName()); - private File classes_server = new File(taskDir, "classes_server"); - private File classes_client = new File(taskDir, "classes_client"); - - def sidesplit(ReobfTask reobf) { - this.reobf = reobf; - getInputs().files({reobf.getObfuscatedFiles()}); - getOutputs().files(classes_server, classes_client); - dependsOn(reobf); + String mmversion; // major.minor + String commit; + try { + String[] parts = getGitDesc().split('-') + if(parts.length != 3) throw new GradleException('no git tags found') + if(!parts[0].startsWith('v')) throw new GradleException('last git tag is not a version') + mmversion = parts[0].substring(1) //removing 'v' + commit = parts[2].substring(1) //removing 'g' + } catch (Exception e) { + if(release_type != 'indev') throw e + return 'indev' } - def getServerClasses() { - classes_server + if(release_type != 'stable') { + int ind = mmversion.lastIndexOf('.') + mmversion = mmversion.substring(0, ind+1) + ((mmversion.substring(ind+1) as int) + 1) + '.0-' + release_type } - def getClientClasses() { - classes_client - } - - @TaskAction - def doSplit() { - File toSplit = reobf.getObfuscatedFiles().getSingleFile(); - File toSplitemp = File.createTempFile("tosplit", ".jar", getTemporaryDir()); - Files.copy(toSplit, toSplitemp); - - classes_server.mkdirs(); - classes_client.mkdirs(); - FileUtils.cleanDirectory(classes_server); - FileUtils.cleanDirectory(classes_client); - ZipInputStream zipIn = new ZipInputStream(new FileInputStream(toSplitemp)); - ZipEntry entry = null; - while ((entry = zipIn.getNextEntry()) != null) { - if(entry.isDirectory()) - continue; - byte[] common = ByteStreams.toByteArray(zipIn); - zipIn.closeEntry(); - if(entry.getName().endsWith('.class')) { - byte[] server_cls = processClass(common, 'SERVER'); - byte[] client_cls = processClass(common, 'CLIENT'); - if(server_cls != null) - addEntry(classes_server, entry, server_cls); - if(client_cls != null) - addEntry(classes_client, entry, client_cls); - } else { - addEntry(classes_server, entry, common); - addEntry(classes_client, entry, common); + int revision = 0; + if(project.hasProperty('override_revision')) { + revision = override_revision as int + } else if(release_type != 'indev' || project.hasProperty('increment_revision')) { + File verfile = file("$buildDir/versions/$mmversion") + String filetext; + if(verfile.exists() && !(filetext = verfile.getText().trim()).isEmpty()) { + String[] fileparts = filetext.split(':') + if(fileparts.length != 2) throw new GradleException('Version file is corrupted: ' + verfile.getAbsolutePath()) + revision = fileparts[1] as int; + if(!commit.equals(fileparts[0]) || project.hasProperty('increment_revision')) { + revision++; + verfile.write(commit+':'+revision) } + } else { + verfile.getParentFile().mkdirs() + verfile.write(commit+':0') } - - zipIn.close(); - toSplitemp.delete(); - } - - private void addEntry(File dir, ZipEntry entry, byte[] contents) { - File file = new File(dir, entry.getName()); - file.getParentFile().mkdirs(); - Files.write(contents, file); } - private byte[] processClass(byte[] input, String side) { - ClassNode classNode = new ClassNode(); - ClassReader classReader = new ClassReader(input); - classReader.accept(classNode, 0); - - if(remove((List)classNode.visibleAnnotations, side)) - return null; - - Iterator fields = classNode.fields.iterator(); - while(fields.hasNext()) { - FieldNode field = fields.next(); - if(remove((List)field.visibleAnnotations, side)) - fields.remove(); - } - Iterator methods = classNode.methods.iterator(); - while(methods.hasNext()) { - MethodNode method = methods.next(); - if(remove((List)method.visibleAnnotations, side)) - methods.remove(); - } - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - classNode.accept(writer); - return writer.toByteArray(); - } - - private boolean remove(List anns, String side) { - if(anns == null) - return false; - for(AnnotationNode ann : anns) { - if(ann.desc.equals(SIDEONLY_DESK) && ann.values != null) { - for (int x = 0; x < ann.values.size() - 1; x += 2) { - Object key = ann.values.get(x); - Object value = ann.values.get(x+1); - if (key instanceof String && key.equals('value')) { - if (value instanceof String[] ) { - if (!((String[])value)[1].equals(side)) { - return true; - } - } - } - } - } - } - return false; - } + return (revision > 0 || release_type == 'stable') ? (mmversion+'.'+revision) : mmversion; } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..7dc5b0d --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,15 @@ +apply plugin: 'java' + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +repositories { + mavenCentral() +} + +dependencies { + compile gradleApi() + compile 'org.ow2.asm:asm-debug-all:5.0.3' + compile 'commons-io:commons-io:2.4' + compile 'net.md-5:SpecialSource:1.7.3' +} diff --git a/buildSrc/src/main/java/org/ultramine/gradle/internal/DirectoryClassRepo.java b/buildSrc/src/main/java/org/ultramine/gradle/internal/DirectoryClassRepo.java new file mode 100644 index 0000000..d4db7eb --- /dev/null +++ b/buildSrc/src/main/java/org/ultramine/gradle/internal/DirectoryClassRepo.java @@ -0,0 +1,38 @@ +package org.ultramine.gradle.internal; + +import net.md_5.specialsource.repo.CachingRepo; +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import java.io.File; +import java.io.IOException; + +public class DirectoryClassRepo extends CachingRepo +{ + private final File dir; + + public DirectoryClassRepo(File dir) + { + this.dir = dir; + } + + @Override + protected ClassNode findClass0(String internalName) + { + File file = new File(dir, internalName + ".class"); + if(!file.isFile()) + return null; + try { + byte[] bytes = FileUtils.readFileToByteArray(file); + + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + return node; + } catch (IOException e) { + throw new RuntimeException("Failed to read classfile: "+file.getAbsolutePath(), e); + } + } +} diff --git a/buildSrc/src/main/java/org/ultramine/gradle/internal/RepoInheritanceProvider.java b/buildSrc/src/main/java/org/ultramine/gradle/internal/RepoInheritanceProvider.java new file mode 100644 index 0000000..1744e48 --- /dev/null +++ b/buildSrc/src/main/java/org/ultramine/gradle/internal/RepoInheritanceProvider.java @@ -0,0 +1,35 @@ +package org.ultramine.gradle.internal; + +import net.md_5.specialsource.provider.InheritanceProvider; +import net.md_5.specialsource.repo.ClassRepo; +import org.objectweb.asm.tree.ClassNode; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +public class RepoInheritanceProvider implements InheritanceProvider +{ + private final ClassRepo repo; + + public RepoInheritanceProvider(ClassRepo repo) + { + this.repo = repo; + } + + @Override + public Collection getParents(String owner) { + ClassNode node = repo.findClass(owner); + if(node == null) { + return null; + } else { + ArrayList parents = new ArrayList(node.interfaces.size() + 1); + parents.addAll(node.interfaces); + + if(node.superName != null) + parents.add(node.superName); + + return parents; + } + } +} diff --git a/buildSrc/src/main/java/org/ultramine/gradle/internal/UMFileUtils.java b/buildSrc/src/main/java/org/ultramine/gradle/internal/UMFileUtils.java new file mode 100644 index 0000000..36f415e --- /dev/null +++ b/buildSrc/src/main/java/org/ultramine/gradle/internal/UMFileUtils.java @@ -0,0 +1,39 @@ +package org.ultramine.gradle.internal; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; + +public class UMFileUtils +{ + public static String getRelativePath(File base, File child) + { + return base.toURI().relativize(child.toURI()).getPath(); + } + + public static boolean isDirEmpty(final Path directory) throws IOException + { + try(DirectoryStream dirStream = Files.newDirectoryStream(directory)) + { + return !dirStream.iterator().hasNext(); + } + } + + public static boolean isDirEmptyRecursive(final Path directory) throws IOException + { + try(DirectoryStream dirStream = Files.newDirectoryStream(directory)) + { + for(Iterator it = dirStream.iterator(); it.hasNext();) + { + Path path = it.next(); + if(!Files.isDirectory(path) || !isDirEmptyRecursive(path)) + return false; + } + } + + return true; + } +} diff --git a/buildSrc/src/main/java/org/ultramine/gradle/task/ReobfTask.java b/buildSrc/src/main/java/org/ultramine/gradle/task/ReobfTask.java new file mode 100644 index 0000000..87ae3be --- /dev/null +++ b/buildSrc/src/main/java/org/ultramine/gradle/task/ReobfTask.java @@ -0,0 +1,208 @@ +package org.ultramine.gradle.task; + +import net.md_5.specialsource.JarMapping; +import net.md_5.specialsource.JarRemapper; +import net.md_5.specialsource.provider.ClassLoaderProvider; +import net.md_5.specialsource.provider.JointProvider; +import org.apache.commons.io.FileUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileVisitDetails; +import org.gradle.api.file.FileVisitor; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.ParallelizableTask; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.incremental.IncrementalTaskInputs; +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.ultramine.gradle.internal.DirectoryClassRepo; +import org.ultramine.gradle.internal.RepoInheritanceProvider; +import org.ultramine.gradle.internal.UMFileUtils; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +@ParallelizableTask +public class ReobfTask extends DefaultTask +{ + @InputDirectory + private File inputDir; + @InputDirectory + private File overrideInputDir; + @InputFile + private File srg; + private FileCollection classpath; + @OutputDirectory + private File outputDir = new File(getProject().getBuildDir(), getName()); + private DirectoryClassRepo classRepo; + private JarRemapper remapper; + + public File getSrg() + { + return srg; + } + + public void setSrg(File srg) + { + this.srg = srg; + } + + public void setSrg(String srg) + { + this.srg = getProject().file(srg); + } + + public File getInputDir() + { + return inputDir; + } + + public void setInputDir(File inputDir) + { + this.inputDir = inputDir; + } + + public File getOverrideInputDir() + { + return overrideInputDir; + } + + public void setOverrideInputDir(File overrideInputDir) + { + this.overrideInputDir = overrideInputDir; + } + + public FileCollection getClasspath() + { + return classpath; + } + + public void setClasspath(FileCollection classpath) + { + this.classpath = classpath; + } + + public File getOutputDir() + { + return outputDir; + } + + public void setOutputDir(File outputDir) + { + this.outputDir = outputDir; + } + + @TaskAction + void doAction(IncrementalTaskInputs inputs) throws IOException + { + initRemapper(); + if(!inputs.isIncremental()) + { + FileUtils.cleanDirectory(outputDir); + getProject().fileTree(inputDir).visit(new FileVisitor(){ + @Override + public void visitDir(FileVisitDetails dirDetails) + { + + } + + @Override + public void visitFile(FileVisitDetails fileDetails) + { + processClass(fileDetails.getPath()); + } + }); + } + else + { + inputs.outOfDate((InputFileDetails detals) -> processClass(detals.getFile())); + + Set dirsToCheck = new HashSet(); + inputs.removed((InputFileDetails detals) -> { + File file = new File(outputDir, getRelPath(detals.getFile())); + file.delete(); + dirsToCheck.add(file.getParentFile()); + }); + + for(File file : dirsToCheck) + if(file.exists() && UMFileUtils.isDirEmptyRecursive(file.toPath())) + { + FileUtils.deleteDirectory(file); + File parent = file.getParentFile(); + if(UMFileUtils.isDirEmpty(parent.toPath())) + FileUtils.deleteDirectory(parent); + } + } + } + + private String getRelPath(File file) + { + return UMFileUtils.getRelativePath(inputDir, file); + } + + private void processClass(File file) + { + if(file.isDirectory()) + return; + String pth1 = getRelPath(file); + String pth2 = UMFileUtils.getRelativePath(overrideInputDir, file); + processClass(pth2.length() < pth1.length() ? pth2 : pth1); + } + + private File resolveFile(String path) + { + if(overrideInputDir != null) + { + File file = new File(overrideInputDir, path); + if(file.exists()) + return file; + } + + return new File(inputDir, path); + } + + private void processClass(String path) + { + try + { + byte[] bytes = remapper.remapClassFile(FileUtils.readFileToByteArray(resolveFile(path)), classRepo); + FileUtils.writeByteArrayToFile(new File(outputDir, remapper.map(path.substring(0, path.length() - 6))+".class"), bytes); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + + private void initRemapper() throws IOException + { + JarMapping mapping = new JarMapping(); + mapping.loadMappings(srg); + classRepo = new DirectoryClassRepo(inputDir); + JointProvider inheritanceProviders = new JointProvider(); + inheritanceProviders.add(new RepoInheritanceProvider(classRepo)); + if(this.classpath != null) { + inheritanceProviders.add(new ClassLoaderProvider(new URLClassLoader(toUrls(this.classpath)))); + } + mapping.setFallbackInheritanceProvider(inheritanceProviders); + + this.remapper = new JarRemapper(null, mapping); + } + + private static URL[] toUrls(FileCollection collection) throws MalformedURLException + { + ArrayList urls = new ArrayList(); + + for(File file : collection.getFiles()) + urls.add(file.toURI().toURL()); + + return urls.toArray(new URL[urls.size()]); + } +} diff --git a/buildSrc/src/main/java/org/ultramine/gradle/task/SideSplitTask.java b/buildSrc/src/main/java/org/ultramine/gradle/task/SideSplitTask.java new file mode 100644 index 0000000..135dfb3 --- /dev/null +++ b/buildSrc/src/main/java/org/ultramine/gradle/task/SideSplitTask.java @@ -0,0 +1,226 @@ +package org.ultramine.gradle.task; + +import org.apache.commons.io.FileUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.FileVisitDetails; +import org.gradle.api.file.FileVisitor; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.ParallelizableTask; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.incremental.IncrementalTaskInputs; +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; +import org.ultramine.gradle.internal.UMFileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +@ParallelizableTask +public class SideSplitTask extends DefaultTask +{ + private static final String SIDEONLY_DESK = "Lcpw/mods/fml/relauncher/SideOnly;"; + @InputDirectory + private File inputDir; + @Input + private boolean outputServerSide = true; + @Input + private boolean outputClientSide = true; + private File taskDir = new File(getProject().getBuildDir(), getName()); + private File classesServer = new File(taskDir, "classes_server"); + private File classesClient = new File(taskDir, "classes_client"); + + public SideSplitTask() throws IOException + { + FileUtils.forceMkdir(classesServer); + FileUtils.forceMkdir(classesClient); + } + + public File getInputDir() + { + return inputDir; + } + + public void setInputDir(File inputDir) + { + this.inputDir = inputDir; + } + + public boolean isOutputServerSide() + { + return outputServerSide; + } + + public void setOutputServerSide(boolean outputServerSide) + { + this.outputServerSide = outputServerSide; + } + + public boolean isOutputClientSide() + { + return outputClientSide; + } + + public void setOutputClientSide(boolean outputClientSide) + { + this.outputClientSide = outputClientSide; + } + + @OutputDirectory + public File getServerClasses() + { + return classesServer; + } + + @OutputDirectory + public File getClientClasses() + { + return classesClient; + } + + @TaskAction + void doAction(IncrementalTaskInputs inputs) throws IOException + { + if(!inputs.isIncremental()) + { + FileUtils.cleanDirectory(classesServer); + FileUtils.cleanDirectory(classesClient); + getProject().fileTree(inputDir).visit(new FileVisitor(){ + @Override + public void visitDir(FileVisitDetails dirDetails) + { + + } + + @Override + public void visitFile(FileVisitDetails fileDetails) + { + processClass(fileDetails.getPath()); + } + }); + } + else + { + inputs.outOfDate((InputFileDetails detals) -> processClass(detals.getFile())); + + Set dirsToCheck = new HashSet(); + inputs.removed((InputFileDetails detals) -> { + File file = new File(classesServer, getRelPath(detals.getFile())); + file.delete(); + dirsToCheck.add(file.getParentFile()); + file = new File(classesClient, getRelPath(detals.getFile())); + file.delete(); + dirsToCheck.add(file.getParentFile()); + }); + + for(File file : dirsToCheck) + if(file.exists() && UMFileUtils.isDirEmptyRecursive(file.toPath())) + { + FileUtils.deleteDirectory(file); + File parent = file.getParentFile(); + if(UMFileUtils.isDirEmpty(parent.toPath())) + FileUtils.deleteDirectory(parent); + } + } + } + + private String getRelPath(File file) + { + return UMFileUtils.getRelativePath(inputDir, file); + } + + private void processClass(File file) + { + if(file.isDirectory()) + return; + processClass(getRelPath(file)); + } + + private void processClass(String path) + { + try + { + byte[] cls = FileUtils.readFileToByteArray(new File(inputDir, path)); + if(outputServerSide) + { + byte[] serverCls = processClass(cls, "SERVER"); + if(serverCls != null) + writeClass(classesServer, path, serverCls); + } + if(outputClientSide) + { + byte[] clientCls = processClass(cls, "CLIENT"); + if(clientCls != null) + writeClass(classesClient, path, clientCls); + } + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + + private void writeClass(File dir, String path, byte[] cls) throws IOException + { + FileUtils.writeByteArrayToFile(new File(dir, path), cls); + } + + private byte[] processClass(byte[] input, String side) + { + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(input); + classReader.accept(classNode, 0); + + if(remove(classNode.visibleAnnotations, side)) + return null; + + Iterator fields = classNode.fields.iterator(); + while(fields.hasNext()) + { + FieldNode field = fields.next(); + if(remove(field.visibleAnnotations, side)) + fields.remove(); + } + Iterator methods = classNode.methods.iterator(); + while(methods.hasNext()) + { + MethodNode method = methods.next(); + if(remove(method.visibleAnnotations, side)) + methods.remove(); + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + + private boolean remove(List anns, String side) + { + if(anns == null) + return false; + for(AnnotationNode ann : anns) + { + if(ann.desc.equals(SIDEONLY_DESK) && ann.values != null) + { + for(int x = 0; x < ann.values.size() - 1; x += 2) + { + Object key = ann.values.get(x); + Object value = ann.values.get(x+1); + if(key.equals("value") && value instanceof String[] && !((String[])value)[1].equals(side)) + return true; + } + } + } + return false; + } +} diff --git a/buildSrc/src/main/java/org/ultramine/gradle/task/SpeicialClassTransformTask.java b/buildSrc/src/main/java/org/ultramine/gradle/task/SpeicialClassTransformTask.java new file mode 100644 index 0000000..f6ab49b --- /dev/null +++ b/buildSrc/src/main/java/org/ultramine/gradle/task/SpeicialClassTransformTask.java @@ -0,0 +1,215 @@ +package org.ultramine.gradle.task; + +import groovy.lang.Closure; +import org.apache.commons.io.FileUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.ParallelizableTask; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.incremental.IncrementalTaskInputs; +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.ultramine.gradle.internal.UMFileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +@ParallelizableTask +public class SpeicialClassTransformTask extends DefaultTask +{ + private File inputDir; + @OutputDirectory + private File outputDir = new File(getProject().getBuildDir(), getName()); + @Input + private final List transformers = new ArrayList<>(); + private final Map> transformerMap = new HashMap<>(); + + public File getInputDir() + { + return inputDir; + } + + public void setInputDir(File inputDir) + { + this.inputDir = inputDir; + } + + public File getOutputDir() + { + return outputDir; + } + + public void setOutputDir(File outputDir) + { + this.outputDir = outputDir; + } + + public List getTransformers() + { + return transformers; + } + + public void addTransformer(ISpecialTransformer transformer) + { + transformers.add(transformer); + transformerMap.computeIfAbsent(transformer.getPath(), k -> new ArrayList<>()).add(transformer); + getInputs().file(new Closure(null, null) + { + @Override + public File call() + { + return new File(inputDir, transformer.getPath()); + } + }); + } + + @TaskAction + void doAction(IncrementalTaskInputs inputs) throws IOException + { + if(!inputs.isIncremental()) + { + FileUtils.cleanDirectory(outputDir); + for(Map.Entry> ent : transformerMap.entrySet()) + { + processClass(ent.getKey(), ent.getValue()); + } + } + else + { + inputs.outOfDate((InputFileDetails detals) -> processClass(detals.getFile())); + + Set dirsToCheck = new HashSet(); + inputs.removed((InputFileDetails detals) -> { + File file = new File(outputDir, getRelPath(detals.getFile())); + file.delete(); + dirsToCheck.add(file.getParentFile()); + }); + + for(File file : dirsToCheck) + if(file.exists() && UMFileUtils.isDirEmpty(file.toPath())) + file.delete(); + } + } + + private String getRelPath(File file) + { + return UMFileUtils.getRelativePath(inputDir, file); + } + + private void processClass(File file) + { + if(file.isDirectory()) + return; + String path = getRelPath(file); + processClass(path, transformerMap.get(file)); + } + + private void processClass(String path, List transfs) + { + if(transfs == null || transfs.isEmpty()) + return; + try + { + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(FileUtils.readFileToByteArray(new File(inputDir, path))); + classReader.accept(classNode, 0); + + int flags = 0; + for(ISpecialTransformer transformer : transfs) + { + transformer.transform(classNode); + flags |= transformer.getWriteFlags(); + } + + ClassWriter writer = new ClassWriter(flags); + classNode.accept(writer); + FileUtils.writeByteArrayToFile(new File(outputDir, path), writer.toByteArray()); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + + public void replace(Closure clsr) + { + ReplaceStringTransformer transformer = new ReplaceStringTransformer(); + clsr.setDelegate(transformer); + clsr.call(); + addTransformer(transformer); + } + + public interface ISpecialTransformer extends Serializable + { + /** @return path to target class file relative to input directory (with .class extension) */ + String getPath(); + void transform(ClassNode node); + int getWriteFlags(); + } + + public static class ReplaceStringTransformer implements ISpecialTransformer + { + private String path; + private Map replaceMap = new HashMap<>(); + + @Override + public String getPath() + { + return path; + } + + @Override + public void transform(ClassNode node) + { + for(Object o : node.methods) + { + MethodNode m = (MethodNode) o; + for(ListIterator it = m.instructions.iterator(); it.hasNext(); ) + { + AbstractInsnNode insnNode = it.next(); + if(insnNode.getOpcode() == Opcodes.LDC) + { + LdcInsnNode ldc = (LdcInsnNode)insnNode; + String replacement; + if(ldc.cst instanceof String && (replacement = replaceMap.get(ldc.cst)) != null) + ldc.cst = replacement; + } + } + } + } + + @Override + public int getWriteFlags() + { + return 0; + } + + public void replaceIn(String path) + { + path = path.replace('.', '/'); + if(!path.endsWith(".class")) + path += ".class"; + this.path = path; + } + + public void replace(String search, String replacement) + { + replaceMap.put(search, replacement); + } + } +} diff --git a/gradle.properties b/gradle.properties index 476856b..66b5bc0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,35 @@ +project_name=ultramine_core +project_group=org.ultramine.core + +# +# Versioning minecraft_version=1.7.10 + +#concat minecraft version to: +#name - end of project name ({name}-{minecraft_version}), +#version - start of project version ({minecraft_version}-{version}), +#none - don't concat +concat_mc_version_to=name + +#major.minor pair always gets from git tag +#release_type affects version string forming scheme: +#indev - {major}.{minor}.0-indev +#stable - {major}.{minor}.{revision (inc each build)} +#any_other_string (alpha/beta/rc/etc) - {major}.{minor}.0-{any_other_string}.{revision (inc each build)} +release_type=indev + +#debug version options +#override_version= +#override_revision= +#increment_revision= + +# +# Artifacts +produce_universal_jar=false +produce_server_jar=true +produce_client_jar=true + +# +# Publish (injecting) +publish_jars= +publish_url= diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1eed3a5..eb67457 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/settings.gradle b/settings.gradle index 19c2483..d7f5c74 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'ultramine_core' +rootProject.name = project_name + (concat_mc_version_to=='name' ? ('-'+minecraft_version) : '') diff --git a/src/main/java/org/ultramine/server/UltramineServerModContainer.java b/src/main/java/org/ultramine/server/UltramineServerModContainer.java index 530e228..f2c54f1 100644 --- a/src/main/java/org/ultramine/server/UltramineServerModContainer.java +++ b/src/main/java/org/ultramine/server/UltramineServerModContainer.java @@ -70,7 +70,7 @@ ModMetadata meta = getMetadata(); meta.modId = "UltramineServer"; meta.name = "Ultramine Server"; - meta.version = "1.0"; + meta.version = "@version@"; } public static UltramineServerModContainer getInstance()