Workaround for getting tests to work.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / multivm / VMCompactLibraryTaskAction.java
blob4409a05a1b0c7a95701a08b5cf67fa1729efdb2c
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 // Adjust manifest resources
46 "-adaptresourcefilenames", "**",
47 "-adaptresourcefilecontents",
48 "META-INF/MANIFEST.MF,META-INF/services/**",
50 // Consumers of the libraries/APIs need to see the annotation
51 // information if it is there, to make sure it is retained
52 "-keepattributes", "*Annotation*",
54 // Keep anything with main in it
55 "-keepclasseswithmembers", "class", "*", "{",
56 "public", "static", "void", "main", "(",
57 "java.lang.String[]", ")", ";",
58 "}",
60 // Keep any MIDlet
61 "-keep", "class", "*", "extends",
62 "javax.microedition.midlet.MIDlet",
64 // Keep classes annotation with @Api and
65 "-keep", "public",
66 "@cc.squirreljme.runtime.cldc.annotation.Api",
67 "class", "*", "{",
68 "public", "protected", "*", ";",
69 "}",
70 "-keep", "public",
71 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
72 "class", "*", "{",
73 "public", "protected", "*", ";",
74 "}",
76 // Keep the names of these classes as well
77 "-keepnames", "public",
78 "@cc.squirreljme.runtime.cldc.annotation.Api",
79 "class", "*",
80 "-keepnames", "public",
81 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
82 "class", "*",
84 // Keep members with these two annotations
85 "-keepclassmembers", "public", "class", "*", "{",
86 "@cc.squirreljme.runtime.cldc.annotation.Api",
87 "public", "protected", "*", ";",
88 "}",
89 "-keepclassmembers", "public", "class", "*", "{",
90 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
91 "public", "protected", "*", ";",
92 "}",
94 // Keep names as well
95 "-keepclassmembernames", "public", "class", "*", "{",
96 "@cc.squirreljme.runtime.cldc.annotation.Api",
97 "public", "protected", "*", ";",
98 "}",
99 "-keepclassmembernames", "public", "class", "*", "{",
100 "@cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi",
101 "public", "protected", "*", ";",
102 "}",
105 /** Settings for tests. */
106 static final String[] _TEST_SETTINGS =
108 // This keeps everything about tests but will use pre-existing
109 // mappings and otherwise if we are using obfuscated classes
110 // This is the only thing I have found that works
111 "-keep", "class", "*",
112 "-keepnames", "class", "*",
113 "-keepclassmembernames", "class", "*",
116 /** The source set used. */
117 public final String sourceSet;
120 * Initializes the task action.
122 * @param __sourceSet The source set used.
123 * @throws NullPointerException On null arguments.
124 * @since 2023/02/01
126 public VMCompactLibraryTaskAction(String __sourceSet)
127 throws NullPointerException
129 if (__sourceSet == null)
130 throw new NullPointerException("NARG");
132 this.sourceSet = __sourceSet;
136 * {@inheritDoc}
137 * @since 2023/02/01
139 @Override
140 public void execute(Task __task)
142 VMCompactLibraryTask compactTask = (VMCompactLibraryTask)__task;
144 // Where are we reading/writing to/from?
145 Path inputPath = compactTask.inputBaseJarPath().get();
146 Path outputJarPath = compactTask.outputJarPath().get();
147 Path outputMapPath = compactTask.outputMapPath().get();
149 // Some settings may be configured
150 SquirrelJMEPluginConfiguration projectConfig =
151 SquirrelJMEPluginConfiguration.configuration(__task.getProject());
153 // Run the task
154 Path tempJarFile = null;
155 Path tempInputMapFile = null;
156 Path tempOutputMapFile = null;
159 // Look into the Jar file and check if there are class files, if
160 // there are none then there is nothing to compact
161 boolean atLeastOneClass = false;
162 try (InputStream in = Files.newInputStream(inputPath,
163 StandardOpenOption.READ);
164 ZipInputStream zip = new ZipInputStream(in))
166 for (;;)
168 // Get the next entry
169 ZipEntry entry = zip.getNextEntry();
170 if (entry == null)
171 break;
173 String name = entry.getName();
174 if (name.endsWith(".class"))
175 atLeastOneClass = true;
179 // No classes were found, so do nothing
180 if (!atLeastOneClass)
182 Files.copy(inputPath, outputJarPath,
183 StandardCopyOption.REPLACE_EXISTING);
185 return;
188 // Setup temporary file to output to when finished
189 tempJarFile = Files.createTempFile("out", ".jar");
190 tempInputMapFile = Files.createTempFile("in", ".map");
191 tempOutputMapFile = Files.createTempFile("out", ".map");
193 // Need to delete the created temporary file, otherwise Proguard
194 // will just say "The output appears up to date" and do nothing
195 Files.delete(tempJarFile);
196 Files.delete(tempOutputMapFile);
198 // We need to include all the inputs that were already ran through
199 // ProGuard, so we basically need to look at the dependencies and
200 // map them around accordingly
201 // We also need to combine the mapping files as well
202 ClassPath libraryJars = new ClassPath();
203 boolean applyMapping = false;
204 for (VMCompactLibraryTask compactDep :
205 VMHelpers.compactLibTaskDepends(__task.getProject(),
206 this.sourceSet))
208 Path baseJarFile = compactDep.baseJar.getOutputs().getFiles()
209 .getSingleFile().toPath();
211 // Add the library, but the pre-obfuscated form since we need
212 // to know what it is
213 if (Files.exists(baseJarFile))
214 libraryJars.add(new ClassPathEntry(
215 compactDep.baseJar.getOutputs().getFiles()
216 .getSingleFile(), false));
218 // If the mapping file exists, concatenate it
219 if (Files.exists(compactDep.outputMapPath().get()))
221 // Do use mapping now
222 applyMapping = true;
224 // Add all the information
225 Files.write(tempInputMapFile,
226 Files.readAllLines(compactDep.outputMapPath().get()),
227 StandardOpenOption.APPEND, StandardOpenOption.WRITE);
231 // Base options to use
232 List<String> proGuardOptions = new ArrayList<>();
233 proGuardOptions.addAll(
234 Arrays.asList(VMCompactLibraryTaskAction._PARSE_SETTINGS));
236 // Are we testing?
237 boolean isTesting =
238 SourceSet.TEST_SOURCE_SET_NAME.equals(this.sourceSet) ||
239 VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME.equals(this.sourceSet);
241 // Test settings?
242 if (isTesting)
243 proGuardOptions.addAll(Arrays.asList(
244 VMCompactLibraryTaskAction._TEST_SETTINGS));
246 // Add any additional options as needed
247 if (projectConfig.proGuardOptions != null &&
248 !projectConfig.proGuardOptions.isEmpty())
249 proGuardOptions.addAll(projectConfig.proGuardOptions);
251 // Parse initial configuration with settings
252 Configuration config = new Configuration();
253 try (ConfigurationParser parser = new ConfigurationParser(
254 proGuardOptions.toArray(new String[proGuardOptions.size()]),
255 new Properties()))
257 parser.parse(config);
260 // We are neither of these platforms, we say we are not Java ME
261 // because it will remove StackMapTable and instead use StackMap
262 // which is not what we want
263 config.android = false;
264 config.microEdition = false;
266 // Reduce space and obfuscate, but we cannot remove everything at
267 // this time
268 config.shrink = false;
269 config.optimize = false;
270 config.flattenPackageHierarchy = "$" +
271 (projectConfig.javaDocErrorCode == null ? "??" :
272 projectConfig.javaDocErrorCode);
274 // For mapping files, members do need to be unique
275 config.useUniqueClassMemberNames = true;
277 // Do not use mix case class names, so that more strings can
278 // be compacted together accordingly
279 config.useMixedCaseClassNames = false;
281 // Write mapping to the output file, since we will use it later on
282 config.printMapping = tempOutputMapFile.toFile();
284 // Utilize the combined mapping file that was made so that we can
285 // use everything we have?
286 if (applyMapping)
287 config.applyMapping = tempInputMapFile.toFile();
289 // Be noisy
290 config.verbose = true;
291 //config.dump = Configuration.STD_OUT;
292 //config.printUsage = Configuration.STD_OUT;
293 config.printConfiguration = Configuration.STD_OUT;
295 // Use whatever libraries were found
296 config.libraryJars = libraryJars;
298 // Setup input and output Jar
299 ClassPath programJars = new ClassPath();
300 config.programJars = programJars;
302 // Input source Jar
303 programJars.add(
304 new ClassPathEntry(inputPath.toFile(), false));
306 // Output temporary Jar
307 programJars.add(new ClassPathEntry(
308 tempJarFile.toFile(), true));
310 // Run the shrinking/obfuscation
313 new ProGuard(config).execute();
315 finally
317 Files.move(tempInputMapFile,
318 outputMapPath.resolveSibling(
319 outputMapPath.getFileName() + ".in"),
320 StandardCopyOption.REPLACE_EXISTING);
323 // Insurance
324 if (Files.size(tempJarFile) <= 12)
325 throw new RuntimeException("Nothing happened?");
327 // Move to output
328 Files.move(tempJarFile,
329 outputJarPath,
330 StandardCopyOption.REPLACE_EXISTING);
332 if (Files.exists(tempOutputMapFile))
333 Files.move(tempOutputMapFile,
334 outputMapPath,
335 StandardCopyOption.REPLACE_EXISTING);
337 catch (Exception __e)
339 throw new RuntimeException("Failed to shrink/obfuscate.", __e);
342 // Cleanup anything left over
343 finally
345 if (tempJarFile != null)
348 Files.delete(tempJarFile);
350 catch (IOException ignored)
354 if (tempInputMapFile != null)
357 Files.delete(tempInputMapFile);
359 catch (IOException ignored)
363 if (tempOutputMapFile != null)
366 Files.delete(tempOutputMapFile);
368 catch (IOException ignored)