Limit how often GC can be run.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / multivm / VMCompactLibraryTaskAction.java
blob07df870eb15292da83ae6f9a5a26f4794e366638
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;
34 /**
35 * Performs the actual compaction of the Jar.
37 * @since 2023/02/01
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...
47 // *facepaw*
48 "!method/inlining/*",
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," +
80 "AnnotationDefault",
82 // Keep interfaces, because with them being used elsewhere and
83 // otherwise things can easily break... also keep the methods they
84 // contain as well
85 "-keep,allowobfuscation", "public", "interface", "*",
86 "-keepclassmembers,allowobfuscation",
87 "public", "interface", "*", "{",
88 "<methods>", ";",
89 "}",
91 // Keep anything with main in it
92 "-keepclasseswithmembers", "class", "*", "{",
93 "public", "static", "void", "main", "(",
94 "java.lang.String[]", ")", ";",
95 "}",
97 // Keep any MIDlet
98 "-keep", "class", "*", "extends",
99 "javax.microedition.midlet.MIDlet",
101 // Keep classes annotation with @Api and
102 "-keep", "public",
103 "@cc.squirreljme.runtime.cldc.annotation.Api",
104 "class", "*", "{",
105 "public", "protected", "*", ";",
106 "}",
107 "-keep", "public",
108 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
109 "class", "*", "{",
110 "public", "protected", "*", ";",
111 "}",
113 // Keep the names of these classes as well
114 "-keepnames", "public",
115 "@cc.squirreljme.runtime.cldc.annotation.Api",
116 "class", "*",
117 "-keepnames", "public",
118 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
119 "class", "*",
121 // Keep members with these two annotations
122 "-keepclassmembers", "public", "class", "*", "{",
123 "@cc.squirreljme.runtime.cldc.annotation.Api",
124 "public", "protected", "*", ";",
125 "}",
126 "-keepclassmembers", "public", "class", "*", "{",
127 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
128 "public", "protected", "*", ";",
129 "}",
131 // Keep all clone methods
132 "-keepclassmembers", "class", "*", "{",
133 "public", "java.lang.Object", "clone", "(", ")", ";",
134 "}",
135 "-keepclassmembernames", "class", "*", "{",
136 "public", "java.lang.Object", "clone", "(", ")", ";",
137 "}",
139 // Keep names as well
140 "-keepclassmembernames", "public", "class", "*", "{",
141 "@cc.squirreljme.runtime.cldc.annotation.Api",
142 "public", "protected", "*", ";",
143 "}",
144 "-keepclassmembernames", "public", "class", "*", "{",
145 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
146 "public", "protected", "*", ";",
147 "}",
149 // Do not trash enumerations as we need those to work properly
150 "-keepclassmembers", "class", "*",
151 "extends", "java.lang.Enum", "{",
152 "<fields>", ";",
153 "public", "static", "**[]", "values",
154 "(", ")", ";",
155 "public", "static", "**", "valueOf",
156 "(", "java.lang.String", ")", ";",
157 "}",
158 "-keepclassmembernames", "class", "*",
159 "extends", "java.lang.Enum", "{",
160 "<fields>", ";",
161 "public", "static", "**[]", "values",
162 "(", ")", ";",
163 "public", "static", "**", "valueOf",
164 "(", "java.lang.String", ")", ";",
165 "}",
167 // Keep constructors, since they can be called and utilized... if
168 // they are removed then some things actually break and stop
169 // working properly
170 "-keepclassmembers", "class", "*", "{",
171 "void", "<clinit>", "(", ")", ";",
172 "<init>", "(", "...", ")", ";",
173 "}",
175 // Assume the debug flags are always false
176 "-assumevalues",
177 "class", "cc.squirreljme.runtime.cldc.debug.Debugging", "{",
178 "public", "static", "boolean", "ENABLED",
179 "=", "false", ";",
180 "public", "static", "boolean", "VERBOSE",
181 "=", "false", ";",
182 "}",
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[]", ")", ";",
202 "}",
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[]", ")", ";",
220 "}",
222 // Disable some DebugShelf methods
223 "-assumevalues",
224 "class", "cc.squirreljme.jvm.mle.DebugShelf", "{",
225 "int", "verbose", "(",
226 "int", ")", "=", "0", ";",
227 "int", "verboseInternalThread", "(",
228 "int", ")", "=", "0", ";",
229 "}",
230 "-assumenosideeffects",
231 "class", "cc.squirreljme.jvm.mle.DebugShelf", "{",
232 "int", "verbose", "(",
233 "int", ")", ";",
234 "int", "verboseInternalThread", "(",
235 "int", ")", ";",
236 "void", "verboseStop", "(",
237 "int", ")", ";",
238 "}",
239 "-assumenoexternalsideeffects",
240 "class", "cc.squirreljme.jvm.mle.DebugShelf", "{",
241 "int", "verbose", "(",
242 "int", ")", ";",
243 "int", "verboseInternalThread", "(",
244 "int", ")", ";",
245 "void", "verboseStop", "(",
246 "int", ")", ";",
247 "}",
250 /** Settings for tests. */
251 static final String[] _TEST_SETTINGS =
253 // Do not optimize here, we want to keep everything around
254 "-dontoptimize",
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", "*", "{",
262 "<fields>", ";",
263 "<methods>", ";",
264 "}",
265 "-keepclassmembernames", "class", "*", "{",
266 "<fields>", ";",
267 "<methods>", ";",
268 "}",
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.
284 * @since 2023/02/01
286 public VMCompactLibraryTaskAction(String __sourceSet)
287 throws NullPointerException
289 if (__sourceSet == null)
290 throw new NullPointerException("NARG");
292 this.sourceSet = __sourceSet;
296 * {@inheritDoc}
297 * @since 2023/02/01
299 @Override
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());
313 // Run the task
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))
326 for (;;)
328 // Get the next entry
329 ZipEntry entry = zip.getNextEntry();
330 if (entry == null)
331 break;
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);
345 return;
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(),
366 this.sourceSet))
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
382 applyMapping = true;
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());
408 // Are we testing?
409 boolean isTesting =
410 SourceSet.TEST_SOURCE_SET_NAME.equals(this.sourceSet) ||
411 VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME.equals(this.sourceSet);
413 // Test settings?
414 if (isTesting)
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();
423 // Add the options
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()]),
431 new Properties()))
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
443 // this time
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?
463 if (applyMapping)
464 config.applyMapping = tempInputMapFile.toFile();
466 // Be noisy
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;
479 // Input source Jar
480 programJars.add(
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();
492 finally
494 Files.move(tempInputMapFile,
495 outputMapPath.resolveSibling(
496 outputMapPath.getFileName() + ".in"),
497 StandardCopyOption.REPLACE_EXISTING);
500 // Insurance
501 if (Files.size(tempJarFile) <= 12)
502 throw new RuntimeException("Nothing happened?");
504 // Move to output
505 Files.move(tempJarFile,
506 outputJarPath,
507 StandardCopyOption.REPLACE_EXISTING);
509 if (Files.exists(tempOutputMapFile))
510 Files.move(tempOutputMapFile,
511 outputMapPath,
512 StandardCopyOption.REPLACE_EXISTING);
514 catch (Exception __e)
516 throw new RuntimeException("Failed to shrink/obfuscate.", __e);
519 // Cleanup anything left over
520 finally
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)