Skip to content

Commit

Permalink
Initial Proguard Task meme
Browse files Browse the repository at this point in the history
Working Determinizer, but it doesn't sort the JSON object keys
time to rek travis
  • Loading branch information
ZeroMemes committed Oct 12, 2018
1 parent 5167c0c commit 8fba36c
Show file tree
Hide file tree
Showing 6 changed files with 441 additions and 8 deletions.
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ buildscript {
}
}

import baritone.gradle.ProguardTask

apply plugin: 'java'
apply plugin: 'net.minecraftforge.gradle.tweaker-client'
apply plugin: 'org.spongepowered.mixin'
Expand Down Expand Up @@ -99,3 +101,11 @@ jar {
preserveFileTimestamps = false
reproducibleFileOrder = true
}

task proguard(type: ProguardTask) {
url 'https://downloads.sourceforge.net/project/proguard/proguard/6.0/proguard6.0.3.zip'
extract 'proguard6.0.3/lib/proguard.jar'
versionManifest 'https://launchermeta.mojang.com/mc/game/version_manifest.json'
}

build.finalizedBy(proguard)
3 changes: 3 additions & 0 deletions buildSrc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build/
.gradle/
out/
25 changes: 25 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/

repositories {
mavenCentral()
}

dependencies {
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
compile group: 'commons-io', name: 'commons-io', version: '2.6'
}
314 changes: 314 additions & 0 deletions buildSrc/src/main/java/baritone/gradle/ProguardTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/

package baritone.gradle;

import baritone.gradle.util.Determinizer;
import com.google.gson.*;
import javafx.util.Pair;
import org.apache.commons.io.IOUtils;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;

import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

