commit 7efa7780fed264e2744d241716d60b2f8712db13 Author: asiekierka Date: Mon Aug 15 12:22:14 2016 +0200 recreate repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..728d01c --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/.idea + +/work +/tools +/build +/.gradle +*.log +*.reject +/run/ + +*.iml +*.ipr +*.iws +/out +/classes/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f8c7c2f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 ChorusMC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b56a32 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# ChorusGradle +Gradle plugin for Chorus + +run : bash gradlew setupChorus genIdeaRuns + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6d07162 --- /dev/null +++ b/build.gradle @@ -0,0 +1,62 @@ +apply plugin: 'java' +apply plugin: 'groovy' +apply plugin: 'maven' +apply plugin: 'maven-publish' +apply plugin: 'idea' + +group = 'chorusmc.gradle' +version = '0.0.1' +sourceCompatibility = targetCompatibility = '1.8' + +if (System.getenv().BUILD_NUMBER) { + version = version + "." + "${System.getenv().BUILD_NUMBER}" +} + +repositories { + mavenCentral() + flatDir { + dirs 'libs' + } + ivy { + name "Enigma-Temporary" + artifactPattern "http://modmuss50.me:8080/job/Enigma-Asie/lastSuccessfulBuild/artifact/build/libs/[module]-[revision]-[classifier].[ext]" + } +} + +configurations { + deployerJars + shade + compile.extendsFrom shade +} + +dependencies { + compile gradleApi() + shade 'org.zeroturnaround:zt-zip:1.9' + compile 'org.slf4j:slf4j-api:1.7.21' + compile 'org.slf4j:slf4j-simple:1.7.21' + shade 'com.google.code.gson:gson:2.6.2' + shade 'com.googlecode.java-diff-utils:diffutils:1.3.0' + shade 'com.cloudbees:diff4j:1.2' + shade 'commons-io:commons-io:1.4' + shade 'com.google.guava:guava-jdk5:17.0' + shade 'org.ow2.asm:asm-debug-all:5.0.3' + shade 'com.google.guava:guava:19.0' + shade name: "enigma", version: "0.11.0.6", classifier: "all" +} + +sourceSets { + main.compileClasspath += configurations.shade; + main.runtimeClasspath += configurations.shade; + test.compileClasspath += configurations.shade; + test.runtimeClasspath += configurations.shade; +} + +jar { + configurations.shade.each { dep -> + from(project.zipTree(dep)) { + exclude 'META-INF', 'META-INF/**' + } + } +} + +//TODO maven uploading diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..30d399d Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b163e60 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jun 22 18:29:49 CEST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d55c27e --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'ChorusGradle' + diff --git a/src/main/java/chorusmc/gradle/AbstractPlugin.java b/src/main/java/chorusmc/gradle/AbstractPlugin.java new file mode 100644 index 0000000..b411611 --- /dev/null +++ b/src/main/java/chorusmc/gradle/AbstractPlugin.java @@ -0,0 +1,118 @@ +package chorusmc.gradle; + +import chorusmc.gradle.util.Constants; +import com.google.common.collect.ImmutableMap; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.plugins.ide.eclipse.model.EclipseModel; +import org.gradle.plugins.ide.idea.model.IdeaModel; + +public class AbstractPlugin implements Plugin { + protected Project project; + + @Override + public void apply(Project target) { + this.project = target; + + // Apply default plugins + project.apply(ImmutableMap.of("plugin", "java")); + project.apply(ImmutableMap.of("plugin", "eclipse")); + project.apply(ImmutableMap.of("plugin", "idea")); + + project.getExtensions().create("minecraft", ChorusGradleExtension.class); + + // Force add Mojang repository + addMavenRepo(target, "Mojang", "https://libraries.minecraft.net/"); + + // Minecraft libraries configuration + project.getConfigurations().maybeCreate(Constants.CONFIG_MC_DEPENDENCIES); + project.getConfigurations().maybeCreate(Constants.CONFIG_MC_DEPENDENCIES_CLIENT); + project.getConfigurations().maybeCreate(Constants.CONFIG_NATIVES); + + // Common libraries extends from client libraries, CONFIG_MC_DEPENDENCIES will contains all MC dependencies + project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES).extendsFrom(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES_CLIENT)); + + configureIDEs(); + configureCompile(); + } + + /** + * Permit to create a Task instance of the type in the project + * + * @param name The name of the task + * @param type The type of the task that will be used to create an instance + * @return The created task object for the project + */ + public T makeTask(String name, Class type) { + return makeTask(project, name, type); + } + + /** + * Permit to create a Task instance of the type in a project + * + * @param target The target project + * @param name The name of the task + * @param type The type of the task that will be used to create an instance + * @return The created task object for the specified project + */ + public static T makeTask(Project target, String name, Class type) { + return target.getTasks().create(name, type); + } + + /** + * Permit to add a Maven repository to a target project + * + * @param target The garget project + * @param name The name of the repository + * @param url The URL of the repository + * @return An object containing the name and the URL of the repository that can be modified later + */ + public MavenArtifactRepository addMavenRepo(Project target, final String name, final String url) { + return target.getRepositories().maven(repo -> { + repo.setName(name); + repo.setUrl(url); + }); + } + + /** + * Add Minecraft dependencies to IDE dependencies + */ + protected void configureIDEs() { + // IDEA + IdeaModel ideaModule = (IdeaModel) project.getExtensions().getByName("idea"); + + ideaModule.getModule().getExcludeDirs().addAll(project.files(".gradle", "build", ".idea", "out").getFiles()); + ideaModule.getModule().setDownloadJavadoc(true); + ideaModule.getModule().setDownloadSources(true); + ideaModule.getModule().setInheritOutputDirs(true); + ideaModule.getModule().getScopes().get("COMPILE").get("plus").add(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES)); + + // ECLIPSE + EclipseModel eclipseModule = (EclipseModel) project.getExtensions().getByName("eclipse"); + eclipseModule.getClasspath().getPlusConfigurations().add(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES)); + } + + /** + * Add Minecraft dependencies to compile time + */ + protected void configureCompile() { + JavaPluginConvention javaModule = (JavaPluginConvention) project.getConvention().getPlugins().get("java"); + + SourceSet main = javaModule.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet test = javaModule.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME); + + main.setCompileClasspath(main.getCompileClasspath().plus(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES))); + test.setCompileClasspath(test.getCompileClasspath().plus(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES))); + main.setRuntimeClasspath(main.getCompileClasspath().plus(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES))); + test.setCompileClasspath(test.getCompileClasspath().plus(project.getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES))); + + Javadoc javadoc = (Javadoc) project.getTasks().getByName(JavaPlugin.JAVADOC_TASK_NAME); + javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath())); + } +} diff --git a/src/main/java/chorusmc/gradle/ChorusGradleExtension.java b/src/main/java/chorusmc/gradle/ChorusGradleExtension.java new file mode 100644 index 0000000..ecc06fd --- /dev/null +++ b/src/main/java/chorusmc/gradle/ChorusGradleExtension.java @@ -0,0 +1,6 @@ +package chorusmc.gradle; + +public class ChorusGradleExtension { + public String version; + public String runDir = "run"; +} diff --git a/src/main/java/chorusmc/gradle/ChorusGradlePlugin.java b/src/main/java/chorusmc/gradle/ChorusGradlePlugin.java new file mode 100644 index 0000000..fb311e2 --- /dev/null +++ b/src/main/java/chorusmc/gradle/ChorusGradlePlugin.java @@ -0,0 +1,19 @@ +package chorusmc.gradle; + +import chorusmc.gradle.task.*; +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; + +public class ChorusGradlePlugin extends AbstractPlugin { + @Override + public void apply(Project target) { + super.apply(target); + + makeTask("download", DownloadTask.class); + makeTask("mapJars", MapJarsTask.class).dependsOn("download"); + makeTask("setupChorus", DefaultTask.class).dependsOn("mapJars"); + + makeTask("extractNatives", ExtractNativesTask.class).dependsOn("download"); + makeTask("genIdeaRuns", GenIdeaProjectTask.class).dependsOn("cleanIdea").dependsOn("idea").dependsOn("extractNatives"); + } +} diff --git a/src/main/java/chorusmc/gradle/task/DownloadNewTask.java b/src/main/java/chorusmc/gradle/task/DownloadNewTask.java new file mode 100644 index 0000000..b8658d9 --- /dev/null +++ b/src/main/java/chorusmc/gradle/task/DownloadNewTask.java @@ -0,0 +1,132 @@ +package chorusmc.gradle.task; + +import chorusmc.gradle.util.Constants; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import chorusmc.gradle.util.progress.ProgressLogger; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; + +/** + * Generic Download class compatible with ProgressLogger + */ +public class DownloadNewTask extends DefaultTask { + + @Input + private Object output; + + @OutputFile + private String url; + private String taskName; + + @TaskAction + public void download() throws IOException { + File outputFile = getProject().file(getOutput()); + outputFile.getParentFile().mkdirs(); + outputFile.createNewFile(); + + getLogger().info("Downloading " + getURL()); + + URL url = new URL(getURL()); + HttpURLConnection connect = (HttpURLConnection) url.openConnection(); + connect.setRequestProperty("User-Agent", Constants.USER_AGENT); + connect.setInstanceFollowRedirects(true); + + ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName()); + progressLogger.setDescription("Downloading " + getURL()); + ReadableByteChannel inChannel = new DownloadChannel(Channels.newChannel(connect.getInputStream()), getContentLength(url), progressLogger); + FileChannel outChannel = new FileOutputStream(outputFile).getChannel(); + outChannel.transferFrom(inChannel, 0, Long.MAX_VALUE); + outChannel.close(); + inChannel.close(); + progressLogger.completed(); + getLogger().info("Download complete"); + } + + private int getContentLength(URL url) { + HttpURLConnection connection; + int contentLength = -1; + try { + connection = (HttpURLConnection) url.openConnection(); + contentLength = connection.getContentLength(); + } catch (Exception e) { + } + return contentLength; + } + + public File getOutput() { + return getProject().file(output); + } + + public void setOutput(Object output) { + this.output = output; + } + + public String getURL() { + return url; + } + + public void setURL(String url) { + this.url = url; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public String getTaskName() { + return taskName; + } + + class DownloadChannel implements ReadableByteChannel { + ProgressLogger logger; + String formattedSize; + ReadableByteChannel rbc; + long totalBytes; + + DownloadChannel(ReadableByteChannel rbc, long expectedSize, ProgressLogger logger) { + this.logger = logger; + this.formattedSize = toHumanReadableLength(expectedSize); + this.rbc = rbc; + } + + public void close() throws IOException { + rbc.close(); + } + + public boolean isOpen() { + return rbc.isOpen(); + } + + public int read(ByteBuffer buffer) throws IOException { + int processedBytes; + if ((processedBytes = rbc.read(buffer)) > 0) { + totalBytes += processedBytes; + logger.progress(toHumanReadableLength(totalBytes) + "/" + formattedSize + " downloaded"); + } + return processedBytes; + } + + private String toHumanReadableLength(long bytes) { + if (bytes < 1024) { + return bytes + " B"; + } else if (bytes < 1024 * 1024) { + return (bytes / 1024) + " KB"; + } else if (bytes < 1024 * 1024 * 1024) { + return String.format("%.2f MB", bytes / (1024.0 * 1024.0)); + } else { + return String.format("%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0)); + } + } + } +} diff --git a/src/main/java/chorusmc/gradle/task/DownloadTask.java b/src/main/java/chorusmc/gradle/task/DownloadTask.java new file mode 100644 index 0000000..ccf544b --- /dev/null +++ b/src/main/java/chorusmc/gradle/task/DownloadTask.java @@ -0,0 +1,124 @@ +package chorusmc.gradle.task; + +import chorusmc.gradle.util.Constants; +import chorusmc.gradle.util.Version; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.apache.commons.io.FileUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.tasks.TaskAction; +import chorusmc.gradle.ChorusGradleExtension; +import chorusmc.gradle.util.Checksum; +import chorusmc.gradle.util.ManifestVersion; +import chorusmc.gradle.util.assets.AssetIndex; +import chorusmc.gradle.util.assets.AssetObject; +import chorusmc.gradle.util.progress.ProgressLogger; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.Optional; + +public class DownloadTask extends DefaultTask { + @TaskAction + public void download() { + try { + ChorusGradleExtension extension = this.getProject().getExtensions().getByType(ChorusGradleExtension.class); + + if (!Constants.MINECRAFT_JSON.get(extension).exists()) { + this.getLogger().lifecycle(":downloading minecraft json"); + FileUtils.copyURLToFile(new URL("https://launchermeta.mojang.com/mc/game/version_manifest.json"), Constants.VERSION_MANIFEST); + ManifestVersion mcManifest = new GsonBuilder().create().fromJson(FileUtils.readFileToString(Constants.VERSION_MANIFEST), ManifestVersion.class); + + Optional optionalVersion = mcManifest.versions.stream().filter(versions -> versions.id.equalsIgnoreCase(extension.version)).findFirst(); + if (optionalVersion.isPresent()) { + FileUtils.copyURLToFile(new URL(optionalVersion.get().url), Constants.MINECRAFT_JSON.get(extension)); + } else { + this.getLogger().info(":failed downloading minecraft json"); + throw new RuntimeException("Failed downloading Minecraft json"); + } + } + + Gson gson = new Gson(); + Version version = gson.fromJson(new FileReader(Constants.MINECRAFT_JSON.get(extension)), Version.class); + + if (!Constants.MINECRAFT_CLIENT_JAR.get(extension).exists() || !Checksum.equals(Constants.MINECRAFT_CLIENT_JAR.get(extension), version.downloads.get("client").sha1)) { + this.getLogger().lifecycle(":downloading client"); + FileUtils.copyURLToFile(new URL(version.downloads.get("client").url), Constants.MINECRAFT_CLIENT_JAR.get(extension)); + } + + if (Constants.MAPPINGS_ZIP.exists()) { + Constants.MAPPINGS_ZIP.delete(); + } + + this.getLogger().lifecycle(":downloading mappings"); + FileUtils.copyURLToFile(new URL("https://github.com/ChorusMC/pomf/archive/master.zip"), Constants.MAPPINGS_ZIP); + + DependencyHandler dependencyHandler = getProject().getDependencies(); + + if (getProject().getConfigurations().getByName(Constants.CONFIG_MC_DEPENDENCIES).getState() == Configuration.State.UNRESOLVED) { + for (Version.Library library : version.libraries) { + if (library.allowed() && library.getFile() != null) { + // By default, they are all available on all sides + String configName = Constants.CONFIG_MC_DEPENDENCIES; + if (library.name.contains("java3d") || library.name.contains("paulscode") || library.name.contains("lwjgl") || library.name.contains("twitch") || library.name.contains("jinput")) { + configName = Constants.CONFIG_MC_DEPENDENCIES_CLIENT; + } + dependencyHandler.add(configName, library.getArtifactName()); + } + } + } + + if (getProject().getConfigurations().getByName(Constants.CONFIG_NATIVES).getState() == Configuration.State.UNRESOLVED) { + version.libraries.stream().filter(lib -> lib.natives != null).forEach(lib -> dependencyHandler.add(Constants.CONFIG_NATIVES, lib.getArtifactName())); + } + + // Force add LaunchWrapper + dependencyHandler.add(Constants.CONFIG_MC_DEPENDENCIES, "net.minecraft:launchwrapper:1.11"); + + Version.AssetIndex assetIndex = version.assetIndex; + + File assets = new File(Constants.CACHE_FILES, "assets"); + if (!assets.exists()) { + assets.mkdirs(); + } + + File assetsInfo = new File(assets, "indexes" + File.separator + assetIndex.id + ".json"); + if (!assetsInfo.exists() || !Checksum.equals(assetsInfo, assetIndex.sha1)) { + this.getLogger().lifecycle(":downloading asset index"); + FileUtils.copyURLToFile(new URL(assetIndex.url), assetsInfo); + } + + ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName()); + progressLogger.start("Downloading assets...", "assets"); + AssetIndex index = new Gson().fromJson(new FileReader(assetsInfo), AssetIndex.class); + Map parent = index.getFileMap(); + final int totalSize = parent.size(); + int position = 0; + this.getLogger().lifecycle(":downloading assets..."); + for (Map.Entry entry : parent.entrySet()) { + AssetObject object = entry.getValue(); + String sha1 = object.getHash(); + File file = new File(assets, "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1); + if (!file.exists() || !Checksum.equals(file, sha1)) { + this.getLogger().debug(":downloading asset " + entry.getKey()); + FileUtils.copyURLToFile(new URL(Constants.RESOURCES_BASE + sha1.substring(0, 2) + "/" + sha1), file); + } + String assetName = entry.getKey(); + int end = assetName.lastIndexOf("/") + 1; + if (end > 0) { + assetName = assetName.substring(end, assetName.length()); + } + progressLogger.progress(assetName + " - " + position + "/" + totalSize + " (" + (int) ((position / (double) totalSize) * 100) + "%) assets downloaded"); + position++; + } + progressLogger.completed(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/chorusmc/gradle/task/ExtractNativesTask.java b/src/main/java/chorusmc/gradle/task/ExtractNativesTask.java new file mode 100644 index 0000000..41577d4 --- /dev/null +++ b/src/main/java/chorusmc/gradle/task/ExtractNativesTask.java @@ -0,0 +1,20 @@ +package chorusmc.gradle.task; + +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.TaskAction; +import org.zeroturnaround.zip.ZipUtil; +import chorusmc.gradle.util.Constants; + +import java.io.File; +import java.io.FileNotFoundException; + +public class ExtractNativesTask extends DefaultTask { + @TaskAction + public void extractNatives() throws FileNotFoundException { + if (!Constants.MINECRAFT_NATIVES.exists()) { + for (File source : getProject().getConfigurations().getByName(Constants.CONFIG_NATIVES)) { + ZipUtil.unpack(source, Constants.MINECRAFT_NATIVES); + } + } + } +} diff --git a/src/main/java/chorusmc/gradle/task/GenIdeaProjectTask.java b/src/main/java/chorusmc/gradle/task/GenIdeaProjectTask.java new file mode 100644 index 0000000..1422e18 --- /dev/null +++ b/src/main/java/chorusmc/gradle/task/GenIdeaProjectTask.java @@ -0,0 +1,167 @@ +package chorusmc.gradle.task; + +import chorusmc.gradle.ChorusGradleExtension; +import chorusmc.gradle.util.Constants; +import chorusmc.gradle.util.IdeaRunConfig; +import chorusmc.gradle.util.Version; +import com.google.gson.Gson; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.TaskAction; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class GenIdeaProjectTask extends DefaultTask { + @TaskAction + public void genIdeaRuns() throws IOException, ParserConfigurationException, SAXException, TransformerException { + ChorusGradleExtension extension = this.getProject().getExtensions().getByType(ChorusGradleExtension.class); + + File file = new File(getProject().getName() + ".iml"); + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(file); + + Node component = null; + NodeList module = doc.getElementsByTagName("module").item(0).getChildNodes(); + for (int i = 0; i < module.getLength(); i++) { + if (module.item(i).getNodeName().equals("component")) { + component = module.item(i); + break; + } + } + + if (component == null) { + this.getLogger().lifecycle(":failed to generate intellij run configurations"); + return; + } + + Node content = null; + NodeList moduleList = component.getChildNodes(); + + for (int i = 0; i < moduleList.getLength(); i++) { + if (moduleList.item(i).getNodeName().equals("content")) { + content = moduleList.item(i); + } + } + + if (content == null) { + this.getLogger().lifecycle(":failed to generate intellij run configurations"); + return; + } + + Element sourceFolder = doc.createElement("sourceFolder"); + sourceFolder.setAttribute("url", "file://$MODULE_DIR$/minecraft/src/main/java"); + sourceFolder.setAttribute("isTestSource", "false"); + content.appendChild(sourceFolder); + + sourceFolder = doc.createElement("sourceFolder"); + sourceFolder.setAttribute("url", "file://$MODULE_DIR$/minecraft/src/main/resources"); + sourceFolder.setAttribute("type", "java-resource"); + content.appendChild(sourceFolder); + + Gson gson = new Gson(); + + Version version = gson.fromJson(new FileReader(Constants.MINECRAFT_JSON.get(extension)), Version.class); + + for (Version.Library library : version.libraries) { + if (library.allowed() && library.getFile() != null && library.getFile().exists()) { + Element node = doc.createElement("orderEntry"); + node.setAttribute("type", "module-library"); + Element libraryElement = doc.createElement("library"); + Element classes = doc.createElement("CLASSES"); + Element javadoc = doc.createElement("JAVADOC"); + Element sources = doc.createElement("SOURCES"); + Element root = doc.createElement("root"); + root.setAttribute("url", "jar://" + library.getFile().getAbsolutePath() + "!/"); + classes.appendChild(root); + libraryElement.appendChild(classes); + libraryElement.appendChild(javadoc); + libraryElement.appendChild(sources); + node.appendChild(libraryElement); + component.appendChild(node); + } else if (!library.allowed()) { + this.getLogger().info(":" + library.getFile().getName() + " is not allowed on this os"); + } + } + + Element node = doc.createElement("orderEntry"); + node.setAttribute("type", "module-library"); + Element libraryElement = doc.createElement("library"); + Element classes = doc.createElement("CLASSES"); + Element javadoc = doc.createElement("JAVADOC"); + Element sources = doc.createElement("SOURCES"); + Element root = doc.createElement("root"); + root.setAttribute("url", "jar://" + Constants.MINECRAFT_CLIENT_MAPPED_JAR.get(extension).getAbsolutePath() + "!/"); + classes.appendChild(root); + libraryElement.appendChild(classes); + libraryElement.appendChild(javadoc); + libraryElement.appendChild(sources); + node.appendChild(libraryElement); + component.appendChild(node); + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(file); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(source, result); + + file = new File(getProject().getName() + ".iws"); + docFactory = DocumentBuilderFactory.newInstance(); + docBuilder = docFactory.newDocumentBuilder(); + doc = docBuilder.parse(file); + + NodeList list = doc.getElementsByTagName("component"); + Element runManager = null; + for (int i = 0; i < list.getLength(); i++) { + Element element = (Element) list.item(i); + if (element.getAttribute("name").equals("RunManager")) { + runManager = element; + break; + } + } + + if (runManager == null) { + this.getLogger().lifecycle(":failed to generate intellij run configurations"); + return; + } + + IdeaRunConfig ideaClient = new IdeaRunConfig(); + ideaClient.mainClass = "chorusmc.base.ClientStart"; + ideaClient.projectName = getProject().getName(); + ideaClient.configName = "Minecraft Client"; + ideaClient.runDir = "file://$PROJECT_DIR$/" + extension.runDir; + ideaClient.arguments = "--assetIndex " + version.assetIndex.id + " --assetsDir " + new File(Constants.CACHE_FILES, "assets").getAbsolutePath(); + + runManager.appendChild(ideaClient.genRuns(runManager)); + + transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + source = new DOMSource(doc); + result = new StreamResult(file); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(source, result); + + File runDir = new File(Constants.WORKING_DIRECTORY, extension.runDir); + if (!runDir.exists()) { + runDir.mkdirs(); + } + } +} diff --git a/src/main/java/chorusmc/gradle/task/MapJarsTask.java b/src/main/java/chorusmc/gradle/task/MapJarsTask.java new file mode 100644 index 0000000..4fea7a5 --- /dev/null +++ b/src/main/java/chorusmc/gradle/task/MapJarsTask.java @@ -0,0 +1,68 @@ +package chorusmc.gradle.task; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.mapping.MappingsEnigmaReader; +import cuchaz.enigma.throwables.MappingParseException; +import groovy.lang.Closure; +import org.apache.commons.io.FileUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.TaskAction; +import org.gradle.process.ExecResult; +import org.gradle.process.JavaExecSpec; +import chorusmc.gradle.ChorusGradleExtension; +import chorusmc.gradle.util.Constants; +import org.zeroturnaround.zip.ZipUtil; + +import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; + +public class MapJarsTask extends DefaultTask { + + Deobfuscator deobfuscator; + + @TaskAction + public void mapJars() throws IOException, MappingParseException { + ChorusGradleExtension extension = this.getProject().getExtensions().getByType(ChorusGradleExtension.class); + if(Constants.MINECRAFT_CLIENT_MAPPED_JAR.get(extension).exists()){ + Constants.MINECRAFT_CLIENT_MAPPED_JAR.get(extension).delete(); + } + this.getLogger().lifecycle(":unpacking mappings"); + if(Constants.MAPPINGS_DIR.exists()){ + FileUtils.deleteDirectory(Constants.MAPPINGS_DIR); + } + ZipUtil.unpack(Constants.MAPPINGS_ZIP, Constants.MAPPINGS_DIR); + + this.getLogger().lifecycle(":remapping jar"); + deobfuscator = new Deobfuscator(new JarFile(Constants.MINECRAFT_CLIENT_JAR.get(extension))); + this.deobfuscator.setMappings(new MappingsEnigmaReader().read(new File(Constants.MAPPINGS_DIR, "pomf-master" + File.separator + "mappings"))); + this.deobfuscator.writeJar(Constants.MINECRAFT_CLIENT_MAPPED_JAR.get(extension), new ProgressListener()); + + File tempAssests = new File(Constants.CACHE_FILES, "tempAssets"); + + ZipUtil.unpack(Constants.MINECRAFT_CLIENT_JAR.get(extension), tempAssests, name -> { + if (name.startsWith("assets") || name.startsWith("log4j2.xml")) { + return name; + } else { + return null; + } + }); + ZipUtil.unpack(Constants.MINECRAFT_CLIENT_MAPPED_JAR.get(extension), tempAssests); + + ZipUtil.pack(tempAssests, Constants.MINECRAFT_CLIENT_MAPPED_JAR.get(extension)); + } + + public static class ProgressListener implements Deobfuscator.ProgressListener { + @Override + public void init(int i, String s) { + + } + + @Override + public void onProgress(int i, String s) { + + } + } + + +} diff --git a/src/main/java/chorusmc/gradle/util/Checksum.java b/src/main/java/chorusmc/gradle/util/Checksum.java new file mode 100644 index 0000000..a725168 --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/Checksum.java @@ -0,0 +1,27 @@ +package chorusmc.gradle.util; + +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; +import com.google.common.io.Files; + +import java.io.File; +import java.io.IOException; + +public class Checksum { + public static boolean equals(File file, String checksum) { + if (file == null) { + return false; + } + try { + HashCode hash = Files.hash(file, Hashing.sha1()); + StringBuilder builder = new StringBuilder(); + for (Byte hashBytes : hash.asBytes()) { + builder.append(Integer.toString((hashBytes & 0xFF) + 0x100, 16).substring(1)); + } + return builder.toString().equals(checksum); + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/src/main/java/chorusmc/gradle/util/Constants.java b/src/main/java/chorusmc/gradle/util/Constants.java new file mode 100644 index 0000000..a9e10c1 --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/Constants.java @@ -0,0 +1,49 @@ +package chorusmc.gradle.util; + +import chorusmc.gradle.util.delayed.IDelayed; +import chorusmc.gradle.util.delayed.DelayedFile; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + +public class Constants { + public static final File WORKING_DIRECTORY = new File("."); + public static final File CACHE_FILES = new File(WORKING_DIRECTORY, ".gradle/minecraft"); + + public static final IDelayed MINECRAFT_CLIENT_JAR = new DelayedFile(extension -> new File(CACHE_FILES, extension.version + "-client.jar")); + public static final IDelayed MINECRAFT_CLIENT_MAPPED_JAR = new DelayedFile(extension -> new File(CACHE_FILES, extension.version + "-client-mapped.jar")); + + public static final File MAPPINGS_ZIP = new File(CACHE_FILES, "mappings.zip"); + public static final File MAPPINGS_DIR = new File(CACHE_FILES, "mappings"); + + public static final File MINECRAFT_LIBS = new File(CACHE_FILES, "libs"); + public static final File MINECRAFT_NATIVES = new File(CACHE_FILES, "natives"); + public static final IDelayed MINECRAFT_JSON = new DelayedFile(extension -> new File(CACHE_FILES, extension.version + "-info.json")); + + public static final File MINECRAFT_ROOT = new File(WORKING_DIRECTORY, "minecraft"); + public static final IDelayed MAPPING_SRG = new DelayedFile(extension -> new File(WORKING_DIRECTORY, "mappings.srg")); + + public static final File VERSION_MANIFEST = new File(CACHE_FILES, "version_manifest.json"); + + public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/"; + public static final String RESOURCES_BASE = "http://resources.download.minecraft.net/"; + + public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"; + + public static final String CONFIG_NATIVES = "MC_NATIVES"; + public static final String CONFIG_MC_DEPENDENCIES = "MC_DEPENDENCIES"; + public static final String CONFIG_MC_DEPENDENCIES_CLIENT = "MC_DEPENDENCIES_CLIENT"; + public static final String SYSTEM_ARCH = System.getProperty("os.arch").equals("64") ? "64" : "32"; + + public static List getClassPath() { + URL[] urls = ((URLClassLoader) Constants.class.getClassLoader()).getURLs(); + ArrayList list = new ArrayList<>(); + for (URL url : urls) { + list.add(url.getPath()); + } + return list; + } +} diff --git a/src/main/java/chorusmc/gradle/util/IdeaRunConfig.java b/src/main/java/chorusmc/gradle/util/IdeaRunConfig.java new file mode 100644 index 0000000..808aa49 --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/IdeaRunConfig.java @@ -0,0 +1,48 @@ +package chorusmc.gradle.util; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.io.IOException; +import java.util.Map; + +public class IdeaRunConfig { + public String configName; + public String projectName; + public String mainClass; + public String runDir; + public String arguments; + + public Element genRuns(Element doc) throws IOException, ParserConfigurationException, TransformerException { + Element root = this.addXml(doc, "component", ImmutableMap.of("name", "ProjectRunConfigurationManager")); + root = addXml(root, "configuration", ImmutableMap.of("default", "false", "name", configName, "type", "Application", "factoryName", "Application")); + + this.addXml(root, "module", ImmutableMap.of("name", projectName)); + this.addXml(root, "option", ImmutableMap.of("name", "MAIN_CLASS_NAME", "value", mainClass)); + this.addXml(root, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", runDir)); + + if (!Strings.isNullOrEmpty(arguments)) { + this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", arguments)); + } + return root; + } + + public Element addXml(Node parent, String name, Map values) { + Document doc = parent.getOwnerDocument(); + if (doc == null) { + doc = (Document) parent; + } + + Element e = doc.createElement(name); + for (Map.Entry entry : values.entrySet()) { + e.setAttribute(entry.getKey(), entry.getValue()); + } + parent.appendChild(e); + return e; + } +} \ No newline at end of file diff --git a/src/main/java/chorusmc/gradle/util/ManifestVersion.java b/src/main/java/chorusmc/gradle/util/ManifestVersion.java new file mode 100644 index 0000000..8dc94cf --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/ManifestVersion.java @@ -0,0 +1,12 @@ +package chorusmc.gradle.util; + +import java.util.ArrayList; +import java.util.List; + +public class ManifestVersion { + public List versions = new ArrayList<>(); + + public static class Versions { + public String id, url; + } +} diff --git a/src/main/java/chorusmc/gradle/util/OperatingSystem.java b/src/main/java/chorusmc/gradle/util/OperatingSystem.java new file mode 100644 index 0000000..118dd8a --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/OperatingSystem.java @@ -0,0 +1,26 @@ +package chorusmc.gradle.util; + +public class OperatingSystem { + public static String getOS() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) { + return "windows"; + } else if (osName.contains("mac")) { + return "osx"; + } else { + return "linux"; + } + } + + public static String getArch() { + if (is64Bit()) { + return "64"; + } else { + return "32"; + } + } + + public static boolean is64Bit() { + return System.getProperty("sun.arch.data.model").contains("64"); + } +} diff --git a/src/main/java/chorusmc/gradle/util/Version.java b/src/main/java/chorusmc/gradle/util/Version.java new file mode 100644 index 0000000..647f8f6 --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/Version.java @@ -0,0 +1,135 @@ +package chorusmc.gradle.util; + +import com.google.gson.JsonObject; + +import java.io.File; +import java.util.List; +import java.util.Map; + +public class Version { + public List libraries; + public Map downloads; + public AssetIndex assetIndex; + + public class Downloads { + public String url; + public String sha1; + } + + public class AssetIndex { + public String id; + public String sha1; + public String url; + } + + public class Library { + public String name; + public JsonObject natives; + public JsonObject downloads; + private Artifact artifact; + public Rule[] rules; + + public String getURL() { + String path; + String[] parts = this.name.split(":", 3); + path = parts[0].replace(".", "/") + "/" + parts[1] + "/" + parts[2] + "/" + parts[1] + "-" + parts[2] + getClassifier() + ".jar"; + return Constants.LIBRARIES_BASE + path; + } + + public File getFile() { + String[] parts = this.name.split(":", 3); + return new File(Constants.MINECRAFT_LIBS, parts[0].replace(".", File.separator) + File.separator + parts[1] + File.separator + parts[2] + File.separator + parts[1] + "-" + parts[2] + getClassifier() + ".jar"); + } + + public String getSha1() { + if (this.downloads == null) { + return ""; + } else if (this.downloads.getAsJsonObject("artifact") == null) { + return ""; + } else if (this.downloads.getAsJsonObject("artifact").get("sha1") == null) { + return ""; + } else { + return this.downloads.getAsJsonObject("artifact").get("sha1").getAsString(); + } + } + + public String getClassifier() { + if (natives == null) { + return ""; + } else { + return "-" + natives.get(OperatingSystem.getOS().replace("${arch}", OperatingSystem.getArch())).getAsString().replace("\"", ""); + } + } + + public boolean allowed() { + if (this.rules == null || this.rules.length <= 0) { + return true; + } + + boolean success = false; + for (Rule rule : this.rules) { + if (rule.os != null && rule.os.name != null) { + if (rule.os.name.equalsIgnoreCase(OperatingSystem.getOS())) { + return rule.action.equalsIgnoreCase("allow"); + } + } else { + success = rule.action.equalsIgnoreCase("allow"); + } + } + return success; + } + + public String getArtifactName() { + if (artifact == null) { + artifact = new Artifact(name); + } + return artifact.getArtifact(natives == null ? artifact.getClassifier() : natives.get(OperatingSystem.getOS()).getAsString()); + } + + private class Artifact { + private final String domain, name, version, classifier, ext; + + public Artifact(String name) { + String[] splitedArtifact = name.split(":"); + int idx = splitedArtifact[splitedArtifact.length - 1].indexOf('@'); + if (idx != -1) { + ext = splitedArtifact[splitedArtifact.length - 1].substring(idx + 1); + splitedArtifact[splitedArtifact.length - 1] = splitedArtifact[splitedArtifact.length - 1].substring(0, idx); + } else { + ext = "jar"; + } + this.domain = splitedArtifact[0]; + this.name = splitedArtifact[1]; + this.version = splitedArtifact[2]; + this.classifier = splitedArtifact.length > 3 ? splitedArtifact[3] : null; + } + + public String getArtifact(String classifier) { + String ret = domain + ":" + name + ":" + version; + if (classifier != null && classifier.indexOf('$') > -1) { + classifier = classifier.replace("${arch}", Constants.SYSTEM_ARCH); + } + if (classifier != null) { + ret += ":" + classifier; + } + if (!"jar".equals(ext)) { + ret += "@" + ext; + } + return ret; + } + + public String getClassifier() { + return classifier; + } + } + } + + private class Rule { + public String action; + public OS os; + + private class OS { + String name; + } + } +} diff --git a/src/main/java/chorusmc/gradle/util/assets/AssetIndex.java b/src/main/java/chorusmc/gradle/util/assets/AssetIndex.java new file mode 100644 index 0000000..fe4bec4 --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/assets/AssetIndex.java @@ -0,0 +1,27 @@ +package chorusmc.gradle.util.assets; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public class AssetIndex { + private final Map objects; + private boolean virtual; + + public AssetIndex() { + this.objects = new LinkedHashMap<>(); + } + + public Map getFileMap() { + return this.objects; + } + + public Set getUniqueObjects() { + return new HashSet<>(this.objects.values()); + } + + public boolean isVirtual() { + return this.virtual; + } +} diff --git a/src/main/java/chorusmc/gradle/util/assets/AssetObject.java b/src/main/java/chorusmc/gradle/util/assets/AssetObject.java new file mode 100644 index 0000000..26a9def --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/assets/AssetObject.java @@ -0,0 +1,33 @@ +package chorusmc.gradle.util.assets; + +public class AssetObject { + private String hash; + private long size; + + public String getHash() { + return this.hash; + } + + public long getSize() { + return this.size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if ((o == null) || (getClass() != o.getClass())) { + return false; + } else { + AssetObject that = (AssetObject) o; + return this.size == that.size && this.hash.equals(that.hash); + } + } + + @Override + public int hashCode() { + int result = this.hash.hashCode(); + result = 31 * result + (int) (this.size ^ this.size >>> 32); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/chorusmc/gradle/util/delayed/DelayedFile.java b/src/main/java/chorusmc/gradle/util/delayed/DelayedFile.java new file mode 100644 index 0000000..633937c --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/delayed/DelayedFile.java @@ -0,0 +1,23 @@ +package chorusmc.gradle.util.delayed; + +import chorusmc.gradle.ChorusGradleExtension; + +import java.io.File; +import java.util.function.Function; + +public class DelayedFile implements IDelayed { + private File file; + private Function function; + + public DelayedFile(Function function) { + this.function = function; + } + + @Override + public File get(ChorusGradleExtension extension) { + if (this.file == null) { + this.file = this.function.apply(extension); + } + return this.file; + } +} diff --git a/src/main/java/chorusmc/gradle/util/delayed/IDelayed.java b/src/main/java/chorusmc/gradle/util/delayed/IDelayed.java new file mode 100644 index 0000000..5faa9d1 --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/delayed/IDelayed.java @@ -0,0 +1,7 @@ +package chorusmc.gradle.util.delayed; + +import chorusmc.gradle.ChorusGradleExtension; + +public interface IDelayed { + T get(ChorusGradleExtension extension); +} diff --git a/src/main/java/chorusmc/gradle/util/progress/ProgressLogger.java b/src/main/java/chorusmc/gradle/util/progress/ProgressLogger.java new file mode 100644 index 0000000..671c09f --- /dev/null +++ b/src/main/java/chorusmc/gradle/util/progress/ProgressLogger.java @@ -0,0 +1,206 @@ +package chorusmc.gradle.util.progress; + +import org.gradle.api.Project; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Wrapper to ProgressLogger internal API + */ +public class ProgressLogger { + private final Object logger; + private final Method getDescription, setDescription, getShortDescription, setShortDescription, getLoggingHeader, setLoggingHeader, start, started, startedArg, progress, completed, completedArg; + + private ProgressLogger(Object logger) { + this.logger = logger; + this.getDescription = getMethod("getDescription"); + this.setDescription = getMethod("setDescription", String.class); + this.getShortDescription = getMethod("getShortDescription"); + this.setShortDescription = getMethod("setShortDescription", String.class); + this.getLoggingHeader = getMethod("getLoggingHeader"); + this.setLoggingHeader = getMethod("setLoggingHeader", String.class); + this.start = getMethod("start", String.class, String.class); + this.started = getMethod("started"); + this.startedArg = getMethod("started", String.class); + this.progress = getMethod("progress", String.class); + this.completed = getMethod("completed"); + this.completedArg = getMethod("completed", String.class); + } + + private static Class getFactoryClass() { + Class progressLoggerFactoryClass = null; + try { + //Gradle 2.14 and higher + progressLoggerFactoryClass = Class.forName("org.gradle.internal.logging.progress.ProgressLoggerFactory"); + } catch (ClassNotFoundException e) { + //prior to Gradle 2.14 + try { + progressLoggerFactoryClass = Class.forName("org.gradle.logging.ProgressLoggerFactory"); + } catch (ClassNotFoundException e1) { + // Unsupported Gradle version + } + } + return progressLoggerFactoryClass; + } + + private Method getMethod(String methodName, Class... args) { + if (logger != null) { + try { + return logger.getClass().getMethod(methodName, args); + } catch (NoSuchMethodException ignored) { + + } + } + return null; + } + + private Object invoke(Method method, Object... args) { + if (logger != null) { + try { + method.setAccessible(true); + return method.invoke(logger, args); + } catch (IllegalAccessException | InvocationTargetException ignored) { + + } + } + return null; + } + + /** + * Get a Progress logger from the Gradle internal API + * + * @param project The project + * @param category The logger category + * @return In any case a progress logger + */ + public static ProgressLogger getProgressFactory(Project project, String category) { + try { + Method getServices = project.getClass().getMethod("getServices"); + Object serviceFactory = getServices.invoke(project); + Method get = serviceFactory.getClass().getMethod("get", Class.class); + Object progressLoggerFactory = get.invoke(serviceFactory, getFactoryClass()); + Method newOperation = progressLoggerFactory.getClass().getMethod("newOperation", String.class); + return new ProgressLogger(newOperation.invoke(progressLoggerFactory, category)); + } catch (Exception e) { + project.getLogger().error("Unable to get progress logger. Download progress will not be displayed."); + return new ProgressLogger(null); + } + } + + /** + * Returns the description of the operation. + * + * @return the description, must not be empty. + */ + public String getDescription() { + return (String) invoke(getDescription); + } + + /** + *

