Cherry pick the banglets and such from wip-l1summercoat, this will be the basis for...
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / tasks / AdditionalManifestPropertiesTaskAction.java
blobff387c9990990099490b742361ea360faa37a28c
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.tasks;
12 import cc.squirreljme.plugin.SquirrelJMEPluginConfiguration;
13 import cc.squirreljme.plugin.multivm.TaskInitialization;
14 import cc.squirreljme.plugin.multivm.VMHelpers;
15 import cc.squirreljme.plugin.swm.JavaMEConfiguration;
16 import cc.squirreljme.plugin.swm.JavaMEMidlet;
17 import cc.squirreljme.plugin.swm.JavaMEMidletType;
18 import cc.squirreljme.plugin.swm.JavaMEProfile;
19 import cc.squirreljme.plugin.swm.JavaMEStandard;
20 import cc.squirreljme.plugin.swm.SuiteDependency;
21 import cc.squirreljme.plugin.swm.SuiteDependencyLevel;
22 import cc.squirreljme.plugin.swm.SuiteDependencyType;
23 import cc.squirreljme.plugin.swm.SuiteName;
24 import cc.squirreljme.plugin.swm.SuiteVendor;
25 import cc.squirreljme.plugin.swm.SuiteVersion;
26 import cc.squirreljme.plugin.swm.SuiteVersionRange;
27 import cc.squirreljme.plugin.util.ProjectAndSourceSet;
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.StandardOpenOption;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.LinkedHashSet;
36 import java.util.NoSuchElementException;
37 import java.util.Set;
38 import java.util.jar.Attributes;
39 import org.gradle.api.Action;
40 import org.gradle.api.Project;
41 import org.gradle.api.Task;
42 import org.gradle.api.artifacts.Configuration;
43 import org.gradle.api.artifacts.Dependency;
44 import org.gradle.api.artifacts.ProjectDependency;
45 import org.gradle.api.tasks.SourceSet;
47 /**
48 * Implements the action for {@link AdditionalManifestPropertiesTask}.
50 * @since 2022/08/07
52 public class AdditionalManifestPropertiesTaskAction
53 implements Action<Task>
55 /** The task output. */
56 protected final Path taskOutput;
58 /** The source set used. */
59 protected final String sourceSet;
61 /**
62 * Initializes the task action.
64 * @param __taskOutput The task output.
65 * @param __sourceSet The source set used.
66 * @since 2022/08/07
68 public AdditionalManifestPropertiesTaskAction(Path __taskOutput,
69 String __sourceSet)
71 this.taskOutput = __taskOutput;
72 this.sourceSet = __sourceSet;
75 /**
76 * {@inheritDoc}
77 * @since 2022/08/07
79 @Override
80 public void execute(Task __task)
82 // Is this the main source set?
83 String sourceSet = this.sourceSet;
84 boolean isTest = sourceSet.equals(SourceSet.TEST_SOURCE_SET_NAME);
86 // Get the project and the config details
87 Project project = __task.getProject();
88 SquirrelJMEPluginConfiguration config =
89 SquirrelJMEPluginConfiguration.configuration(project);
91 // Setup manifest to write into
92 java.util.jar.Manifest javaManifest = new java.util.jar.Manifest();
93 Attributes attributes = javaManifest.getMainAttributes();
95 // Set manifest to 1.0
96 attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
98 // Determine project name for lookup
99 String internalProjectName =
100 VMHelpers.projectInternalNameViaSourceSet(project, sourceSet);
102 // Project name is used internally for dependency lookup
103 attributes.putValue("X-SquirrelJME-InternalProjectName",
104 internalProjectName);
106 // Generation really depends on the application type
107 // The main sources are whatever, but everything else such as
108 // text fixtures is considered a library dependency wise
109 // Tests are always considered to be applications that are run
110 JavaMEMidletType type =
111 config.swmType.normalizeViaSourceSet(sourceSet);
113 // Add common keys
114 attributes.putValue(type.nameKey(),
115 VMHelpers.projectSwmNameViaSourceSet(project, sourceSet));
116 attributes.putValue(type.vendorKey(), config.swmVendor);
117 attributes.putValue(type.versionKey(),
118 new SuiteVersion(project.getVersion().toString()).toString());
120 // Application
121 if (type == JavaMEMidletType.APPLICATION)
123 // Add ability for tests to be launched
124 if (isTest)
126 // SquirrelJME specific indicator that this is for testing
127 attributes.putValue("X-SquirrelJME-Tests", "true");
129 // Main entry point is always the TAC test runner
130 attributes.putValue("Main-Class",
131 "net.multiphasicapps.tac.MainSuiteRunner");
134 // Normal application
135 else
137 // Main class of entry?
138 if (config.mainClass != null)
139 attributes.putValue("Main-Class", config.mainClass);
141 // Add any defined MIDlets
142 int midletId = 1;
143 for (JavaMEMidlet midlet : config.midlets)
144 attributes.putValue("MIDlet-" + (midletId++),
145 midlet.toString());
147 // Ignored in the launcher?
148 if (config.ignoreInLauncher)
149 attributes.putValue("X-SquirrelJME-NoLauncher",
150 "true");
154 // Library
155 else if (type == JavaMEMidletType.LIBRARY)
159 // API
160 else if (type == JavaMEMidletType.API)
162 // Configurations defined?
163 if (!config.definedConfigurations.isEmpty())
164 attributes.putValue(
165 "X-SquirrelJME-DefinedConfigurations",
166 AdditionalManifestPropertiesTaskAction
167 .__delimate(config.definedConfigurations, ' '));
169 // Profiles defined?
170 if (!config.definedProfiles.isEmpty())
171 attributes.putValue(
172 "X-SquirrelJME-DefinedProfiles",
173 AdditionalManifestPropertiesTaskAction
174 .__delimate(config.definedProfiles, ' '));
176 // Standards defined?
177 if (!config.definedStandards.isEmpty())
178 attributes.putValue(
179 "X-SquirrelJME-DefinedStandards",
180 AdditionalManifestPropertiesTaskAction
181 .__delimate(config.definedStandards, ','));
184 // Always depend on the main source set if we are a non-main
185 // source set
186 Set<ProjectAndSourceSet> dependencies = new LinkedHashSet<>();
187 if (!sourceSet.equals(SourceSet.MAIN_SOURCE_SET_NAME))
188 dependencies.add(new ProjectAndSourceSet(project,
189 SourceSet.MAIN_SOURCE_SET_NAME));
191 // Find dependencies based on their inclusion
192 for (String configGroup : Arrays.<String>asList(
193 "api", "implementation"))
195 // What this configuration is called?
196 String configurationName = TaskInitialization.task("",
197 this.sourceSet, configGroup);
199 // The configuration might not even exist
200 Configuration buildConfig = project.getConfigurations()
201 .findByName(configurationName);
202 if (buildConfig == null)
203 continue;
205 // Add all project based dependencies
206 for (Dependency dependency : buildConfig.getDependencies())
207 if (dependency instanceof ProjectDependency)
209 ProjectDependency mod = (ProjectDependency)dependency;
211 dependencies.add(new ProjectAndSourceSet(mod));
215 // Put in standard required dependencies
216 int[] normalDep = new int[]{1};
217 for (ProjectAndSourceSet depend : dependencies)
218 AdditionalManifestPropertiesTaskAction.__addDependency(__task,
219 false, depend, normalDep,
220 attributes, this.sourceSet);
222 // Add any optional dependencies now, which may or may not exist
223 for (Project depend : VMHelpers.optionalDepends(project, sourceSet))
224 AdditionalManifestPropertiesTaskAction.__addDependency(__task,
225 true,
226 new ProjectAndSourceSet(depend,
227 SourceSet.MAIN_SOURCE_SET_NAME), normalDep, attributes,
228 this.sourceSet);
230 // Write the manifest output
231 try (OutputStream out = Files.newOutputStream(this.taskOutput,
232 StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING,
233 StandardOpenOption.WRITE))
235 javaManifest.write(out);
237 // Make sure it is really written!
238 out.flush();
240 catch (IOException e)
242 throw new RuntimeException(e);
247 * Turns an iterable into a string with the given delimiter.
249 * @param __it The iteration to convert.
250 * @param __delim The delimiter.
251 * @return The converted string.
252 * @throws NullPointerException On null arguments.
253 * @since 2020/02/15
255 private static String __delimate(Iterable<?> __it, char __delim)
256 throws NullPointerException
258 if (__it == null)
259 throw new NullPointerException("NARG");
261 // Build output string
262 StringBuilder sb = new StringBuilder();
263 for (Object o : __it)
265 // Add decimeter?
266 if (sb.length() > 0)
267 sb.append(__delim);
269 // Add object
270 sb.append(o);
273 return sb.toString();
277 * Adds the given configuration to the configuration list.
279 * @param __attributes The attributes to put into.
280 * @param __config The configuration to add.
281 * @throws NullPointerException On null arguments.
282 * @since 2022/08/09
284 private static void __addConfiguration(Attributes __attributes,
285 JavaMEConfiguration __config)
286 throws NullPointerException
288 if (__attributes == null || __config == null)
289 throw new NullPointerException("NARG");
291 // Do nothing if the configuration is older or the same as we always
292 // want the best
293 String existing =
294 __attributes.getValue("Microedition-Configuration");
295 if (existing != null &&
296 __config.compareTo(new JavaMEConfiguration(existing)) <= 0)
297 return;
299 // Store it
300 __attributes.putValue("Microedition-Configuration",
301 __config.toString());
305 * Adds a single dependency to a given project.
307 * @param __task The current task?
308 * @param __isOptional Is this an optional dependency?
309 * @param __dependency The dependency that this depends on.
310 * @param __depCounter Dependency counter.
311 * @param __attributes The output attributes.
312 * @param __sourceSourceSet The source task source set.
313 * @throws NullPointerException On null arguments.
314 * @since 2022/02/03
316 static void __addDependency(Task __task, boolean __isOptional,
317 ProjectAndSourceSet __dependency, int[] __depCounter,
318 Attributes __attributes, String __sourceSourceSet)
319 throws NullPointerException
321 if (__task == null || __dependency == null)
322 throw new NullPointerException("NARG");
324 // Generation really depends on the application type
325 Project project = __task.getProject();
326 SquirrelJMEPluginConfiguration config =
327 SquirrelJMEPluginConfiguration.configuration(project);
328 JavaMEMidletType type = config.swmType
329 .normalizeViaSourceSet(__sourceSourceSet);
331 // Which requirement level?
332 SuiteDependencyLevel requireLevel = (__isOptional ?
333 SuiteDependencyLevel.OPTIONAL : SuiteDependencyLevel.REQUIRED);
335 // Get the project config
336 SquirrelJMEPluginConfiguration subConfig =
337 SquirrelJMEPluginConfiguration.configurationOrNull(
338 __dependency.dependency);
339 if (subConfig == null)
341 __task.getLogger().error(String.format(
342 "Project %s:%s wants to add %s as a dependency however " +
343 "it is not a SquirrelJME module, ignoring.",
344 __task.getProject().getPath(),
345 __sourceSourceSet, __dependency.dependency.getPath()));
346 return;
349 // The dependency being constructed (which might be added)
350 SuiteDependency suiteDependency = null;
351 boolean didDepend = false;
352 boolean forceDepend = false;
354 // What is the SWM type of the dependency?
355 JavaMEMidletType subSwmType = subConfig.swmType
356 .normalizeViaSourceSet(__dependency.sourceSet);
358 // Nothing can depend on a MIDlet!
359 if (subSwmType == JavaMEMidletType.APPLICATION)
361 // If this is another project, we cannot just depend on it at all
362 if (project.compareTo(__dependency.dependency) != 0)
363 throw new IllegalArgumentException(String.format(
364 "Project %s:%s cannot depend on application %s:%s.",
365 project.getPath(), __sourceSourceSet,
366 __dependency.dependency.getPath(),
367 __dependency.sourceSet));
369 // Fall and do the default project level dependency...
370 __task.getLogger().warn(String.format(
371 "Project %s:%s is depending on %s:%s via SquirrelJME means.",
372 project.getPath(), __sourceSourceSet,
373 __dependency.dependency.getPath(),
374 __dependency.sourceSet));
375 forceDepend = true;
378 // This is another library
379 else if (subSwmType == JavaMEMidletType.LIBRARY)
381 suiteDependency = new SuiteDependency(
382 SuiteDependencyType.LIBLET,
383 requireLevel,
384 new SuiteName(VMHelpers.projectSwmNameViaSourceSet(
385 __dependency.dependency, __dependency.sourceSet)),
386 new SuiteVendor(subConfig.swmVendor),
387 SuiteVersionRange.exactly(new SuiteVersion(
388 __dependency.dependency.getVersion().toString())));
391 // Is otherwise an API
392 else
394 // Configuration specified?
395 if (subConfig.definedConfigurations != null &&
396 !subConfig.definedConfigurations.isEmpty())
399 if (!config.noEmitConfiguration)
401 AdditionalManifestPropertiesTaskAction
402 .__addConfiguration(__attributes,
403 Collections.max(subConfig.definedConfigurations));
404 didDepend = true;
407 catch (NoSuchElementException e)
409 // Ignore
412 // Profile specified?
413 if (subConfig.definedProfiles != null &&
414 !subConfig.definedProfiles.isEmpty())
417 AdditionalManifestPropertiesTaskAction.__addProfile(
418 __attributes,
419 Collections.max(subConfig.definedProfiles));
420 didDepend = true;
422 catch (NoSuchElementException e)
424 // Ignore
427 // Standard specified? Use that reference... but do not use one
428 // if in the event there was a configuration or profile used
429 if (!didDepend && subConfig.definedStandards != null &&
430 !subConfig.definedStandards.isEmpty())
432 // Use the best standard
433 JavaMEStandard bestStandard = Collections.max(
434 subConfig.definedStandards);
435 suiteDependency = new SuiteDependency(
436 SuiteDependencyType.STANDARD,
437 requireLevel,
438 bestStandard.name(),
439 bestStandard.vendor(),
440 (bestStandard.version() != null ?
441 SuiteVersionRange.exactly(bestStandard.version()) :
442 null));
446 // Unknown, do a generic project dependency
447 if (forceDepend || (!didDepend && suiteDependency == null))
448 suiteDependency = new SuiteDependency(
449 SuiteDependencyType.PROPRIETARY,
450 requireLevel,
451 new SuiteName("squirreljme.project@" +
452 VMHelpers.projectInternalNameViaSourceSet(
453 __dependency.dependency, __dependency.sourceSet)),
454 null,
455 null);
457 // Write out the dependency if one was requested
458 if (suiteDependency != null)
460 __attributes.putValue(type.dependencyKey(__depCounter[0]++),
461 suiteDependency.toString());
466 * Adds the given profile to the profile list.
468 * @param __attributes The attributes to add into.
469 * @param __profile The profile to add.
470 * @throws NullPointerException On null arguments.
471 * @since 2022/08/09
473 private static void __addProfile(Attributes __attributes,
474 JavaMEProfile __profile)
475 throws NullPointerException
477 if (__attributes == null || __profile == null)
478 throw new NullPointerException("NARG");
480 // Load all the existing profiles
481 Set<JavaMEProfile> existing = new LinkedHashSet<>();
482 String rawExisting =
483 __attributes.getValue("Microedition-Profile");
484 if (rawExisting != null)
485 existing.addAll(JavaMEProfile.parseProfiles(rawExisting));
487 // Add to profile set
488 existing.add(__profile);
490 // Store all of them
491 __attributes.putValue("Microedition-Profile",
492 JavaMEProfile.toString(existing));