/**
* @author Brady
* @since 10/11/2018
*/
public class ProguardTask extends DefaultTask {

private static final JsonParser PARSER = new JsonParser();

private static final Pattern TEMP_LIBRARY_PATTERN = Pattern.compile("-libraryjars 'tempLibraries\\/([a-zA-Z0-9/_\\-\\.]+)\\.jar'");

private static final String
PROGUARD_ZIP = "proguard.zip",
PROGUARD_JAR = "proguard.jar",
PROGUARD_CONFIG_TEMPLATE = "scripts/proguard.pro",
PROGUARD_CONFIG_DEST = "template.pro",
PROGUARD_API_CONFIG = "api.pro",
PROGUARD_STANDALONE_CONFIG = "standalone.pro",
PROGUARD_EXPORT_PATH = "proguard_out.jar",

VERSION_MANIFEST = "version_manifest.json",

TEMP_LIBRARY_DIR = "tempLibraries/",

ARTIFACT_UNOPTIMIZED = "%s-unoptimized-%s.jar",
ARTIFACT_API = "%s-api-%s.jar",
ARTIFACT_STANDALONE = "%s-standalone-%s.jar";

@Input
private String url;

@Input
private String extract;

@Input
private String versionManifest;

private String artifactName, artifactVersion;
private Path artifactPath, artifactUnoptimizedPath, artifactApiPath, artifactStandalonePath, proguardOut;
private Map<String, String> versionDownloadMap;
private List<String> requiredLibraries;

@TaskAction
private void exec() throws Exception {
// "Haha brady why don't you make separate tasks"
verifyArtifacts();
processArtifact();
downloadProguard();
extractProguard();
generateConfigs();
downloadVersionManifest();
acquireDependencies();
proguardApi();
proguardStandalone();
cleanup();
}

private void verifyArtifacts() throws Exception {
this.artifactName = getProject().getName();
this.artifactVersion = getProject().getVersion().toString();

// The compiled baritone artifact that is exported when the build task is ran
String artifactName = String.format("%s-%s.jar", this.artifactName, this.artifactVersion);

this.artifactPath = this.getBuildFile(artifactName);
this.artifactUnoptimizedPath = this.getBuildFile(String.format(ARTIFACT_UNOPTIMIZED, this.artifactName, this.artifactVersion));
this.artifactApiPath = this.getBuildFile(String.format(ARTIFACT_API, this.artifactName, this.artifactVersion));
this.artifactStandalonePath = this.getBuildFile(String.format(ARTIFACT_STANDALONE, this.artifactName, this.artifactVersion));

this.proguardOut = this.getTemporaryFile(PROGUARD_EXPORT_PATH);

if (!Files.exists(this.artifactPath)) {
throw new Exception("Artifact not found! Run build first!");
}
}

private void processArtifact() throws Exception {
if (Files.exists(this.artifactUnoptimizedPath)) {
Files.delete(this.artifactUnoptimizedPath);
}

Determinizer.main(this.artifactPath.toString(), this.artifactUnoptimizedPath.toString());
}

private void downloadProguard() throws Exception {
Path proguardZip = getTemporaryFile(PROGUARD_ZIP);
if (!Files.exists(proguardZip)) {
write(new URL(this.url).openStream(), proguardZip);
}
}

private void extractProguard() throws Exception {
Path proguardJar = getTemporaryFile(PROGUARD_JAR);
if (!Files.exists(proguardJar)) {
ZipFile zipFile = new ZipFile(getTemporaryFile(PROGUARD_ZIP).toFile());
ZipEntry zipJarEntry = zipFile.getEntry(this.extract);
write(zipFile.getInputStream(zipJarEntry), proguardJar);
zipFile.close();
}
}

private void generateConfigs() throws Exception {
Files.copy(getRelativeFile(PROGUARD_CONFIG_TEMPLATE), getTemporaryFile(PROGUARD_CONFIG_DEST), REPLACE_EXISTING);

// Setup the template that will be used to derive the API and Standalone configs
List<String> template = Files.readAllLines(getTemporaryFile(PROGUARD_CONFIG_DEST));
template.removeIf(s -> s.endsWith("# this is the rt jar") || s.startsWith("-injars") || s.startsWith("-outjars"));
template.add(0, "-injars " + this.artifactPath.toString());
template.add(1, "-outjars " + this.getTemporaryFile(PROGUARD_EXPORT_PATH));

// Acquire the RT jar using "java -verbose". This doesn't work on Java 9+
Process p = new ProcessBuilder("java", "-verbose").start();
String out = IOUtils.toString(p.getInputStream(), "UTF-8").split("\n")[0].split("Opened ")[1].replace("]", "");
template.add(2, "-libraryjars '" + out + "'");

// API config doesn't require any changes from the changes that we made to the template
Files.write(getTemporaryFile(PROGUARD_API_CONFIG), template);

// For the Standalone config, don't keep the API package
List<String> standalone = new ArrayList<>(template);
standalone.removeIf(s -> s.contains("# this is the keep api"));
Files.write(getTemporaryFile(PROGUARD_STANDALONE_CONFIG), standalone);

// Discover all of the libraries that we will need to acquire from gradle
this.requiredLibraries = new ArrayList<>();
template.forEach(line -> {
if (!line.startsWith("#")) {
Matcher m = TEMP_LIBRARY_PATTERN.matcher(line);
if (m.find()) {
this.requiredLibraries.add(m.group(1));
}
}
});
}

private void downloadVersionManifest() throws Exception {
Path manifestJson = getTemporaryFile(VERSION_MANIFEST);
write(new URL(this.versionManifest).openStream(), manifestJson);

// Place all the versions in the map with their download URL
this.versionDownloadMap = new HashMap<>();
JsonObject json = readJson(Files.readAllLines(manifestJson)).getAsJsonObject();
JsonArray versions = json.getAsJsonArray("versions");
versions.forEach(element -> {
JsonObject object = element.getAsJsonObject();
this.versionDownloadMap.put(object.get("id").getAsString(), object.get("url").getAsString());
});
}

private void acquireDependencies() throws Exception {

// Create a map of all of the dependencies that we are able to access in this project
// Likely a better way to do this, I just pair the dependency with the first valid configuration
Map<String, Pair<Configuration, Dependency>> dependencyLookupMap = new HashMap<>();
getProject().getConfigurations().stream().filter(Configuration::isCanBeResolved).forEach(config ->
config.getAllDependencies().forEach(dependency ->
dependencyLookupMap.putIfAbsent(dependency.getName() + "-" + dependency.getVersion(), new Pair<>(config, dependency))));

// Create the directory if it doesn't already exist
Path tempLibraries = getTemporaryFile(TEMP_LIBRARY_DIR);
if (!Files.exists(tempLibraries)) {
Files.createDirectory(tempLibraries);
}

// Iterate the required libraries to copy them to tempLibraries
for (String lib : this.requiredLibraries) {
// Download the version jar from the URL acquired from the version manifest
if (lib.startsWith("minecraft")) {
String version = lib.split("-")[1];
Path versionJar = getTemporaryFile("tempLibraries/" + lib + ".jar");
if (!Files.exists(versionJar)) {
write(new URL(this.versionDownloadMap.get(version)).openStream(), versionJar);
}
continue;
}

// Find a configuration/dependency pair that matches the desired library
Pair<Configuration, Dependency> pair = null;
for (Map.Entry<String, Pair<Configuration, Dependency>> entry : dependencyLookupMap.entrySet()) {
if (entry.getKey().startsWith(lib)) {
pair = entry.getValue();
}
}

// The pair must be non-null
Objects.requireNonNull(pair);

// Find the library jar file, and copy it to tempLibraries
for (File file : pair.getKey().files(pair.getValue())) {
if (file.getName().startsWith(lib)) {
Files.copy(file.toPath(), getTemporaryFile("tempLibraries/" + lib + ".jar"), REPLACE_EXISTING);
}
}
}
}

private void proguardApi() throws Exception {
runProguard(getTemporaryFile(PROGUARD_API_CONFIG));
Determinizer.main(this.proguardOut.toString(), this.artifactApiPath.toString());
}

private void proguardStandalone() throws Exception {
runProguard(getTemporaryFile(PROGUARD_STANDALONE_CONFIG));
Determinizer.main(this.proguardOut.toString(), this.artifactStandalonePath.toString());
}

private void cleanup() {
try {
Files.delete(this.proguardOut);
} catch (IOException ignored) {}
}

public void setUrl(String url) {
this.url = url;
}

public void setExtract(String extract) {
this.extract = extract;
}

public void setVersionManifest(String versionManifest) {
this.versionManifest = versionManifest;
}

/*
* A LOT OF SHITTY UTIL METHODS ARE BELOW.
*
* PROCEED WITH CAUTION
*/

private void runProguard(Path config) throws Exception {
// Delete the existing proguard output file. Proguard probably handles this already, but why not do it ourselves
if (Files.exists(this.proguardOut)) {
Files.delete(this.proguardOut);
}

Path proguardJar = getTemporaryFile(PROGUARD_JAR);
Process p = new ProcessBuilder("java", "-jar", proguardJar.toString(), "@" + config.toString())
.directory(getTemporaryFile("").toFile()) // Set the working directory to the temporary folder
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start();

// Halt the current thread until the process is complete, if the exit code isn't 0, throw an exception
int exitCode;
if ((exitCode = p.waitFor()) != 0) {
throw new Exception("Proguard exited with code " + exitCode);
}
}

private void write(InputStream stream, Path file) throws Exception {
if (Files.exists(file)) {
Files.delete(file);
}
Files.copy(stream, file);
}

private Path getRelativeFile(String file) {
return Paths.get(new File(file).getAbsolutePath());
}

private Path getTemporaryFile(String file) {
return Paths.get(new File(getTemporaryDir(), file).getAbsolutePath());
}

private Path getBuildFile(String file) {
return getRelativeFile("build/libs/" + file);
}

private JsonElement readJson(List<String> lines) {
return PARSER.parse(String.join("\n", lines));
}
}
Loading

0 comments on commit 8fba36c

Please sign in to comment.