Sets the description of the operation. This should be a full, stand-alone description of the operation. + *

+ *

This must be called before {@link #started()}. + * + * @param description The description. + */ + public ProgressLogger setDescription(String description) { + invoke(setDescription, description); + return this; + } + + /** + * Returns the short description of the operation. This is used in place of the full description when display space is limited. + * + * @return The short description, must not be empty. + */ + public String getShortDescription() { + return (String) invoke(getShortDescription); + } + + /** + *

Sets the short description of the operation. This is used in place of the full description when display space is limited. + *

+ *

This must be called before {@link #started()} + * + * @param description The short description. + */ + public ProgressLogger setShortDescription(String description) { + invoke(setShortDescription, description); + return this; + } + + /** + *

Returns the logging header for the operation. This is logged before any other log messages for this operation are logged. It is usually + * also logged at the end of the operation, along with the final status message. Defaults to null. + *

+ *

If not specified, no logging header is logged. + * + * @return The logging header, possibly empty. + */ + public String getLoggingHeader() { + return (String) invoke(getLoggingHeader); + } + + /** + *

Sets the logging header for the operation. This is logged before any other log messages for this operation are logged. It is usually + * also logged at the end of the operation, along with the final status message. Defaults to null. + * + * @param header The header. May be empty or null. + */ + public ProgressLogger setLoggingHeader(String header) { + invoke(setLoggingHeader, header); + return this; + } + + /** + * Convenience method that sets descriptions and logs started() event. + * + * @return this logger instance + */ + public ProgressLogger start(String description, String shortDescription) { + invoke(start, description, shortDescription); + return this; + } + + /** + * Logs the start of the operation, with no initial status. + */ + public void started() { + invoke(started); + } + + /** + * Logs the start of the operation, with the given status. + * + * @param status The initial status message. Can be null or empty. + */ + public void started(String status) { + invoke(started, status); + } + + /** + * Logs some progress, indicated by a new status. + * + * @param status The new status message. Can be null or empty. + */ + public void progress(String status) { + invoke(progress, status); + } + + /** + * Logs the completion of the operation, with no final status + */ + public void completed() { + invoke(completed); + } + + /** + * Logs the completion of the operation, with a final status. This is generally logged along with the description. + * + * @param status The final status message. Can be null or empty. + */ + public void completed(String status) { + invoke(completed, status); + } +} diff --git a/src/main/resources/META-INF/gradle-plugins/chorus.gradle.properties b/src/main/resources/META-INF/gradle-plugins/chorus.gradle.properties new file mode 100644 index 0000000..a92d33c --- /dev/null +++ b/src/main/resources/META-INF/gradle-plugins/chorus.gradle.properties @@ -0,0 +1 @@ +implementation-class=chorusmc.gradle.ChorusGradlePlugin \ No newline at end of file