1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // Multi-Phasic Applications: SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.plugin
.multivm
;
12 import cc
.squirreljme
.plugin
.SquirrelJMEPluginConfiguration
;
13 import java
.io
.IOException
;
14 import java
.io
.InputStream
;
15 import java
.nio
.file
.Files
;
16 import java
.nio
.file
.Path
;
17 import java
.nio
.file
.StandardCopyOption
;
18 import java
.nio
.file
.StandardOpenOption
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Arrays
;
21 import java
.util
.List
;
22 import java
.util
.Properties
;
23 import java
.util
.zip
.ZipEntry
;
24 import java
.util
.zip
.ZipInputStream
;
25 import org
.gradle
.api
.Action
;
26 import org
.gradle
.api
.Task
;
27 import org
.gradle
.api
.tasks
.SourceSet
;
28 import proguard
.ClassPath
;
29 import proguard
.ClassPathEntry
;
30 import proguard
.Configuration
;
31 import proguard
.ConfigurationParser
;
32 import proguard
.ProGuard
;
35 * Performs the actual compaction of the Jar.
39 public class VMCompactLibraryTaskAction
40 implements Action
<Task
>
42 /** The optimizations to use. */
43 static final String
[] _OPTIMIZATIONS
= new String
[]
45 // ProGuard's method inlining causes code to break! So disable
46 // it otherwise it generates an incorrect StackMapTable...
50 // These cause incompatible class change errors if such things
51 // were to be accessed
52 "!class/marking/final",
53 "!field/marking/private",
54 "!method/marking/private",
55 "!method/marking/static",
56 "!method/marking/final",
57 "!method/marking/synchronized",
59 // Do not propagate parameters to method calls nor remove them
60 "!method/propagation/parameter",
61 "!method/removal/parameter",
64 /** Settings to use in the configuration for keeping, etc. */
65 static final String
[] _PARSE_SETTINGS
= new String
[]
67 // Ignore all JetBrains IntelliJ related annotations
68 "-dontwarn", "org.jetbrains.annotations.**",
69 "-dontwarn", "org.intellij.lang.annotations.**",
71 // Adjust manifest resources
72 "-adaptresourcefilenames", "**",
73 "-adaptresourcefilecontents",
74 "META-INF/MANIFEST.MF,META-INF/services/**",
76 // Consumers of the libraries/APIs need to see the annotation
77 // information if it is there, to make sure it is retained
78 "-keepattributes", "RuntimeVisibleAnnotations," +
79 "RuntimeInvisibleAnnotations," +
82 // Keep interfaces, because with them being used elsewhere and
83 // otherwise things can easily break... also keep the methods they
85 "-keep,allowobfuscation", "public", "interface", "*",
86 "-keepclassmembers,allowobfuscation",
87 "public", "interface", "*", "{",
91 // Keep anything with main in it
92 "-keepclasseswithmembers", "class", "*", "{",
93 "public", "static", "void", "main", "(",
94 "java.lang.String[]", ")", ";",
98 "-keep", "class", "*", "extends",
99 "javax.microedition.midlet.MIDlet",
101 // Keep classes annotation with @Api and
103 "@cc.squirreljme.runtime.cldc.annotation.Api",
105 "public", "protected", "*", ";",
108 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
110 "public", "protected", "*", ";",
113 // Keep the names of these classes as well
114 "-keepnames", "public",
115 "@cc.squirreljme.runtime.cldc.annotation.Api",
117 "-keepnames", "public",
118 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
121 // Keep members with these two annotations
122 "-keepclassmembers", "public", "class", "*", "{",
123 "@cc.squirreljme.runtime.cldc.annotation.Api",
124 "public", "protected", "*", ";",
126 "-keepclassmembers", "public", "class", "*", "{",
127 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
128 "public", "protected", "*", ";",
131 // Keep all clone methods
132 "-keepclassmembers", "class", "*", "{",
133 "public", "java.lang.Object", "clone", "(", ")", ";",
135 "-keepclassmembernames", "class", "*", "{",
136 "public", "java.lang.Object", "clone", "(", ")", ";",
139 // Keep names as well
140 "-keepclassmembernames", "public", "class", "*", "{",
141 "@cc.squirreljme.runtime.cldc.annotation.Api",
142 "public", "protected", "*", ";",
144 "-keepclassmembernames", "public", "class", "*", "{",
145 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
146 "public", "protected", "*", ";",
149 // Do not trash enumerations as we need those to work properly
150 "-keepclassmembers", "class", "*",
151 "extends", "java.lang.Enum", "{",
153 "public", "static", "**[]", "values",
155 "public", "static", "**", "valueOf",
156 "(", "java.lang.String", ")", ";",
158 "-keepclassmembernames", "class", "*",
159 "extends", "java.lang.Enum", "{",
161 "public", "static", "**[]", "values",
163 "public", "static", "**", "valueOf",
164 "(", "java.lang.String", ")", ";",
167 // Keep constructors, since they can be called and utilized... if
168 // they are removed then some things actually break and stop
170 "-keepclassmembers", "class", "*", "{",
171 "void", "<clinit>", "(", ")", ";",
172 "<init>", "(", "...", ")", ";",
175 // Assume the debug flags are always false
177 "class", "cc.squirreljme.runtime.cldc.debug.Debugging", "{",
178 "public", "static", "boolean", "ENABLED",
180 "public", "static", "boolean", "VERBOSE",
184 // Remove any code that calls these debugging calls
185 "-assumenosideeffects",
186 "class", "cc.squirreljme.runtime.cldc.debug.Debugging", "{",
187 "void", "debugNote", "(",
188 "java.lang.String", ")", ";",
189 "void", "debugNote", "(",
190 "java.lang.String", ",",
191 "java.lang.Object[]", ")", ";",
192 "void", "notice", "(",
193 "java.lang.String", ")", ";",
194 "void", "notice", "(",
195 "java.lang.String", ",",
196 "java.lang.Object[]", ")", ";",
197 "void", "todoNote", "(",
198 "java.lang.String", ")", ";",
199 "void", "todoNote", "(",
200 "java.lang.String", ",",
201 "java.lang.Object[]", ")", ";",
203 "-assumenoexternalsideeffects",
204 "class", "cc.squirreljme.runtime.cldc.debug.Debugging", "{",
205 "void", "debugNote", "(",
206 "java.lang.String", ")", ";",
207 "void", "debugNote", "(",
208 "java.lang.String", ",",
209 "java.lang.Object[]", ")", ";",
210 "void", "notice", "(",
211 "java.lang.String", ")", ";",
212 "void", "notice", "(",
213 "java.lang.String", ",",
214 "java.lang.Object[]", ")", ";",
215 "void", "todoNote", "(",
216 "java.lang.String", ")", ";",
217 "void", "todoNote", "(",
218 "java.lang.String", ",",
219 "java.lang.Object[]", ")", ";",
222 // Disable some DebugShelf methods
224 "class", "cc.squirreljme.jvm.mle.DebugShelf", "{",
225 "int", "verbose", "(",
226 "int", ")", "=", "0", ";",
227 "int", "verboseInternalThread", "(",
228 "int", ")", "=", "0", ";",
230 "-assumenosideeffects",
231 "class", "cc.squirreljme.jvm.mle.DebugShelf", "{",
232 "int", "verbose", "(",
234 "int", "verboseInternalThread", "(",
236 "void", "verboseStop", "(",
239 "-assumenoexternalsideeffects",
240 "class", "cc.squirreljme.jvm.mle.DebugShelf", "{",
241 "int", "verbose", "(",
243 "int", "verboseInternalThread", "(",
245 "void", "verboseStop", "(",
250 /** Settings for tests. */
251 static final String
[] _TEST_SETTINGS
=
253 // Do not optimize here, we want to keep everything around
256 // This keeps everything about tests but will use pre-existing
257 // mappings and otherwise if we are using obfuscated classes
258 // This is the only thing I have found that works
259 "-keep", "class", "*",
260 "-keepnames", "class", "*",
261 "-keepclassmembers", "class", "*", "{",
265 "-keepclassmembernames", "class", "*", "{",
270 // Keep more debugging attributes, so we can more easily figure
271 // things out when debugging
272 "-keepattributes", "*Annotation*,SourceFile,LineNumberTable," +
273 "LocalVariableTable",
276 /** The source set used. */
277 public final String sourceSet
;
280 * Initializes the task action.
282 * @param __sourceSet The source set used.
283 * @throws NullPointerException On null arguments.
286 public VMCompactLibraryTaskAction(String __sourceSet
)
287 throws NullPointerException
289 if (__sourceSet
== null)
290 throw new NullPointerException("NARG");
292 this.sourceSet
= __sourceSet
;
300 public void execute(Task __task
)
302 VMCompactLibraryTask compactTask
= (VMCompactLibraryTask
)__task
;
304 // Where are we reading/writing to/from?
305 Path inputPath
= compactTask
.inputBaseJarPath().get();
306 Path outputJarPath
= compactTask
.outputJarPath().get();
307 Path outputMapPath
= compactTask
.outputMapPath().get();
309 // Some settings may be configured
310 SquirrelJMEPluginConfiguration projectConfig
=
311 SquirrelJMEPluginConfiguration
.configuration(__task
.getProject());
314 Path tempJarFile
= null;
315 Path tempInputMapFile
= null;
316 Path tempOutputMapFile
= null;
319 // Look into the Jar file and check if there are class files, if
320 // there are none then there is nothing to compact
321 boolean atLeastOneClass
= false;
322 try (InputStream in
= Files
.newInputStream(inputPath
,
323 StandardOpenOption
.READ
);
324 ZipInputStream zip
= new ZipInputStream(in
))
328 // Get the next entry
329 ZipEntry entry
= zip
.getNextEntry();
333 String name
= entry
.getName();
334 if (name
.endsWith(".class"))
335 atLeastOneClass
= true;
339 // No classes were found, so do nothing
340 if (!atLeastOneClass
)
342 Files
.copy(inputPath
, outputJarPath
,
343 StandardCopyOption
.REPLACE_EXISTING
);
348 // Setup temporary file to output to when finished
349 tempJarFile
= Files
.createTempFile("out", ".jar");
350 tempInputMapFile
= Files
.createTempFile("in", ".map");
351 tempOutputMapFile
= Files
.createTempFile("out", ".map");
353 // Need to delete the created temporary file, otherwise Proguard
354 // will just say "The output appears up to date" and do nothing
355 Files
.delete(tempJarFile
);
356 Files
.delete(tempOutputMapFile
);
358 // We need to include all the inputs that were already ran through
359 // ProGuard, so we basically need to look at the dependencies and
360 // map them around accordingly
361 // We also need to combine the mapping files as well
362 ClassPath libraryJars
= new ClassPath();
363 boolean applyMapping
= false;
364 for (VMCompactLibraryTask compactDep
:
365 VMHelpers
.compactLibTaskDepends(__task
.getProject(),
368 Path baseJarFile
= compactDep
.baseJar
.getOutputs().getFiles()
369 .getSingleFile().toPath();
371 // Add the library, but the pre-obfuscated form since we need
372 // to know what it is
373 if (Files
.exists(baseJarFile
))
374 libraryJars
.add(new ClassPathEntry(
375 compactDep
.baseJar
.getOutputs().getFiles()
376 .getSingleFile(), false));
378 // If the mapping file exists, concatenate it
379 if (Files
.exists(compactDep
.outputMapPath().get()))
381 // Do use mapping now
384 // Add all the information
385 Files
.write(tempInputMapFile
,
386 Files
.readAllLines(compactDep
.outputMapPath().get()),
387 StandardOpenOption
.APPEND
, StandardOpenOption
.WRITE
);
391 // Base options to use
392 List
<String
> proGuardOptions
= new ArrayList
<>();
393 proGuardOptions
.addAll(
394 Arrays
.asList(VMCompactLibraryTaskAction
._PARSE_SETTINGS
));
396 // Optimization settings
397 proGuardOptions
.add("-optimizations");
398 StringBuilder optimizationOptions
= new StringBuilder();
399 for (String optimize
: VMCompactLibraryTaskAction
._OPTIMIZATIONS
)
401 if (optimizationOptions
.length() > 0)
402 optimizationOptions
.append(',');
404 optimizationOptions
.append(optimize
);
406 proGuardOptions
.add(optimizationOptions
.toString());
410 SourceSet
.TEST_SOURCE_SET_NAME
.equals(this.sourceSet
) ||
411 VMHelpers
.TEST_FIXTURES_SOURCE_SET_NAME
.equals(this.sourceSet
);
415 proGuardOptions
.addAll(Arrays
.asList(
416 VMCompactLibraryTaskAction
._TEST_SETTINGS
));
418 // Add any additional options as needed
419 List
<String
> projectOptions
=
420 VMCompactLibraryTask
.__optionsBySourceSet(
421 __task
.getProject(), this.sourceSet
).get();
424 if (projectOptions
!= null && !projectOptions
.isEmpty())
425 proGuardOptions
.addAll(projectOptions
);
427 // Parse initial configuration with settings
428 Configuration config
= new Configuration();
429 try (ConfigurationParser parser
= new ConfigurationParser(
430 proGuardOptions
.toArray(new String
[proGuardOptions
.size()]),
433 parser
.parse(config
);
436 // We are neither of these platforms, we say we are not Java ME
437 // because it will remove StackMapTable and instead use StackMap
438 // which is not what we want
439 config
.android
= false;
440 config
.microEdition
= false;
442 // Reduce space and obfuscate, but we cannot remove everything at
444 config
.shrink
= false;
445 config
.optimizationPasses
= 2;
446 /*config.optimize = false;*/
447 config
.flattenPackageHierarchy
= "$" +
448 (projectConfig
.javaDocErrorCode
== null ?
"??" :
449 projectConfig
.javaDocErrorCode
);
451 // For mapping files, members do need to be unique
452 config
.useUniqueClassMemberNames
= true;
454 // Do not use mix case class names, so that more strings can
455 // be compacted together accordingly
456 config
.useMixedCaseClassNames
= false;
458 // Write mapping to the output file, since we will use it later on
459 config
.printMapping
= tempOutputMapFile
.toFile();
461 // Utilize the combined mapping file that was made so that we can
462 // use everything we have?
464 config
.applyMapping
= tempInputMapFile
.toFile();
467 config
.verbose
= true;
468 //config.dump = Configuration.STD_OUT;
469 //config.printUsage = Configuration.STD_OUT;
470 config
.printConfiguration
= Configuration
.STD_OUT
;
472 // Use whatever libraries were found
473 config
.libraryJars
= libraryJars
;
475 // Setup input and output Jar
476 ClassPath programJars
= new ClassPath();
477 config
.programJars
= programJars
;
481 new ClassPathEntry(inputPath
.toFile(), false));
483 // Output temporary Jar
484 programJars
.add(new ClassPathEntry(
485 tempJarFile
.toFile(), true));
487 // Run the shrinking/obfuscation
490 new ProGuard(config
).execute();
494 Files
.move(tempInputMapFile
,
495 outputMapPath
.resolveSibling(
496 outputMapPath
.getFileName() + ".in"),
497 StandardCopyOption
.REPLACE_EXISTING
);
501 if (Files
.size(tempJarFile
) <= 12)
502 throw new RuntimeException("Nothing happened?");
505 Files
.move(tempJarFile
,
507 StandardCopyOption
.REPLACE_EXISTING
);
509 if (Files
.exists(tempOutputMapFile
))
510 Files
.move(tempOutputMapFile
,
512 StandardCopyOption
.REPLACE_EXISTING
);
514 catch (Exception __e
)
516 throw new RuntimeException("Failed to shrink/obfuscate.", __e
);
519 // Cleanup anything left over
522 if (tempJarFile
!= null)
525 Files
.delete(tempJarFile
);
527 catch (IOException ignored
)
531 if (tempInputMapFile
!= null)
534 Files
.delete(tempInputMapFile
);
536 catch (IOException ignored
)
540 if (tempOutputMapFile
!= null)
543 Files
.delete(tempOutputMapFile
);
545 catch (IOException ignored
)