Use WeakHashMap in StaticDisplayState; Add extra options for compacting.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / multivm / VMCompactLibraryTaskAction.java
blob79c6e4763d3561e3ac147333b5103815de31a682
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 GNU General Public License v3+, or later.
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 /** Settings to use in the configuration for keeping, etc. */
43 static final String[] _PARSE_SETTINGS = new String[]
45 // ProGuard's method inlining causes code to break! So disable it
46 // otherwise it generates an incorrect StackMapTable... *facepalm*
47 "-optimizations", "!method/inlining/*",
49 // Adjust manifest resources
50 "-adaptresourcefilenames", "**",
51 "-adaptresourcefilecontents",
52 "META-INF/MANIFEST.MF,META-INF/services/**",
54 // Consumers of the libraries/APIs need to see the annotation
55 // information if it is there, to make sure it is retained
56 "-keepattributes", "*Annotation*",
58 // Keep anything with main in it
59 "-keepclasseswithmembers", "class", "*", "{",
60 "public", "static", "void", "main", "(",
61 "java.lang.String[]", ")", ";",
62 "}",
64 // Keep any MIDlet
65 "-keep", "class", "*", "extends",
66 "javax.microedition.midlet.MIDlet",
68 // Keep classes annotation with @Api and
69 "-keep", "public",
70 "@cc.squirreljme.runtime.cldc.annotation.Api",
71 "class", "*", "{",
72 "public", "protected", "*", ";",
73 "}",
74 "-keep", "public",
75 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
76 "class", "*", "{",
77 "public", "protected", "*", ";",
78 "}",
80 // Keep the names of these classes as well
81 "-keepnames", "public",
82 "@cc.squirreljme.runtime.cldc.annotation.Api",
83 "class", "*",
84 "-keepnames", "public",
85 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
86 "class", "*",
88 // Keep members with these two annotations
89 "-keepclassmembers", "public", "class", "*", "{",
90 "@cc.squirreljme.runtime.cldc.annotation.Api",
91 "public", "protected", "*", ";",
92 "}",
93 "-keepclassmembers", "public", "class", "*", "{",
94 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
95 "public", "protected", "*", ";",
96 "}",
98 // Keep all clone methods
99 "-keepclassmembers", "class", "*", "{",
100 "public", "java.lang.Object", "clone", "(", ")", ";",
101 "}",
102 "-keepclassmembernames", "class", "*", "{",
103 "public", "java.lang.Object", "clone", "(", ")", ";",
104 "}",
106 // Keep names as well
107 "-keepclassmembernames", "public", "class", "*", "{",
108 "@cc.squirreljme.runtime.cldc.annotation.Api",
109 "public", "protected", "*", ";",
110 "}",
111 "-keepclassmembernames", "public", "class", "*", "{",
112 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
113 "public", "protected", "*", ";",
114 "}",
116 // Do not trash enumerations as we need those to work properly
117 "-keepclassmembers", "class", "*",
118 "extends", "java.lang.Enum", "{",
119 "<fields>", ";",
120 "public", "static", "**[]", "values",
121 "(", ")", ";",
122 "public", "static", "**", "valueOf",
123 "(", "java.lang.String", ")", ";",
124 "}",
125 "-keepclassmembernames", "class", "*",
126 "extends", "java.lang.Enum", "{",
127 "<fields>", ";",
128 "public", "static", "**[]", "values",
129 "(", ")", ";",
130 "public", "static", "**", "valueOf",
131 "(", "java.lang.String", ")", ";",
132 "}",
135 /** Settings for tests. */
136 static final String[] _TEST_SETTINGS =
138 // This keeps everything about tests but will use pre-existing
139 // mappings and otherwise if we are using obfuscated classes
140 // This is the only thing I have found that works
141 "-keep", "class", "*",
142 "-keepnames", "class", "*",
143 "-keepclassmembernames", "class", "*",
146 /** The source set used. */
147 public final String sourceSet;
150 * Initializes the task action.
152 * @param __sourceSet The source set used.
153 * @throws NullPointerException On null arguments.
154 * @since 2023/02/01
156 public VMCompactLibraryTaskAction(String __sourceSet)
157 throws NullPointerException
159 if (__sourceSet == null)
160 throw new NullPointerException("NARG");
162 this.sourceSet = __sourceSet;
166 * {@inheritDoc}
167 * @since 2023/02/01
169 @Override
170 public void execute(Task __task)
172 VMCompactLibraryTask compactTask = (VMCompactLibraryTask)__task;
174 // Where are we reading/writing to/from?
175 Path inputPath = compactTask.inputBaseJarPath().get();
176 Path outputJarPath = compactTask.outputJarPath().get();
177 Path outputMapPath = compactTask.outputMapPath().get();
179 // Some settings may be configured
180 SquirrelJMEPluginConfiguration projectConfig =
181 SquirrelJMEPluginConfiguration.configuration(__task.getProject());
183 // Run the task
184 Path tempJarFile = null;
185 Path tempInputMapFile = null;
186 Path tempOutputMapFile = null;
189 // Look into the Jar file and check if there are class files, if
190 // there are none then there is nothing to compact
191 boolean atLeastOneClass = false;
192 try (InputStream in = Files.newInputStream(inputPath,
193 StandardOpenOption.READ);
194 ZipInputStream zip = new ZipInputStream(in))
196 for (;;)
198 // Get the next entry
199 ZipEntry entry = zip.getNextEntry();
200 if (entry == null)
201 break;
203 String name = entry.getName();
204 if (name.endsWith(".class"))
205 atLeastOneClass = true;
209 // No classes were found, so do nothing
210 if (!atLeastOneClass)
212 Files.copy(inputPath, outputJarPath,
213 StandardCopyOption.REPLACE_EXISTING);
215 return;
218 // Setup temporary file to output to when finished
219 tempJarFile = Files.createTempFile("out", ".jar");
220 tempInputMapFile = Files.createTempFile("in", ".map");
221 tempOutputMapFile = Files.createTempFile("out", ".map");
223 // Need to delete the created temporary file, otherwise Proguard
224 // will just say "The output appears up to date" and do nothing
225 Files.delete(tempJarFile);
226 Files.delete(tempOutputMapFile);
228 // We need to include all the inputs that were already ran through
229 // ProGuard, so we basically need to look at the dependencies and
230 // map them around accordingly
231 // We also need to combine the mapping files as well
232 ClassPath libraryJars = new ClassPath();
233 boolean applyMapping = false;
234 for (VMCompactLibraryTask compactDep :
235 VMHelpers.compactLibTaskDepends(__task.getProject(),
236 this.sourceSet))
238 Path baseJarFile = compactDep.baseJar.getOutputs().getFiles()
239 .getSingleFile().toPath();
241 // Add the library, but the pre-obfuscated form since we need
242 // to know what it is
243 if (Files.exists(baseJarFile))
244 libraryJars.add(new ClassPathEntry(
245 compactDep.baseJar.getOutputs().getFiles()
246 .getSingleFile(), false));
248 // If the mapping file exists, concatenate it
249 if (Files.exists(compactDep.outputMapPath().get()))
251 // Do use mapping now
252 applyMapping = true;
254 // Add all the information
255 Files.write(tempInputMapFile,
256 Files.readAllLines(compactDep.outputMapPath().get()),
257 StandardOpenOption.APPEND, StandardOpenOption.WRITE);
261 // Base options to use
262 List<String> proGuardOptions = new ArrayList<>();
263 proGuardOptions.addAll(
264 Arrays.asList(VMCompactLibraryTaskAction._PARSE_SETTINGS));
266 // Are we testing?
267 boolean isTesting =
268 SourceSet.TEST_SOURCE_SET_NAME.equals(this.sourceSet) ||
269 VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME.equals(this.sourceSet);
271 // Test settings?
272 if (isTesting)
273 proGuardOptions.addAll(Arrays.asList(
274 VMCompactLibraryTaskAction._TEST_SETTINGS));
276 // Add any additional options as needed
277 if (projectConfig.proGuardOptions != null &&
278 !projectConfig.proGuardOptions.isEmpty())
279 proGuardOptions.addAll(projectConfig.proGuardOptions);
281 // Parse initial configuration with settings
282 Configuration config = new Configuration();
283 try (ConfigurationParser parser = new ConfigurationParser(
284 proGuardOptions.toArray(new String[proGuardOptions.size()]),
285 new Properties()))
287 parser.parse(config);
290 // We are neither of these platforms, we say we are not Java ME
291 // because it will remove StackMapTable and instead use StackMap
292 // which is not what we want
293 config.android = false;
294 config.microEdition = false;
296 // Reduce space and obfuscate, but we cannot remove everything at
297 // this time
298 config.shrink = false;
299 config.optimize = false;
300 config.flattenPackageHierarchy = "$" +
301 (projectConfig.javaDocErrorCode == null ? "??" :
302 projectConfig.javaDocErrorCode);
304 // For mapping files, members do need to be unique
305 config.useUniqueClassMemberNames = true;
307 // Do not use mix case class names, so that more strings can
308 // be compacted together accordingly
309 config.useMixedCaseClassNames = false;
311 // Write mapping to the output file, since we will use it later on
312 config.printMapping = tempOutputMapFile.toFile();
314 // Utilize the combined mapping file that was made so that we can
315 // use everything we have?
316 if (applyMapping)
317 config.applyMapping = tempInputMapFile.toFile();
319 // Be noisy
320 config.verbose = true;
321 //config.dump = Configuration.STD_OUT;
322 //config.printUsage = Configuration.STD_OUT;
323 config.printConfiguration = Configuration.STD_OUT;
325 // Use whatever libraries were found
326 config.libraryJars = libraryJars;
328 // Setup input and output Jar
329 ClassPath programJars = new ClassPath();
330 config.programJars = programJars;
332 // Input source Jar
333 programJars.add(
334 new ClassPathEntry(inputPath.toFile(), false));
336 // Output temporary Jar
337 programJars.add(new ClassPathEntry(
338 tempJarFile.toFile(), true));
340 // Run the shrinking/obfuscation
343 new ProGuard(config).execute();
345 finally
347 Files.move(tempInputMapFile,
348 outputMapPath.resolveSibling(
349 outputMapPath.getFileName() + ".in"),
350 StandardCopyOption.REPLACE_EXISTING);
353 // Insurance
354 if (Files.size(tempJarFile) <= 12)
355 throw new RuntimeException("Nothing happened?");
357 // Move to output
358 Files.move(tempJarFile,
359 outputJarPath,
360 StandardCopyOption.REPLACE_EXISTING);
362 if (Files.exists(tempOutputMapFile))
363 Files.move(tempOutputMapFile,
364 outputMapPath,
365 StandardCopyOption.REPLACE_EXISTING);
367 catch (Exception __e)
369 throw new RuntimeException("Failed to shrink/obfuscate.", __e);
372 // Cleanup anything left over
373 finally
375 if (tempJarFile != null)
378 Files.delete(tempJarFile);
380 catch (IOException ignored)
384 if (tempInputMapFile != null)
387 Files.delete(tempInputMapFile);
389 catch (IOException ignored)
393 if (tempOutputMapFile != null)
396 Files.delete(tempOutputMapFile);
398 catch (IOException ignored)