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
;
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
;
48 * Implements the action for {@link AdditionalManifestPropertiesTask}.
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
;
62 * Initializes the task action.
64 * @param __taskOutput The task output.
65 * @param __sourceSet The source set used.
68 public AdditionalManifestPropertiesTaskAction(Path __taskOutput
,
71 this.taskOutput
= __taskOutput
;
72 this.sourceSet
= __sourceSet
;
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
);
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());
121 if (type
== JavaMEMidletType
.APPLICATION
)
123 // Add ability for tests to be launched
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
137 // Main class of entry?
138 if (config
.mainClass
!= null)
139 attributes
.putValue("Main-Class", config
.mainClass
);
141 // Add any defined MIDlets
143 for (JavaMEMidlet midlet
: config
.midlets
)
144 attributes
.putValue("MIDlet-" + (midletId
++),
147 // Ignored in the launcher?
148 if (config
.ignoreInLauncher
)
149 attributes
.putValue("X-SquirrelJME-NoLauncher",
155 else if (type
== JavaMEMidletType
.LIBRARY
)
160 else if (type
== JavaMEMidletType
.API
)
162 // Configurations defined?
163 if (!config
.definedConfigurations
.isEmpty())
165 "X-SquirrelJME-DefinedConfigurations",
166 AdditionalManifestPropertiesTaskAction
167 .__delimate(config
.definedConfigurations
, ' '));
170 if (!config
.definedProfiles
.isEmpty())
172 "X-SquirrelJME-DefinedProfiles",
173 AdditionalManifestPropertiesTaskAction
174 .__delimate(config
.definedProfiles
, ' '));
176 // Standards defined?
177 if (!config
.definedStandards
.isEmpty())
179 "X-SquirrelJME-DefinedStandards",
180 AdditionalManifestPropertiesTaskAction
181 .__delimate(config
.definedStandards
, ','));
184 // Always depend on the main source set if we are a non-main
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)
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
,
226 new ProjectAndSourceSet(depend
,
227 SourceSet
.MAIN_SOURCE_SET_NAME
), normalDep
, attributes
,
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!
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.
255 private static String
__delimate(Iterable
<?
> __it
, char __delim
)
256 throws NullPointerException
259 throw new NullPointerException("NARG");
261 // Build output string
262 StringBuilder sb
= new StringBuilder();
263 for (Object o
: __it
)
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.
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
294 __attributes
.getValue("Microedition-Configuration");
295 if (existing
!= null &&
296 __config
.compareTo(new JavaMEConfiguration(existing
)) <= 0)
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.
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()));
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
));
378 // This is another library
379 else if (subSwmType
== JavaMEMidletType
.LIBRARY
)
381 suiteDependency
= new SuiteDependency(
382 SuiteDependencyType
.LIBLET
,
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
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
));
407 catch (NoSuchElementException e
)
412 // Profile specified?
413 if (subConfig
.definedProfiles
!= null &&
414 !subConfig
.definedProfiles
.isEmpty())
417 AdditionalManifestPropertiesTaskAction
.__addProfile(
419 Collections
.max(subConfig
.definedProfiles
));
422 catch (NoSuchElementException e
)
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
,
439 bestStandard
.vendor(),
440 (bestStandard
.version() != null ?
441 SuiteVersionRange
.exactly(bestStandard
.version()) :
446 // Unknown, do a generic project dependency
447 if (forceDepend
|| (!didDepend
&& suiteDependency
== null))
448 suiteDependency
= new SuiteDependency(
449 SuiteDependencyType
.PROPRIETARY
,
451 new SuiteName("squirreljme.project@" +
452 VMHelpers
.projectInternalNameViaSourceSet(
453 __dependency
.dependency
, __dependency
.sourceSet
)),
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.
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
<>();
483 __attributes
.getValue("Microedition-Profile");
484 if (rawExisting
!= null)
485 existing
.addAll(JavaMEProfile
.parseProfiles(rawExisting
));
487 // Add to profile set
488 existing
.add(__profile
);
491 __attributes
.putValue("Microedition-Profile",
492 JavaMEProfile
.toString(existing
));