2018-10-12 22:56:09 +00:00
/ *
* 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/>.
* /
2018-10-13 02:21:16 +00:00
package baritone.gradle.task ;
2018-10-12 22:56:09 +00:00
import baritone.gradle.util.Determinizer ;
import org.apache.commons.io.IOUtils ;
2023-06-17 01:59:06 +00:00
import org.gradle.api.Project ;
2018-10-12 22:56:09 +00:00
import org.gradle.api.artifacts.Configuration ;
import org.gradle.api.artifacts.Dependency ;
import org.gradle.api.tasks.Input ;
import org.gradle.api.tasks.TaskAction ;
2020-07-15 07:18:01 +00:00
import org.gradle.api.tasks.TaskCollection ;
import org.gradle.api.tasks.compile.ForkOptions ;
import org.gradle.api.tasks.compile.JavaCompile ;
import org.gradle.internal.jvm.Jvm ;
2018-10-12 22:56:09 +00:00
2023-06-17 01:59:06 +00:00
import java.io.File ;
2018-12-01 18:45:58 +00:00
import java.lang.reflect.Field ;
2023-06-17 01:59:06 +00:00
import java.lang.reflect.Method ;
import java.nio.file.Path ;
import java.nio.file.StandardCopyOption ;
import java.util.Objects ;
import java.io.* ;
2018-10-12 22:56:09 +00:00
import java.net.URL ;
import java.nio.file.Files ;
import java.util.* ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
import java.util.zip.ZipEntry ;
import java.util.zip.ZipFile ;
/ * *
* @author Brady
* @since 10 / 11 / 2018
* /
2018-10-13 02:21:16 +00:00
public class ProguardTask extends BaritoneGradleTask {
2018-10-12 22:56:09 +00:00
private static final Pattern TEMP_LIBRARY_PATTERN = Pattern . compile ( " -libraryjars 'tempLibraries \\ /([a-zA-Z0-9/_ \\ - \\ .]+) \\ .jar' " ) ;
@Input
private String url ;
@Input
private String extract ;
private List < String > requiredLibraries ;
2019-02-05 03:34:44 +00:00
private File mixin ;
2023-06-16 03:59:08 +00:00
private File pathfinder ;
2019-02-05 03:34:44 +00:00
2018-10-12 22:56:09 +00:00
@TaskAction
2018-10-13 19:28:20 +00:00
protected void exec ( ) throws Exception {
2018-10-13 02:21:16 +00:00
super . verifyArtifacts ( ) ;
2018-10-12 22:56:09 +00:00
// "Haha brady why don't you make separate tasks"
downloadProguard ( ) ;
extractProguard ( ) ;
generateConfigs ( ) ;
acquireDependencies ( ) ;
2023-06-16 03:59:08 +00:00
processArtifact ( ) ;
2018-10-12 22:56:09 +00:00
proguardApi ( ) ;
proguardStandalone ( ) ;
cleanup ( ) ;
}
private void processArtifact ( ) throws Exception {
if ( Files . exists ( this . artifactUnoptimizedPath ) ) {
Files . delete ( this . artifactUnoptimizedPath ) ;
}
2023-06-16 03:59:08 +00:00
Determinizer . determinize ( this . artifactPath . toString ( ) , this . artifactUnoptimizedPath . toString ( ) , Arrays . asList ( pathfinder ) , false ) ;
2018-10-12 22:56:09 +00:00
}
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 ( ) ;
}
}
2020-07-26 20:21:51 +00:00
private String getJavaBinPathForProguard ( ) throws Exception {
2020-07-15 07:18:01 +00:00
String path ;
try {
path = findJavaPathByGradleConfig ( ) ;
if ( path ! = null ) return path ;
2023-06-16 03:59:08 +00:00
} catch ( Exception ex ) {
2020-07-15 07:18:01 +00:00
System . err . println ( " Unable to find java by javaCompile options " ) ;
ex . printStackTrace ( ) ;
}
2023-08-16 04:27:23 +00:00
path = findJavaByGradleCurrentRuntime ( ) ;
if ( path ! = null ) return path ;
2020-07-15 07:18:01 +00:00
try {
path = findJavaByJavaHome ( ) ;
if ( path ! = null ) return path ;
2023-06-16 03:59:08 +00:00
} catch ( Exception ex ) {
2020-07-15 07:18:01 +00:00
System . err . println ( " Unable to find java by JAVA_HOME " ) ;
ex . printStackTrace ( ) ;
}
2020-07-15 07:34:19 +00:00
throw new Exception ( " Unable to find java to determine ProGuard libraryjars. Please specify forkOptions.executable in javaCompile, " +
2020-07-15 07:18:01 +00:00
" JAVA_HOME environment variable, or make sure to run Gradle with the correct JDK (a v1.8 only) " ) ;
}
private String findJavaByGradleCurrentRuntime ( ) {
String path = Jvm . current ( ) . getJavaExecutable ( ) . getAbsolutePath ( ) ;
2023-06-17 01:59:06 +00:00
System . out . println ( " Using Gradle's runtime Java for ProGuard " ) ;
return path ;
2020-07-15 07:18:01 +00:00
}
private String findJavaByJavaHome ( ) {
final String javaHomeEnv = System . getenv ( " JAVA_HOME " ) ;
if ( javaHomeEnv ! = null ) {
String path = Jvm . forHome ( new File ( javaHomeEnv ) ) . getJavaExecutable ( ) . getAbsolutePath ( ) ;
2023-06-17 01:59:06 +00:00
System . out . println ( " Detected Java path by JAVA_HOME " ) ;
return path ;
2020-07-15 07:18:01 +00:00
}
return null ;
}
private String findJavaPathByGradleConfig ( ) {
final TaskCollection < JavaCompile > javaCompiles = super . getProject ( ) . getTasks ( ) . withType ( JavaCompile . class ) ;
final JavaCompile compileTask = javaCompiles . iterator ( ) . next ( ) ;
final ForkOptions forkOptions = compileTask . getOptions ( ) . getForkOptions ( ) ;
if ( forkOptions ! = null ) {
String javacPath = forkOptions . getExecutable ( ) ;
if ( javacPath ! = null ) {
File javacFile = new File ( javacPath ) ;
if ( javacFile . exists ( ) ) {
2023-06-17 01:59:06 +00:00
File [ ] maybeJava = javacFile . getParentFile ( ) . listFiles ( ( dir , name ) - > name . equals ( " java " ) ) ;
2020-07-15 07:18:01 +00:00
if ( maybeJava ! = null & & maybeJava . length > 0 ) {
String path = maybeJava [ 0 ] . getAbsolutePath ( ) ;
2023-06-17 01:59:06 +00:00
System . out . println ( " Detected Java path by forkOptions " ) ;
return path ;
2020-07-15 07:18:01 +00:00
}
}
}
}
return null ;
}
2018-10-12 22:56:09 +00:00
private void generateConfigs ( ) throws Exception {
2023-06-17 01:59:06 +00:00
Files . copy ( getRelativeFile ( PROGUARD_CONFIG_TEMPLATE ) , getTemporaryFile ( PROGUARD_CONFIG_DEST ) , StandardCopyOption . REPLACE_EXISTING ) ;
2018-10-12 22:56:09 +00:00
// Setup the template that will be used to derive the API and Standalone configs
List < String > template = Files . readAllLines ( getTemporaryFile ( PROGUARD_CONFIG_DEST ) ) ;
2022-12-12 00:08:49 +00:00
template . add ( 0 , " -injars ' " + this . artifactPath . toString ( ) + " ' " ) ;
template . add ( 1 , " -outjars ' " + this . getTemporaryFile ( PROGUARD_EXPORT_PATH ) + " ' " ) ;
2018-10-12 22:56:09 +00:00
// Acquire the RT jar using "java -verbose". This doesn't work on Java 9+
2020-07-15 07:18:01 +00:00
Process p = new ProcessBuilder ( this . getJavaBinPathForProguard ( ) , " -verbose " ) . start ( ) ;
2018-10-12 22:56:09 +00:00
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 ) ) ;
}
}
} ) ;
}
2023-06-17 01:59:06 +00:00
private static final class Pair < A , B > {
public final A a ;
public final B b ;
private Pair ( final A a , final B b ) {
this . a = a ;
this . b = b ;
}
@Override
public String toString ( ) {
return " Pair{ " +
" a= " + this . a +
" , " +
" b= " + this . b +
'}' ;
}
}
2018-10-12 22:56:09 +00:00
2023-06-17 01:59:06 +00:00
private void acquireDependencies ( ) throws Exception {
2018-10-12 22:56:09 +00:00
// 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 < > ( ) ;
2023-06-17 01:59:06 +00:00
Map < String , File > files = new HashMap < > ( ) ;
getProject ( ) . getConfigurations ( ) . stream ( ) . filter ( Configuration : : isCanBeResolved ) . forEach ( config - > {
for ( File file : config . getFiles ( ) ) {
files . put ( file . getName ( ) , file ) ;
}
config . getAllDependencies ( ) . forEach ( dependency - >
dependencyLookupMap . putIfAbsent ( dependency . getName ( ) + " - " + dependency . getVersion ( ) , new Pair < > ( config , dependency ) ) ) ;
} ) ;
2018-10-12 22:56:09 +00:00
// 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 ) {
2018-12-01 11:31:40 +00:00
// copy from the forgegradle cache
if ( lib . equals ( " minecraft " ) ) {
2018-12-01 18:45:58 +00:00
Path cachedJar = getMinecraftJar ( ) ;
Path inTempDir = getTemporaryFile ( " tempLibraries/minecraft.jar " ) ;
2018-12-01 11:31:40 +00:00
// TODO: maybe try not to copy every time
2023-06-17 01:59:06 +00:00
Files . copy ( cachedJar , inTempDir , StandardCopyOption . REPLACE_EXISTING ) ;
2018-12-01 11:31:40 +00:00
2018-10-12 22:56:09 +00:00
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 ( ) ;
}
}
// Find the library jar file, and copy it to tempLibraries
2023-06-17 01:59:06 +00:00
if ( pair = = null ) {
File libFile = files . get ( lib + " .jar " ) ;
if ( libFile = = null ) {
libFile = files . values ( ) . stream ( ) . filter ( file - > file . getName ( ) . startsWith ( lib ) ) . findFirst ( ) . orElse ( null ) ;
if ( libFile = = null ) {
throw new IllegalStateException ( lib ) ;
2019-02-05 03:34:44 +00:00
}
2023-06-17 01:59:06 +00:00
}
copyTempLib ( lib , libFile ) ;
} else {
for ( File file : pair . a . files ( pair . b ) ) {
if ( file . getName ( ) . startsWith ( lib ) ) {
copyTempLib ( lib , file ) ;
2023-06-16 03:59:08 +00:00
}
2018-10-12 22:56:09 +00:00
}
}
}
2019-02-05 03:34:44 +00:00
if ( mixin = = null ) {
throw new IllegalStateException ( " Unable to find mixin jar " ) ;
}
2023-06-16 03:59:08 +00:00
if ( pathfinder = = null ) {
throw new IllegalStateException ( " Unable to find pathfinder jar " ) ;
}
2018-10-12 22:56:09 +00:00
}
2023-06-17 01:59:06 +00:00
private void copyTempLib ( String lib , File libFile ) throws IOException {
if ( lib . contains ( " mixin " ) ) {
mixin = libFile ;
2018-12-01 11:31:40 +00:00
}
2023-06-17 01:59:06 +00:00
if ( lib . contains ( " nether-pathfinder " ) ) {
pathfinder = libFile ;
2018-12-01 11:31:40 +00:00
}
2023-06-17 01:59:06 +00:00
Files . copy ( libFile . toPath ( ) , getTemporaryFile ( " tempLibraries/ " + lib + " .jar " ) , StandardCopyOption . REPLACE_EXISTING ) ;
}
2018-12-01 11:31:40 +00:00
2023-06-17 01:59:06 +00:00
// a bunch of epic stuff to get the path to the cached jar
private Path getMinecraftJar ( ) throws Exception {
return getObfuscatedMinecraftJar ( getProject ( ) , false ) ; // always notch jar for now.
2018-12-01 11:31:40 +00:00
}
2023-06-17 01:59:06 +00:00
private static Path getObfuscatedMinecraftJar ( final Project project , final boolean srg ) throws Exception {
final Object extension = Objects . requireNonNull ( project . getExtensions ( ) . findByName ( " minecraft " ) , " Unable to find Minecraft extension. " ) ;
2018-12-01 11:31:40 +00:00
2023-06-17 01:59:06 +00:00
final Class < ? > mcpRepoClass = mcpRepoClass ( extension . getClass ( ) . getClassLoader ( ) ) ;
final Field mcpRepoInstanceField = mcpRepoClass . getDeclaredField ( " INSTANCE " ) ;
mcpRepoInstanceField . setAccessible ( true ) ;
final Method findMethod = mcpRepoClass . getDeclaredMethod ( srg ? " findSrg " : " findRaw " , String . class , String . class ) ;
findMethod . setAccessible ( true ) ;
2018-12-01 11:31:40 +00:00
2023-06-17 01:59:06 +00:00
final Object mcpRepo = mcpRepoInstanceField . get ( null ) ;
final String mcpVersion = ( String ) Objects . requireNonNull ( project . getExtensions ( ) . getExtraProperties ( ) . get ( " MCP_VERSION " ) , " Extra property \" MCP_VERSION \" not found " ) ;
return ( ( File ) findMethod . invoke ( mcpRepo , " joined " , mcpVersion ) ) . toPath ( ) ;
2018-12-01 11:31:40 +00:00
}
2023-06-17 01:59:06 +00:00
private static Class < ? > mcpRepoClass ( final ClassLoader loader ) throws Exception {
final Method forName0 = Class . class . getDeclaredMethod ( " forName0 " , String . class , boolean . class , ClassLoader . class , Class . class ) ;
forName0 . setAccessible ( true ) ;
return ( Class < ? > ) forName0 . invoke ( null , " net.minecraftforge.gradle.mcp.MCPRepo " , true , loader , null ) ;
2018-12-01 11:31:40 +00:00
}
2018-10-12 22:56:09 +00:00
private void proguardApi ( ) throws Exception {
runProguard ( getTemporaryFile ( PROGUARD_API_CONFIG ) ) ;
2023-06-16 03:59:08 +00:00
Determinizer . determinize ( this . proguardOut . toString ( ) , this . artifactApiPath . toString ( ) , Arrays . asList ( pathfinder ) , false ) ;
Determinizer . determinize ( this . proguardOut . toString ( ) , this . artifactForgeApiPath . toString ( ) , Arrays . asList ( pathfinder , mixin ) , true ) ;
2018-10-12 22:56:09 +00:00
}
private void proguardStandalone ( ) throws Exception {
runProguard ( getTemporaryFile ( PROGUARD_STANDALONE_CONFIG ) ) ;
2023-06-16 03:59:08 +00:00
Determinizer . determinize ( this . proguardOut . toString ( ) , this . artifactStandalonePath . toString ( ) , Arrays . asList ( pathfinder ) , false ) ;
Determinizer . determinize ( this . proguardOut . toString ( ) , this . artifactForgeStandalonePath . toString ( ) , Arrays . asList ( pathfinder , mixin ) , true ) ;
2018-10-12 22:56:09 +00:00
}
private void cleanup ( ) {
try {
Files . delete ( this . proguardOut ) ;
} catch ( IOException ignored ) { }
}
public void setUrl ( String url ) {
this . url = url ;
}
2023-06-17 01:59:06 +00:00
public String getUrl ( ) {
return url ;
}
2018-10-12 22:56:09 +00:00
public void setExtract ( String extract ) {
this . extract = extract ;
}
2023-06-17 01:59:06 +00:00
public String getExtract ( ) {
return extract ;
}
2018-10-12 22:56:09 +00:00
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 ) ;
}
2022-12-12 00:08:49 +00:00
// Make paths relative to work directory; fixes spaces in path to config, @"" doesn't work
Path workingDirectory = getTemporaryFile ( " " ) ;
Path proguardJar = workingDirectory . relativize ( getTemporaryFile ( PROGUARD_JAR ) ) ;
config = workingDirectory . relativize ( config ) ;
2023-06-16 03:59:08 +00:00
2022-12-12 00:08:49 +00:00
// Honestly, if you still have spaces in your path at this point, you're SOL.
2018-10-12 22:56:09 +00:00
Process p = new ProcessBuilder ( " java " , " -jar " , proguardJar . toString ( ) , " @ " + config . toString ( ) )
2022-12-12 00:08:49 +00:00
. directory ( workingDirectory . toFile ( ) ) // Set the working directory to the temporary folder]
2018-10-12 22:56:09 +00:00
. start ( ) ;
2018-10-13 02:21:16 +00:00
// We can't do output inherit process I/O with gradle for some reason and have it work, so we have to do this
2019-02-04 03:27:28 +00:00
this . printOutputLog ( p . getInputStream ( ) , System . out ) ;
this . printOutputLog ( p . getErrorStream ( ) , System . err ) ;
2018-10-13 02:21:16 +00:00
2018-10-12 22:56:09 +00:00
// Halt the current thread until the process is complete, if the exit code isn't 0, throw an exception
2018-10-13 04:19:11 +00:00
int exitCode = p . waitFor ( ) ;
if ( exitCode ! = 0 ) {
2023-06-16 03:59:08 +00:00
Thread . sleep ( 1000 ) ;
2018-10-27 21:41:25 +00:00
throw new IllegalStateException ( " Proguard exited with code " + exitCode ) ;
2018-10-12 22:56:09 +00:00
}
}
2019-02-04 03:27:28 +00:00
private void printOutputLog ( InputStream stream , PrintStream outerr ) {
2018-10-13 02:21:16 +00:00
new Thread ( ( ) - > {
try ( BufferedReader reader = new BufferedReader ( new InputStreamReader ( stream ) ) ) {
String line ;
while ( ( line = reader . readLine ( ) ) ! = null ) {
2019-02-04 03:27:28 +00:00
outerr . println ( line ) ;
2018-10-13 02:21:16 +00:00
}
2018-12-01 18:45:58 +00:00
} catch ( Exception e ) {
2018-10-13 02:21:16 +00:00
e . printStackTrace ( ) ;
}
} ) . start ( ) ;
2018-10-12 22:56:09 +00:00
}
}