View Javadoc
1   /*
2     Copyright (C) 2020 - 2022 Alexander Kapitman
3   
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10    Unless required by applicable law or agreed to in writing, software
11    distributed under the License is distributed on an "AS IS" BASIS,
12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    See the License for the specific language governing permissions and
14    limitations under the License.
15  */
16  
17  package ru.akman.maven.plugins.jpackage;
18  
19  import java.io.BufferedWriter;
20  import java.io.File;
21  import java.io.IOException;
22  import java.nio.file.FileSystems;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.PathMatcher;
26  import java.text.MessageFormat;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Objects;
31  import java.util.Properties;
32  import java.util.Set;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  import java.util.stream.Collectors;
36  import java.util.stream.Stream;
37  import org.apache.commons.lang3.JavaVersion;
38  import org.apache.commons.lang3.StringUtils;
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugins.annotations.Component;
42  import org.apache.maven.plugins.annotations.Execute;
43  import org.apache.maven.plugins.annotations.Mojo;
44  import org.apache.maven.plugins.annotations.Parameter;
45  import org.apache.maven.plugins.annotations.ResolutionScope;
46  import org.apache.maven.shared.model.fileset.FileSet;
47  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
48  import org.codehaus.plexus.languages.java.jpms.LocationManager;
49  import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
50  import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
51  import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
52  import org.codehaus.plexus.util.FileUtils;
53  import org.codehaus.plexus.util.cli.CommandLineException;
54  import org.codehaus.plexus.util.cli.Commandline;
55  import ru.akman.maven.plugins.BaseToolMojo;
56  import ru.akman.maven.plugins.CommandLineBuilder;
57  import ru.akman.maven.plugins.CommandLineOption;
58  
59  /**
60   * The jpackage goal lets you create a custom runtime image with
61   * the jpackage tool introduced in Java 13.
62   *
63   * <p>
64   * The main idea is to avoid being tied to project artifacts and allow the user
65   * to fully control the process of creating an image.
66   * </p>
67   */
68  @Mojo(
69      name = "jpackage",
70      requiresDependencyResolution = ResolutionScope.RUNTIME
71  //    defaultPhase = LifecyclePhase.VERIFY,
72  //    requiresProject = true,
73  //    aggregator = <false|true>,
74  //    configurator = "<role hint>",
75  //    executionStrategy = "<once-per-session|always>",
76  //    inheritByDefault = <true|false>,
77  //    instantiationStrategy = InstantiationStrategy.<strategy>,
78  //    requiresDependencyCollection = ResolutionScope.<scope>,
79  //    requiresDirectInvocation = <false|true>,
80  //    requiresOnline = <false|true>,
81  //    threadSafe = <false|true>,
82  )
83  // @Execute(
84  //    This will fork an alternate build lifecycle up to the specified phase
85  //    before continuing to execute the current one.
86  //    If no lifecycle is specified, Maven will use the lifecycle
87  //    of the current build.
88  //    phase = LifecyclePhase.VERIFY
89  //
90  //    This will execute the given goal before execution of this one.
91  //    The goal name is specified using the prefix:goal notation.
92  //    goal = "prefix:goal"
93  //
94  //    This will execute the given alternate lifecycle. A custom lifecycle
95  //    can be defined in META-INF/maven/lifecycle.xml.
96  //    lifecycle = "<lifecycle>", phase="<phase>"
97  // )
98  public class JPackageMojo extends BaseToolMojo {
99  
100   /**
101    * The name of the subdirectory where the tool live.
102    */
103   private static final String TOOL_HOME_BIN = "bin";
104 
105   /**
106    * The tool name.
107    */
108   private static final String TOOL_NAME = "jpackage";
109 
110   /**
111    * Filename for temporary file contains the tool options.
112    */
113   private static final String OPTS_FILE = TOOL_NAME + ".opts";
114 
115   /**
116    * Filename of a module descriptor.
117    */
118   private static final String DESCRIPTOR_NAME = "module-info.class";
119 
120   /**
121    * Filename prefix for temporary file contains the launcher properties.
122    */
123   private static final String PROPS_PREFIX = "launcher.";
124 
125   /**
126    * Filename suffix for temporary file contains the launcher properties.
127    */
128   private static final String PROPS_SUFFIX = ".properties";
129 
130   /**
131    * The line ending pattern.
132    */
133   private static final String SPACES_PATTERN = "\\s+";
134 
135   /**
136    * The char that will used to wrap an option string.
137    */
138   private static final char WRAP_CHAR = '\'';
139 
140   /**
141    * Error message pattern for unability to resolve file path.
142    */
143   private static final String ERROR_RESOLVE =
144       "Error: Unable to resolve file path for {0} [{1}]";
145 
146   /**
147    * List of temporary files.
148    */
149   private final List<File> tempFiles = new ArrayList<>();
150 
151   /**
152    * Resolved project dependencies.
153    */
154   private ResolvePathsResult<File> projectDependencies;
155 
156   /**
157    * Resolved main module descriptor.
158    */
159   private JavaModuleDescriptor mainModuleDescriptor;
160 
161   /**
162    * JPMS location manager.
163    */
164   @Component
165   private LocationManager locationManager;
166 
167   /**
168    * Specifies the path to the JDK home directory providing the tool needed.
169    */
170   @Parameter
171   private File toolhome;
172 
173 
174   // generic options
175 
176 
177   /**
178    * Specifies the location in which generated output files are placed.
179    *
180    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
181    *
182    * <p>The jpackage CLI is: <code>--dest path</code></p>
183    */
184   @Parameter(
185       defaultValue = "${project.build.directory}/jpackage"
186   )
187   private File dest;
188 
189   /**
190    * Specifies the location in which temporary files are placed.
191    * If specified, the directory will not be removed upon the task
192    * completion and must be removed manually.
193    *
194    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
195    *
196    * <p>The jpackage CLI is: <code>--temp path</code></p>
197    */
198   @Parameter
199   private File temp;
200 
201   /**
202    * Specifies the type of package
203    * to create: { 'PLATFORM', 'IMAGE', 'EXE', 'MSI' }.
204    *
205    * <p>The jpackage CLI is: <code>--type {app-image|exe|msi}</code></p>
206    */
207   @Parameter(
208       defaultValue = "PLATFORM"
209   )
210   private PackageType type;
211 
212   /**
213    * Enable verbose tracing.
214    *
215    * <p>The jpackage CLI is: <code>--verbose</code></p>
216    */
217   @Parameter(
218       defaultValue = "false"
219   )
220   private boolean verbose;
221 
222   /**
223    * Specifies version of the application and/or package.
224    *
225    * <p>The jpackage CLI is: <code>--app-version version</code></p>
226    */
227   @Parameter
228   private String appversion;
229 
230   /**
231    * Specifies copyright for the application.
232    *
233    * <p>The jpackage CLI is: <code>--copyright copyright</code></p>
234    */
235   @Parameter
236   private String copyright;
237 
238   /**
239    * Specifies description of the application.
240    *
241    * <p>The jpackage CLI is: <code>--description description</code></p>
242    */
243   @Parameter
244   private String description;
245 
246   /**
247    * Specifies the name of subdirectory relative to the destination
248    * directory in which files of generated application image are placed.
249    *
250    * <em>BUG: A name cannot contain spaces and unicode characters.</em>
251    * <em>BUG: The names used to create the application image and
252    * the application installer must match.</em>
253    *
254    * <p>The jpackage CLI is: <code>--name directory-name</code></p>
255    */
256   @Parameter
257   private String name;
258 
259   /**
260    * Specifies vendor of the application.
261    *
262    * <p>The jpackage CLI is: <code>--vendor vendor</code></p>
263    */
264   @Parameter
265   private String vendor;
266 
267 
268   // options for creating the application image
269 
270 
271   /**
272    * Specifies the location of the icon of the application launcher.
273    *
274    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
275    *
276    * <p>The jpackage CLI is: <code>--icon path</code></p>
277    */
278   @Parameter
279   private File icon;
280 
281   /**
282    * Specifies the location of the input directory that contains
283    * the files to be packaged. All files in the input directory
284    * will be packaged into the application image into $APPDIR directory.
285    *
286    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
287    *
288    * <p>The jpackage CLI is: <code>--input path</code></p>
289    */
290   @Parameter
291   private File input;
292 
293 
294   // options for creating the runtime image
295 
296 
297   /**
298    * Specifies the location of the predefined runtime image (result of jlink)
299    * that will be copied into the application image.
300    * If not specified, jpackage will run jlink to create
301    * the runtime image using options:
302    *   - <code>--strip-debug</code>
303    *   - <code>--no-header-files</code>
304    *   - <code>--no-man-pages</code>
305    *   - <code>--strip-native-commands</code>
306    *
307    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
308    *
309    * <p>The jpackage CLI is: <code>--runtime-image path</code></p>
310    */
311   @Parameter
312   private File runtimeimage;
313 
314   /**
315    * Specifies the module path. The path where the jlink tool discovers
316    * observable modules: modular JAR files, JMOD files, exploded modules.
317    * If this option is not specified, then the default module path
318    * is $JAVA_HOME/jmods. This directory contains the java.base module
319    * and the other standard and JDK modules. If this option is specified
320    * but the java.base module cannot be resolved from it, then
321    * the jlink command appends $JAVA_HOME/jmods to the module path.
322    * Pass on --modulepath option to jlink.
323    *
324    * <p>
325    * pathelements - passed to jlink as is
326    * filesets - sets of files (without directories)
327    * dirsets - sets of directories (without files)
328    * dependencysets - sets of dependencies with specified includes and
329    *                  excludes patterns (glob: or regex:) for file names
330    *                  and regex patterns only for module names
331    * </p>
332    *
333    * <p><pre>
334    * &lt;modulepath&gt;
335    *   &lt;pathelements&gt;
336    *     &lt;pathelement&gt;mod.jar&lt;/pathelement&gt;
337    *     &lt;pathelement&gt;mod.jmod&lt;/pathelement&gt;
338    *     &lt;pathelement&gt;mods/exploded/mod&lt;/pathelement&gt;
339    *   &lt;/pathelements&gt;
340    *   &lt;filesets&gt;
341    *     &lt;fileset&gt;
342    *       &lt;directory&gt;${project.build.directory}&lt;/directory&gt;
343    *       &lt;includes&gt;
344    *         &lt;include&gt;*&#42;/*&lt;/include&gt;
345    *       &lt;/includes&gt;
346    *       &lt;excludes&gt;
347    *         &lt;exclude&gt;*&#42;/*Empty.jar&lt;/exclude&gt;
348    *       &lt;/excludes&gt;
349    *       &lt;followSymlinks&gt;false&lt;/followSymlinks&gt;
350    *     &lt;/fileset&gt;
351    *   &lt;/filesets&gt;
352    *   &lt;dirsets&gt;
353    *     &lt;dirset&gt;
354    *       &lt;directory&gt;target&lt;/directory&gt;
355    *       &lt;includes&gt;
356    *         &lt;include&gt;*&#42;/*&lt;/include&gt;
357    *       &lt;/includes&gt;
358    *       &lt;excludes&gt;
359    *         &lt;exclude&gt;*&#42;/*Test&lt;/exclude&gt;
360    *       &lt;/excludes&gt;
361    *       &lt;followSymlinks&gt;true&lt;/followSymlinks&gt;
362    *     &lt;/dirset&gt;
363    *   &lt;/dirsets&gt;
364    *   &lt;dependencysets&gt;
365    *     &lt;dependencyset&gt;
366    *       &lt;includeoutput&gt;false&lt;/includeoutput&gt;
367    *       &lt;excludeautomatic&gt;false&lt;/excludeautomatic&gt;
368    *       &lt;includes&gt;
369    *         &lt;include&gt;glob:*&#42;/*.jar&lt;/include&gt;
370    *         &lt;include&gt;regex:foo-(bar|baz)-.*?\.jar&lt;/include&gt;
371    *       &lt;/includes&gt;
372    *       &lt;includenames&gt;
373    *         &lt;includename&gt;.*&lt;/includename&gt;
374    *       &lt;/includenames&gt;
375    *       &lt;excludes&gt;
376    *         &lt;exclude&gt;glob:*&#42;/javafx.*Empty&lt;/exclude&gt;
377    *       &lt;/excludes&gt;
378    *       &lt;excludenames&gt;
379    *         &lt;excludename&gt;javafx\..+Empty&lt;/excludename&gt;
380    *       &lt;/excludenames&gt;
381    *     &lt;/dependencyset&gt;
382    *   &lt;/dependencysets&gt;
383    * &lt;/modulepath&gt;
384    * </pre></p>
385    *
386    * <p>The jpackage CLI is: <code>--modulepath path</code></p>
387    */
388   @Parameter
389   private ModulePath modulepath;
390 
391   /**
392    * Specifies the modules names (names of root modules) to add to
393    * the runtime image. Their transitive dependencies will add too.
394    * This module list, along with the main module (if specified)
395    * will be passed to jlink as the --add-module argument.
396    * If not specified, either just the main module (if module is specified),
397    * or the default set of modules (if mainjar is specified) are used.
398    *
399    * <p><pre>
400    * &lt;addmodules&gt;
401    *   &lt;addmodule&gt;java.base&lt;/addmodule&gt;
402    *   &lt;addmodule&gt;org.example.rootmodule&lt;/addmodule&gt;
403    * &lt;/addmodules&gt;
404    * </pre></p>
405    *
406    * <p>The jpackage CLI is: <code>--add-modules module [, module...]</code></p>
407    */
408   @Parameter
409   private List<String> addmodules;
410 
411   /**
412    * Link service provider modules and their dependencies.
413    * Pass on --bind-services option to jlink.
414    *
415    * <p>The jpackage CLI is: <code>--bind-services</code></p>
416    */
417   @Parameter(
418       defaultValue = "false"
419   )
420   private boolean bindservices;
421 
422 
423   // options for creating the application launcher(s)
424 
425 
426   /**
427    * Specifies the main module (and optionally main class) of
428    * the application. This module must be located on the module path.
429    * When this option is specified, the main module will be linked
430    * in the Java runtime image. Either module or mainjar option
431    * can be specified but not both.
432    *
433    * <p>The jpackage CLI is: <code>--module module-name[/class-name]</code></p>
434    */
435   @Parameter
436   private String module;
437 
438   /**
439    * Specifies the main JAR of the application, specified as a path
440    * relative to the input path, containing the main class.
441    * Either module or mainjar option can be specified but not both.
442    *
443    * <p>The jpackage CLI is: <code>--main-jar jar-name</code></p>
444    */
445   @Parameter
446   private String mainjar;
447 
448   /**
449    * Specifies the qualified name of the application main class to execute.
450    * This option can only be used if mainjar is specified.
451    *
452    * <p>The jpackage CLI is: <code>--main-class class-name</code></p>
453    */
454   @Parameter
455   private String mainclass;
456 
457   /**
458    * Specifies the command line arguments to pass to the main class
459    * if no command line arguments are given to the launcher.
460    *
461    * <p>The jpackage CLI is: <code>--arguments args</code></p>
462    */
463   @Parameter
464   private String arguments;
465 
466   /**
467    * Specifies the options to pass to the Java runtime.
468    *
469    * <p>The jpackage CLI is: <code>--java-options opts</code></p>
470    */
471   @Parameter
472   private String javaoptions;
473 
474   /**
475    * Specifies options are added to, or used to overwrite, the original
476    * command line options to build additional alternative launchers.
477    *
478    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
479    *
480    * <p><pre>
481    * &lt;addlaunchers&gt;
482    *   &lt;addlauncher&gt;
483    *     &lt;name&gt;launcher1&lt;/name&gt;
484    *     &lt;file&gt;config/jpackage/launcher1.properties&lt;/file&gt;
485    *     &lt;module&gt;mainModule1Name/mainClass1Name&lt;/module&gt;
486    *     &lt;mainjar&gt;mainJar1.jar&lt;/mainjar&gt;
487    *     &lt;mainclass&gt;mainClass1Name&lt;/mainclass&gt;
488    *     &lt;arguments&gt;--arg11 --arg12&lt;/arguments&gt;
489    *     &lt;javaoptions&gt;-Xms128m -Xmx1024m&lt;/javaoptions&gt;
490    *     &lt;appversion&gt;1.0.1&lt;/appversion&gt;
491    *     &lt;icon&gt;config/jpackage/launcher1.ico&lt;/icon&gt;
492    *     &lt;winconsole&gt;true&lt;/winconsole&gt;
493    *   &lt;/addlauncher&gt;
494    * &lt;/addlaunchers&gt;
495    * </pre></p>
496    *
497    * <p>The jpackage CLI is: <code>--add-launcher name=path</code></p>
498    */
499   @Parameter
500   private List<Launcher> addlaunchers;
501 
502 
503   // platform dependent option for creating the application launcher
504 
505 
506   /**
507    * Enable creating a console launcher for the application, should be
508    * specified for application which requires console interactions.
509    *
510    * <p>The jlink CLI is: <code>--win-console</code></p>
511    */
512   @Parameter(
513       defaultValue = "false"
514   )
515   private boolean winconsole;
516 
517 
518   // options for creating the application installable package
519 
520 
521   /**
522    * Specifies the location of the predefined application image that is used
523    * to build an installable package.
524    *
525    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
526    *
527    * <p>The jpackage CLI is: <code>--app-image path</code></p>
528    */
529   @Parameter
530   private File appimage;
531 
532   /**
533    * Specifies the location of a properties file that contains list of key,
534    * value pairs. The keys "extension", "mime-type", "icon", and "description"
535    * can be used to describe the association.
536    *
537    * <p><pre>
538    * &lt;fileassociations&gt;
539    *   &lt;fileassociation&gt;assoc1.properties&lt;/fileassociation&gt;
540    *   &lt;fileassociation&gt;assoc2.properties&lt;/fileassociation&gt;
541    * &lt;/fileassociations&gt;
542    * </pre></p>
543    *
544    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
545    *
546    * <p>The jpackage CLI is: <code>--file-associations path</code></p>
547    */
548   @Parameter
549   private List<File> fileassociations;
550 
551   /**
552    * Specifies the relative sub-path under the default installation
553    * location of the application for Windows, or absolute path of the
554    * installation directory of the application for Mac or Linux.
555    *
556    * <p>The jpackage CLI is: <code>--install-dir name</code></p>
557    */
558   @Parameter
559   private String installdir;
560 
561   /**
562    * Specifies the location of a license file.
563    *
564    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
565    *
566    * <p>The jpackage CLI is: <code>--license-file path</code></p>
567    */
568   @Parameter
569   private File licensefile;
570 
571   /**
572    * Specifies the location of a resources directory that override
573    * jpackage resources. Icons, template files, and other resources
574    * of jpackage can be overridden by adding replacement resources
575    * to this directory.
576    *
577    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
578    *
579    * <p>The jpackage CLI is: <code>--resource-dir path</code></p>
580    */
581   @Parameter
582   private File resourcedir;
583 
584 
585   // platform dependent options for creating the application
586   // installable package (Windows)
587 
588 
589   /**
590    * Enable adding a dialog to choose a directory in which
591    * the product is installed.
592    *
593    * <p>The jpackage CLI is: <code>--win-dir-chooser</code></p>
594    */
595   @Parameter(
596       defaultValue = "false"
597   )
598   private boolean windirchooser;
599 
600   /**
601    * Enable adding the application to the system menu.
602    *
603    * <p>The jpackage CLI is: <code>--win-menu</code></p>
604    */
605   @Parameter(
606       defaultValue = "false"
607   )
608   private boolean winmenu;
609 
610   /**
611    * Start menu group this application is placed in.
612    *
613    * <p>The jpackage CLI is: <code>--win-menu-group name</code></p>
614    */
615   @Parameter
616   private String winmenugroup;
617 
618   /**
619    * Enable requesting to perform an install on a per-user basis.
620    *
621    * <p>The jpackage CLI is: <code>--win-per-user-install</code></p>
622    */
623   @Parameter(
624       defaultValue = "false"
625   )
626   private boolean winperuserinstall;
627 
628   /**
629    * Enable creating a desktop shortcut for the application.
630    *
631    * <p>The jpackage CLI is: <code>--win-shortcut</code></p>
632    */
633   @Parameter(
634       defaultValue = "false"
635   )
636   private boolean winshortcut;
637 
638   /**
639    * UUID associated with upgrades for this package.
640    *
641    * <p>The jpackage CLI is: <code>--win-upgrade-uuid uuid</code></p>
642    */
643   @Parameter
644   private String winupgradeuuid;
645 
646 
647   // platform dependent options for creating the application
648   // installable package (Mac)
649 
650 
651   /**
652    * An identifier that uniquely identifies the application for macOS.
653    * Defaults to the main class name. May only use alphanumeric (A-Z,a-z,0-9),
654    * hyphen (-), and period (.) characters.
655    *
656    * <p>The jpackage CLI is: <code>--mac-package-identifier id</code></p>
657    */
658   @Parameter
659   private String macpackageidentifier;
660 
661   /**
662    * Name of the application as it appears in the Menu Bar.
663    * This can be different from the application name.
664    * This name must be less than 16 characters long and be suitable for
665    * displaying in the menu bar and the application Info window.
666    * Defaults to the application name.
667    *
668    * <p>The jpackage CLI is: <code>--mac-package-name name</code></p>
669    */
670   @Parameter
671   private String macpackagename;
672 
673   /**
674    * When signing the application package, this value is prefixed
675    * to all components that need to be signed that don't have
676    * an existing package identifier.
677    *
678    * <p>The jpackage CLI is: <code>--mac-package-signing-prefix prefix</code>
679    * </p>
680    */
681   @Parameter
682   private String macpackagesigningprefix;
683 
684   /**
685    * Request that the package be signed.
686    *
687    * <p>The jpackage CLI is: <code>--mac-sign</code></p>
688    */
689   @Parameter
690   private boolean macsign;
691 
692   /**
693    * Path of the keychain to search for the signing identity
694    * (absolute path or relative to the current directory).
695    * If not specified, the standard keychains are used.
696    *
697    * <em>BUG: A path cannot contain spaces and unicode characters.</em>
698    *
699    * <p>The jpackage CLI is: <code>--mac-signing-keychain path</code></p>
700    */
701   @Parameter
702   private File macsigningkeychain;
703 
704   /**
705    * Team name portion in Apple signing identities' names.
706    * For example "Developer ID Application: ".
707    *
708    * <p>The jpackage CLI is: <code>--mac-signing-key-user-name name</code></p>
709    */
710   @Parameter
711   private String macsigningkeyusername;
712 
713 
714   // platform dependent options for creating the application
715   // installable package (Linux)
716 
717 
718   /**
719    * Name for Linux package, defaults to the application name.
720    *
721    * <p>The jpackage CLI is: <code>--linux-package-name name</code></p>
722    */
723   @Parameter
724   private String linuxpackagename;
725 
726   /**
727    * Maintainer for .deb package.
728    *
729    * <p>The jpackage CLI is: <code>--linux-deb-maintainer email</code></p>
730    */
731   @Parameter
732   private String linuxdebmaintainer;
733 
734   /**
735    * Menu group this application is placed in.
736    *
737    * <p>The jpackage CLI is: <code>--linux-menu-group name</code></p>
738    */
739   @Parameter
740   private String linuxmenugroup;
741 
742   /**
743    * Required packages or capabilities for the application.
744    *
745    * <p>The jpackage CLI is: <code>--linux-package-deps</code></p>
746    */
747   @Parameter
748   private boolean linuxpackagedeps;
749 
750   /**
751    * Type of the license ("License: name" of the RPM .spec).
752    *
753    * <p>The jpackage CLI is: <code>--linux-rpm-license-type name</code></p>
754    */
755   @Parameter
756   private String linuxrpmlicensetype;
757 
758   /**
759    * Release value of the RPM name.spec file or Debian revision value
760    * of the DEB control file.
761    *
762    * <p>The jpackage CLI is: <code>--linux-app-release name</code></p>
763    */
764   @Parameter
765   private String linuxapprelease;
766 
767   /**
768    * Group value of the RPM name.spec file or Section value
769    * of DEB control file.
770    *
771    * <p>The jpackage CLI is: <code>--linux-app-category name</code></p>
772    */
773   @Parameter
774   private String linuxappcategory;
775 
776   /**
777    * Creates a shortcut for the application.
778    *
779    * <p>The jpackage CLI is: <code>--linux-shortcut</code></p>
780    */
781   @Parameter
782   private boolean linuxshortcut;
783 
784   /**
785    * Process options.
786    *
787    * @param cmdLine the command line builder
788    *
789    * @throws MojoExecutionException if any errors occurred
790    */
791   private void processOptions(final CommandLineBuilder cmdLine)
792       throws MojoExecutionException {
793     CommandLineOption opt = null;
794     // dest
795     opt = cmdLine.createOpt();
796     opt.createArg().setValue("--dest");
797     try {
798       opt.createArg().setValue(dest.getCanonicalPath());
799     } catch (IOException ex) {
800       throw new MojoExecutionException(MessageFormat.format(
801           ERROR_RESOLVE,
802           "--dest",
803           dest.toString()), ex);
804     }
805     // temp
806     if (temp != null) {
807       opt = cmdLine.createOpt();
808       opt.createArg().setValue("--temp");
809       try {
810         opt.createArg().setValue(temp.getCanonicalPath());
811       } catch (IOException ex) {
812         throw new MojoExecutionException(MessageFormat.format(
813             ERROR_RESOLVE,
814             "--temp",
815             temp.toString()), ex);
816       }
817     }
818     // type
819     if (type != null && !type.equals(PackageType.PLATFORM)) {
820       opt = cmdLine.createOpt();
821       opt.createArg().setValue("--type");
822       switch (type) {
823         case IMAGE:
824           opt.createArg().setValue("app-image");
825           break;
826         case EXE:
827           opt.createArg().setValue("exe");
828           break;
829         case MSI:
830           opt.createArg().setValue("msi");
831           break;
832         case RPM:
833           opt.createArg().setValue("rpm");
834           break;
835         case DEB:
836           opt.createArg().setValue("deb");
837           break;
838         case PKG:
839           opt.createArg().setValue("pkg");
840           break;
841         case DMG:
842           opt.createArg().setValue("dmg");
843           break;
844         default:
845           // skip
846       }
847     }
848     // verbose
849     if (verbose) {
850       opt = cmdLine.createOpt();
851       opt.createArg().setValue("--verbose");
852     }
853     // appversion
854     if (!StringUtils.isBlank(appversion)) {
855       opt = cmdLine.createOpt();
856       opt.createArg().setValue("--app-version");
857       opt.createArg().setValue(PluginUtils.wrapOpt(
858           appversion, WRAP_CHAR));
859     }
860     // copyright
861     if (!StringUtils.isBlank(copyright)) {
862       opt = cmdLine.createOpt();
863       opt.createArg().setValue("--copyright");
864       opt.createArg().setValue(PluginUtils.wrapOpt(
865           copyright, WRAP_CHAR));
866     }
867     // description
868     if (!StringUtils.isBlank(description)) {
869       opt = cmdLine.createOpt();
870       opt.createArg().setValue("--description");
871       opt.createArg().setValue(PluginUtils.wrapOpt(
872           description.replaceAll(SPACES_PATTERN, " "), WRAP_CHAR));
873     }
874     // name
875     if (!StringUtils.isBlank(name)) {
876       opt = cmdLine.createOpt();
877       opt.createArg().setValue("--name");
878       opt.createArg().setValue(PluginUtils.wrapOpt(
879           name, WRAP_CHAR));
880     }
881     // vendor
882     if (!StringUtils.isBlank(vendor)) {
883       opt = cmdLine.createOpt();
884       opt.createArg().setValue("--vendor");
885       opt.createArg().setValue(PluginUtils.wrapOpt(
886           vendor, WRAP_CHAR));
887     }
888     // icon
889     if (icon != null) {
890       opt = cmdLine.createOpt();
891       opt.createArg().setValue("--icon");
892       try {
893         opt.createArg().setValue(icon.getCanonicalPath());
894       } catch (IOException ex) {
895         throw new MojoExecutionException(MessageFormat.format(
896             ERROR_RESOLVE,
897             "--icon",
898             icon.toString()), ex);
899       }
900     }
901     // input
902     if (input != null) {
903       opt = cmdLine.createOpt();
904       opt.createArg().setValue("--input");
905       try {
906         opt.createArg().setValue(input.getCanonicalPath());
907       } catch (IOException ex) {
908         throw new MojoExecutionException(MessageFormat.format(
909             ERROR_RESOLVE,
910             "--input",
911             input.toString()), ex);
912       }
913     }
914     // runtimeimage
915     if (runtimeimage != null) {
916       opt = cmdLine.createOpt();
917       opt.createArg().setValue("--runtime-image");
918       try {
919         opt.createArg().setValue(runtimeimage.getCanonicalPath());
920       } catch (IOException ex) {
921         throw new MojoExecutionException(MessageFormat.format(
922             ERROR_RESOLVE,
923             "--runtime-image",
924             runtimeimage.toString()), ex);
925       }
926     }
927     // module
928     if (!StringUtils.isBlank(module)) {
929       opt = cmdLine.createOpt();
930       opt.createArg().setValue("--module");
931       opt.createArg().setValue(StringUtils.stripToEmpty(
932           module));
933     }
934     // mainjar
935     if (!StringUtils.isBlank(mainjar)) {
936       opt = cmdLine.createOpt();
937       opt.createArg().setValue("--main-jar");
938       opt.createArg().setValue(PluginUtils.wrapOpt(
939           mainjar, WRAP_CHAR));
940     }
941     // mainclass
942     if (!StringUtils.isBlank(mainclass)) {
943       opt = cmdLine.createOpt();
944       opt.createArg().setValue("--main-class");
945       opt.createArg().setValue(StringUtils.stripToEmpty(
946           mainclass));
947     }
948     // arguments
949     // TODO: argument quotes
950     /*
951     --arguments "\"String 1\" \"String 2\""
952     --arguments "\"String 1\"" --arguments "\"String 2\""
953     --arguments "'String 1'" --arguments "'String 2'"
954     */
955     if (!StringUtils.isBlank(arguments)) {
956       opt = cmdLine.createOpt();
957       opt.createArg().setValue("--arguments");
958       opt.createArg().setValue(PluginUtils.wrapOpt(
959           arguments, WRAP_CHAR));
960     }
961     // javaoptions
962     // TODO: javaoptions quotes
963     /*
964     --java-options "\"-DAppOption=text string\""
965     --java-options "'-DAppOption=text string'"
966     --java-options "-XX:OnError=\"\\\"userdump.exe %p\\\"\""
967     --java-options "-splash:\$APPDIR/myAppSplash.jpg"
968     */
969     if (!StringUtils.isBlank(javaoptions)) {
970       opt = cmdLine.createOpt();
971       opt.createArg().setValue("--java-options");
972       opt.createArg().setValue(PluginUtils.wrapOpt(
973           javaoptions, WRAP_CHAR));
974     }
975     // addlaunchers
976     if (addlaunchers != null && !addlaunchers.isEmpty()) {
977       for (final Launcher addlauncher : addlaunchers) {
978         final String name = StringUtils.stripToEmpty(addlauncher.getName());
979         if (!StringUtils.isBlank(name)) {
980           final File file = processLauncher(addlauncher);
981           tempFiles.add(file);
982           opt = cmdLine.createOpt();
983           opt.createArg().setValue("--add-launcher");
984           try {
985             opt.createArg().setValue(name + "=" + file.getCanonicalPath());
986           } catch (IOException ex) {
987             throw new MojoExecutionException(MessageFormat.format(
988                 ERROR_RESOLVE,
989                 "--add-launcher",
990                 file.toString()), ex);
991           }
992         }
993       }
994     }
995     // winconsole
996     if (winconsole) {
997       opt = cmdLine.createOpt();
998       opt.createArg().setValue("--win-console");
999     }
1000     // appimage
1001     if (appimage != null) {
1002       opt = cmdLine.createOpt();
1003       opt.createArg().setValue("--app-image");
1004       try {
1005         opt.createArg().setValue(appimage.getCanonicalPath());
1006       } catch (IOException ex) {
1007         throw new MojoExecutionException(MessageFormat.format(
1008             ERROR_RESOLVE,
1009             "--app-image",
1010             appimage.toString()), ex);
1011       }
1012     }
1013     // fileassociations
1014     if (fileassociations != null) {
1015       for (final File fileassociation : fileassociations) {
1016         if (fileassociation != null
1017             && !StringUtils.isBlank(fileassociation.toString())) {
1018           opt = cmdLine.createOpt();
1019           opt.createArg().setValue("--file-associations");
1020           try {
1021             opt.createArg().setValue(fileassociation.getCanonicalPath());
1022           } catch (IOException ex) {
1023             throw new MojoExecutionException(MessageFormat.format(
1024                 ERROR_RESOLVE,
1025                 "--file-associations",
1026                 fileassociation.toString()), ex);
1027           }
1028         }
1029       }
1030     }
1031     // installdir
1032     if (!StringUtils.isBlank(installdir)) {
1033       opt = cmdLine.createOpt();
1034       opt.createArg().setValue("--install-dir");
1035       opt.createArg().setValue(PluginUtils.wrapOpt(
1036           installdir, WRAP_CHAR));
1037     }
1038     // licensefile
1039     if (licensefile != null) {
1040       opt = cmdLine.createOpt();
1041       opt.createArg().setValue("--license-file");
1042       try {
1043         opt.createArg().setValue(licensefile.getCanonicalPath());
1044       } catch (IOException ex) {
1045         throw new MojoExecutionException(MessageFormat.format(
1046             ERROR_RESOLVE,
1047             "--license-file",
1048             licensefile.toString()), ex);
1049       }
1050     }
1051     // resourcedir
1052     if (resourcedir != null) {
1053       opt = cmdLine.createOpt();
1054       opt.createArg().setValue("--resource-dir");
1055       try {
1056         opt.createArg().setValue(resourcedir.getCanonicalPath());
1057       } catch (IOException ex) {
1058         throw new MojoExecutionException(MessageFormat.format(
1059             ERROR_RESOLVE,
1060             "--resource-dir",
1061             resourcedir.toString()), ex);
1062       }
1063     }
1064     // windirchooser
1065     if (windirchooser) {
1066       opt = cmdLine.createOpt();
1067       opt.createArg().setValue("--win-dir-chooser");
1068     }
1069     // winmenu
1070     if (winmenu) {
1071       opt = cmdLine.createOpt();
1072       opt.createArg().setValue("--win-menu");
1073     }
1074     // winmenugroup
1075     if (!StringUtils.isBlank(winmenugroup)) {
1076       opt = cmdLine.createOpt();
1077       opt.createArg().setValue("--win-menu-group");
1078       opt.createArg().setValue(PluginUtils.wrapOpt(
1079           winmenugroup, WRAP_CHAR));
1080     }
1081     // winperuserinstall
1082     if (winperuserinstall) {
1083       opt = cmdLine.createOpt();
1084       opt.createArg().setValue("--win-per-user-install");
1085     }
1086     // winshortcut
1087     if (winshortcut) {
1088       opt = cmdLine.createOpt();
1089       opt.createArg().setValue("--win-shortcut");
1090     }
1091     // winupgradeuuid
1092     if (!StringUtils.isBlank(winupgradeuuid)) {
1093       opt = cmdLine.createOpt();
1094       opt.createArg().setValue("--win-upgrade-uuid");
1095       opt.createArg().setValue(PluginUtils.wrapOpt(
1096           winupgradeuuid, WRAP_CHAR));
1097     }
1098     // macpackageidentifier
1099     if (!StringUtils.isBlank(macpackageidentifier)) {
1100       opt = cmdLine.createOpt();
1101       opt.createArg().setValue("--mac-package-identifier");
1102       opt.createArg().setValue(PluginUtils.wrapOpt(
1103           macpackageidentifier, WRAP_CHAR));
1104     }
1105     // macpackagename
1106     if (!StringUtils.isBlank(macpackagename)) {
1107       opt = cmdLine.createOpt();
1108       opt.createArg().setValue("--mac-package-name");
1109       opt.createArg().setValue(PluginUtils.wrapOpt(
1110           macpackagename, WRAP_CHAR));
1111     }
1112     // macpackagesigningprefix
1113     if (!StringUtils.isBlank(macpackagesigningprefix)) {
1114       opt = cmdLine.createOpt();
1115       opt.createArg().setValue("--mac-package-signing-prefix");
1116       opt.createArg().setValue(PluginUtils.wrapOpt(
1117           macpackagesigningprefix, WRAP_CHAR));
1118     }
1119     // macsign
1120     if (macsign) {
1121       opt = cmdLine.createOpt();
1122       opt.createArg().setValue("--mac-sign");
1123     }
1124     // macsigningkeychain
1125     if (macsigningkeychain != null) {
1126       opt = cmdLine.createOpt();
1127       opt.createArg().setValue("--mac-signing-keychain");
1128       try {
1129         opt.createArg().setValue(macsigningkeychain.getCanonicalPath());
1130       } catch (IOException ex) {
1131         throw new MojoExecutionException(MessageFormat.format(
1132             ERROR_RESOLVE,
1133             "--mac-signing-keychain",
1134             macsigningkeychain.toString()), ex);
1135       }
1136     }
1137     // macsigningkeyusername
1138     if (!StringUtils.isBlank(macsigningkeyusername)) {
1139       opt = cmdLine.createOpt();
1140       opt.createArg().setValue("--mac-signing-key-user-name");
1141       opt.createArg().setValue(PluginUtils.wrapOpt(
1142           macsigningkeyusername, WRAP_CHAR));
1143     }
1144     // linuxpackagename
1145     if (!StringUtils.isBlank(linuxpackagename)) {
1146       opt = cmdLine.createOpt();
1147       opt.createArg().setValue("--linux-package-name");
1148       opt.createArg().setValue(PluginUtils.wrapOpt(
1149           linuxpackagename, WRAP_CHAR));
1150     }
1151     // linuxdebmaintainer
1152     if (!StringUtils.isBlank(linuxdebmaintainer)) {
1153       opt = cmdLine.createOpt();
1154       opt.createArg().setValue("--linux-deb-maintainer");
1155       opt.createArg().setValue(PluginUtils.wrapOpt(
1156           linuxdebmaintainer, WRAP_CHAR));
1157     }
1158     // linuxmenugroup
1159     if (!StringUtils.isBlank(linuxmenugroup)) {
1160       opt = cmdLine.createOpt();
1161       opt.createArg().setValue("--linux-menu-group");
1162       opt.createArg().setValue(PluginUtils.wrapOpt(
1163           linuxmenugroup, WRAP_CHAR));
1164     }
1165     // linuxpackagedeps
1166     if (linuxpackagedeps) {
1167       opt = cmdLine.createOpt();
1168       opt.createArg().setValue("--linux-package-deps");
1169     }
1170     // linuxrpmlicensetype
1171     if (!StringUtils.isBlank(linuxrpmlicensetype)) {
1172       opt = cmdLine.createOpt();
1173       opt.createArg().setValue("--linux-rpm-license-type");
1174       opt.createArg().setValue(PluginUtils.wrapOpt(
1175           linuxrpmlicensetype, WRAP_CHAR));
1176     }
1177     // linuxapprelease
1178     if (!StringUtils.isBlank(linuxapprelease)) {
1179       opt = cmdLine.createOpt();
1180       opt.createArg().setValue("--linux-app-release");
1181       opt.createArg().setValue(PluginUtils.wrapOpt(
1182           linuxapprelease, WRAP_CHAR));
1183     }
1184     // linuxappcategory
1185     if (!StringUtils.isBlank(linuxappcategory)) {
1186       opt = cmdLine.createOpt();
1187       opt.createArg().setValue("--linux-app-category");
1188       opt.createArg().setValue(PluginUtils.wrapOpt(
1189           linuxappcategory, WRAP_CHAR));
1190     }
1191     // linuxshortcut
1192     if (linuxshortcut) {
1193       opt = cmdLine.createOpt();
1194       opt.createArg().setValue("--linux-shortcut");
1195     }
1196   }
1197 
1198   /**
1199    * Process additional launcher.
1200    * Create a temporary file contains the efective launcher properties.
1201    *
1202    * @param launcher the additional launcher
1203    *
1204    * @return the temporary file contains the efective launcher properties
1205    *
1206    * @throws MojoExecutionException if any errors occurred
1207    */
1208   private File processLauncher(final Launcher launcher)
1209       throws MojoExecutionException {
1210     final String name = launcher.getName();
1211     // get properties
1212     Properties props;
1213     try {
1214       props = launcher.getProperties(getCharset());
1215     } catch (IOException ex) {
1216       throw new MojoExecutionException(MessageFormat.format(
1217           "Error: Unable to read properties for launcher: [{0}]",
1218           name), ex);
1219     }
1220     // create a temporary properties file
1221     File file;
1222     try {
1223       file = Files.createTempFile(getBuildDir().toPath(),
1224           PROPS_PREFIX, PROPS_SUFFIX).toFile();
1225     } catch (IOException ex) {
1226       throw new MojoExecutionException(MessageFormat.format(
1227           "Error: Unable to create temporary file for launcher: [{0}]",
1228           name), ex);
1229     }
1230     // save properties to the temporary file
1231     try (BufferedWriter bw =
1232         Files.newBufferedWriter(file.toPath(), getCharset())) {
1233       props.store(bw, null);
1234     } catch (IOException ex) {
1235       throw new MojoExecutionException(MessageFormat.format(
1236           "Error: Unable to write temporary file for launcher: [{0}]",
1237           name), ex);
1238     }
1239     if (getLog().isDebugEnabled()) {
1240       getLog().debug(MessageFormat.format(
1241           "Found additional launcher: [{0}]", name)
1242           + System.lineSeparator()
1243           + props.toString());
1244     }
1245     return file;
1246   }
1247 
1248   /**
1249    * Resolve project dependencies.
1250    *
1251    * @return map of the resolved project dependencies
1252    *
1253    * @throws MojoExecutionException if any errors occurred while resolving
1254    *                                dependencies
1255    */
1256   private ResolvePathsResult<File> resolveDependencies()
1257       throws MojoExecutionException {
1258 
1259     // get project artifacts - all dependencies that this project has,
1260     // including transitive ones (depends on what phases have run)
1261     final Set<Artifact> artifacts = getProject().getArtifacts();
1262     if (getLog().isDebugEnabled()) {
1263       getLog().debug(PluginUtils.getArtifactSetDebugInfo(artifacts));
1264     }
1265 
1266     // create a list of the paths which will be resolved
1267     final List<File> paths = new ArrayList<>();
1268 
1269     // add the project output directory
1270     paths.add(getOutputDir());
1271 
1272     // SCOPE_COMPILE  - This is the default scope, used if none is specified.
1273     //                  Compile dependencies are available in all classpaths.
1274     //                  Furthermore, those dependencies are propagated to
1275     //                  dependent projects.
1276     // SCOPE_PROVIDED - This is much like compile, but indicates you expect
1277     //                  the JDK or a container to provide it at runtime.
1278     //                  It is only available on the compilation and
1279     //                  test classpath, and is not transitive.
1280     // SCOPE_SYSTEM   - This scope is similar to provided except that you
1281     //                  have to provide the JAR which contains it explicitly.
1282     //                  The artifact is always available and is not looked up
1283     //                  in a repository.    
1284     // SCOPE_RUNTIME  - This scope indicates that the dependency is not
1285     //                  required for compilation, but is for execution.
1286     //                  It is in the runtime and test classpaths, but not
1287     //                  the compile classpath.
1288     // SCOPE_TEST     - This scope indicates that the dependency is not
1289     //                  required for normal use of the application, and is
1290     //                  only available for the test compilation and execution
1291     //                  phases. It is not transitive.
1292     // SCOPE_IMPORT   - This scope indicates that the dependency is a managed
1293     //                  POM dependency i.e. only other POM into
1294     //                  the dependencyManagement section.
1295 
1296     // [ !SCOPE_TEST ] add the project artifacts files
1297     paths.addAll(artifacts.stream()
1298         .filter(a -> a != null && !Artifact.SCOPE_TEST.equals(a.getScope()))
1299         .map(a -> a.getFile())
1300         .collect(Collectors.toList()));
1301 
1302     // [ SCOPE_SYSTEM ] add the project system dependencies
1303     // getSystemPath() is used only if the dependency scope is system
1304     paths.addAll(getProject().getDependencies().stream()
1305         .filter(d -> d != null && !StringUtils.isBlank(d.getSystemPath()))
1306         .map(d -> new File(StringUtils.stripToEmpty(d.getSystemPath())))
1307         .collect(Collectors.toList()));
1308 
1309     // create request contains all information
1310     // required to analyze the project
1311     final ResolvePathsRequest<File> request =
1312         ResolvePathsRequest.ofFiles(paths);
1313 
1314     // this is used to resolve main module descriptor
1315     final File descriptorFile =
1316         getOutputDir().toPath().resolve(DESCRIPTOR_NAME).toFile();
1317     if (descriptorFile.exists() && !descriptorFile.isDirectory()) {
1318       request.setMainModuleDescriptor(descriptorFile);
1319     }
1320 
1321     // this is used to extract the module name
1322     if (getToolHomeDirectory() != null) {
1323       request.setJdkHome(getToolHomeDirectory());
1324     }
1325 
1326     // resolve project dependencies
1327     try {
1328       return locationManager.resolvePaths(request);
1329     } catch (IOException ex) {
1330       throw new MojoExecutionException(
1331           "Error: Unable to resolve project dependencies", ex);
1332     }
1333 
1334   }
1335 
1336   /**
1337    * Fetch the resolved main module descriptor.
1338    *
1339    * @return main module descriptor or null if it not exists
1340    */
1341   private JavaModuleDescriptor fetchMainModuleDescriptor() {
1342     final JavaModuleDescriptor descriptor =
1343         projectDependencies.getMainModuleDescriptor();
1344     if (descriptor == null) {
1345       // detected that the project is non modular
1346       if (getLog().isWarnEnabled()) {
1347         getLog().warn("The main module descriptor not found");
1348       }
1349     } else {
1350       if (getLog().isDebugEnabled()) {
1351         getLog().debug(MessageFormat.format(
1352             "Found the main module descriptor: [{0}]", descriptor.name()));
1353       }
1354     }
1355     return descriptor;
1356   }
1357 
1358   /**
1359    * Fetch path exceptions for every modulename which resolution failed.
1360    *
1361    * @return pairs of path exception file and cause
1362    */
1363   private Map<File, String> fetchPathExceptions() {
1364     return projectDependencies.getPathExceptions()
1365         .entrySet().stream()
1366         .filter(entry -> entry != null && entry.getKey() != null)
1367         .collect(Collectors.toMap(
1368             entry -> entry.getKey(),
1369             entry -> PluginUtils.getThrowableCause(entry.getValue())
1370         ));
1371   }
1372 
1373   /**
1374    * Fetch classpath elements.
1375    *
1376    * @return classpath elements
1377    */
1378   private List<File> fetchClasspathElements() {
1379     final List<File> result = projectDependencies.getClasspathElements()
1380         .stream()
1381         .filter(Objects::nonNull)
1382         .collect(Collectors.toList());
1383     if (getLog().isDebugEnabled()) {
1384       getLog().debug("Found classpath elements: " + result.size()
1385           + System.lineSeparator()
1386           + result.stream()
1387               .map(file -> file.toString())
1388               .collect(Collectors.joining(System.lineSeparator())));
1389     }
1390     return result;
1391   }
1392 
1393   /**
1394    * Fetch modulepath elements.
1395    *
1396    * @return modulepath elements
1397    */
1398   private List<File> fetchModulepathElements() {
1399     final List<File> result = projectDependencies.getModulepathElements()
1400         .keySet()
1401         .stream()
1402         .filter(Objects::nonNull)
1403         .collect(Collectors.toList());
1404     if (getLog().isDebugEnabled()) {
1405       getLog().debug("Found modulepath elements: " + result.size()
1406           + System.lineSeparator()
1407           + projectDependencies.getModulepathElements().entrySet().stream()
1408               .filter(entry -> entry != null && entry.getKey() != null)
1409               .map(entry -> entry.getKey().toString()
1410                   + (ModuleNameSource.FILENAME.equals(entry.getValue())
1411                       ? System.lineSeparator()
1412                           + "[!] Detected 'requires' filename based "
1413                           + "automatic module"
1414                           + System.lineSeparator()
1415                           + "[!] Please don't publish this project to "
1416                           + "a public artifact repository"
1417                           + System.lineSeparator()
1418                           + (mainModuleDescriptor != null
1419                               && mainModuleDescriptor.exports().isEmpty()
1420                                   ? "[!] APPLICATION"
1421                                   : "[!] LIBRARY")
1422                       : ""))
1423               .collect(Collectors.joining(System.lineSeparator())));
1424     }
1425     return result;
1426   }
1427 
1428   /**
1429    * Get path from the pathelements parameter.
1430    *
1431    * @return path contains parameter elements
1432    */
1433   private String getPathElements() {
1434     String result = null;
1435     if (modulepath != null) {
1436       final List<File> pathelements = modulepath.getPathElements();
1437       if (pathelements != null && !pathelements.isEmpty()) {
1438         result = pathelements.stream()
1439             .filter(Objects::nonNull)
1440             .map(file -> file.toString())
1441             .collect(Collectors.joining(File.pathSeparator));
1442         if (getLog().isDebugEnabled()) {
1443           getLog().debug(PluginUtils.getPathElementsDebugInfo("PATHELEMENTS",
1444               pathelements));
1445           getLog().debug(result);
1446         }
1447       }
1448     }
1449     return result;
1450   }
1451 
1452   /**
1453    * Get filesets from modulepath parameter.
1454    *
1455    * @return path contains filesets
1456    *
1457    * @throws MojoExecutionException if any errors occurred while resolving
1458    *                                a fileset
1459    */
1460   private String getFileSets() throws MojoExecutionException {
1461     String result = null;
1462     if (modulepath != null) {
1463       final List<FileSet> filesets = modulepath.getFileSets();
1464       if (filesets != null && !filesets.isEmpty()) {
1465         for (final FileSet fileSet : filesets) {
1466           final File fileSetDir;
1467           try {
1468             fileSetDir =
1469                 PluginUtils.normalizeFileSetBaseDir(getBaseDir(), fileSet);
1470           } catch (IOException ex) {
1471             throw new MojoExecutionException(
1472                 "Error: Unable to resolve fileset", ex);
1473           }
1474           result = Stream.of(getFileSetManager().getIncludedFiles(fileSet))
1475               .filter(fileName -> !StringUtils.isBlank(fileName))
1476               .map(fileName -> fileSetDir.toPath().resolve(
1477                   StringUtils.stripToEmpty(fileName)).toString())
1478               .collect(Collectors.joining(File.pathSeparator));
1479           if (getLog().isDebugEnabled()) {
1480             getLog().debug(PluginUtils.getFileSetDebugInfo("FILESET",
1481                 fileSet, result));
1482           }
1483         }
1484       }
1485     }
1486     return result;
1487   }
1488 
1489   /**
1490    * Get dirsets from modulepath parameter.
1491    *
1492    * @return path contains dirsets
1493    *
1494    * @throws MojoExecutionException if any errors occurred while resolving
1495    *                                a dirset
1496    */
1497   private String getDirSets() throws MojoExecutionException {
1498     String result = null;
1499     if (modulepath != null) {
1500       final List<FileSet> dirsets = modulepath.getDirSets();
1501       if (dirsets != null && !dirsets.isEmpty()) {
1502         for (final FileSet dirSet : dirsets) {
1503           final File dirSetDir;
1504           try {
1505             dirSetDir =
1506                 PluginUtils.normalizeFileSetBaseDir(getBaseDir(), dirSet);
1507           } catch (IOException ex) {
1508             throw new MojoExecutionException(
1509                 "Error: Unable to resolve dirset", ex);
1510           }
1511           result = Stream.of(getFileSetManager().getIncludedDirectories(dirSet))
1512               .filter(dirName -> !StringUtils.isBlank(dirName))
1513               .map(dirName -> dirSetDir.toPath().resolve(
1514                   StringUtils.stripToEmpty(dirName)).toString())
1515               .collect(Collectors.joining(File.pathSeparator));
1516           if (getLog().isDebugEnabled()) {
1517             getLog().debug(PluginUtils.getFileSetDebugInfo("DIRSET",
1518                 dirSet, result));
1519           }
1520         }
1521       }
1522     }
1523     return result;
1524   }
1525 
1526   /**
1527    * Get dependencysets from modulepath parameter.
1528    *
1529    * @return path contains dependencysets
1530    */
1531   private String getDependencySets() {
1532     String result = null;
1533     if (modulepath != null) {
1534       final List<DependencySet> dependencysets =
1535           modulepath.getDependencySets();
1536       if (dependencysets != null && !dependencysets.isEmpty()) {
1537         for (final DependencySet dependencySet : dependencysets) {
1538           result = getIncludedDependencies(dependencySet)
1539               .stream()
1540               .collect(Collectors.joining(File.pathSeparator));
1541           if (getLog().isDebugEnabled()) {
1542             getLog().debug(PluginUtils.getDependencySetDebugInfo(
1543                 "DEPENDENCYSET", dependencySet, result));
1544           }
1545         }
1546       }
1547     }
1548     return result;
1549   }
1550 
1551   /**
1552    * Get the included project dependencies
1553    * defined in the specified dependencyset.
1554    *
1555    * @param depSet the dependencyset
1556    *
1557    * @return the set of the included project dependencies
1558    */
1559   private Set<String> getIncludedDependencies(final DependencySet depSet) {
1560     return projectDependencies.getPathElements().entrySet().stream()
1561         .filter(entry -> entry != null
1562             && entry.getKey() != null
1563             && filterDependency(depSet, entry.getKey(), entry.getValue()))
1564         .map(entry -> entry.getKey().toString())
1565         .collect(Collectors.toSet());
1566   }
1567 
1568   /**
1569    * Get the excluded project dependencies
1570    * defined in the specified dependencyset.
1571    *
1572    * @param depSet the dependencyset
1573    *
1574    * @return the set of the excluded project dependencies
1575    */
1576   private Set<String> getExcludedDependencies(final DependencySet depSet) {
1577     return projectDependencies.getPathElements().entrySet().stream()
1578         .filter(entry -> entry != null
1579             && entry.getKey() != null
1580             && !filterDependency(depSet, entry.getKey(), entry.getValue()))
1581         .map(entry -> entry.getKey().toString())
1582         .collect(Collectors.toSet());
1583   }
1584 
1585   /**
1586    * Checks whether the dependency defined by the file and
1587    * the module descriptor matches the rules defined in the dependencyset.
1588    * The dependency that matches at least one include pattern will be included,
1589    * but if the dependency matches at least one exclude pattern too,
1590    * then the dependency will not be included.
1591    *
1592    * @param depSet the dependencyset
1593    * @param file the dependency file
1594    * @param descriptor the dependency module descriptor
1595    *
1596    * @return will the dependency be accepted
1597    */
1598   private boolean filterDependency(final DependencySet depSet, final File file,
1599       final JavaModuleDescriptor descriptor) {
1600 
1601     if (descriptor == null) {
1602       if (getLog().isWarnEnabled()) {
1603         getLog().warn("Missing module descriptor: " + file);
1604       }
1605     } else {
1606       if (descriptor.isAutomatic() && getLog().isDebugEnabled()) {
1607         getLog().debug("Found automatic module: " + file);
1608       }
1609     }
1610 
1611     boolean isIncluded = false;
1612 
1613     if (depSet == null) {
1614       // include module by default
1615       isIncluded = true;
1616       // include automatic module by default
1617       if (descriptor != null && descriptor.isAutomatic()
1618           && getLog().isDebugEnabled()) {
1619         getLog().debug("Included automatic module: " + file);
1620       }
1621       // exclude output module by default
1622       if (file.compareTo(getOutputDir()) == 0) {
1623         isIncluded = false;
1624         if (getLog().isDebugEnabled()) {
1625           getLog().debug("Excluded output module: " + file);
1626         }
1627       }
1628     } else {
1629       if (descriptor != null && descriptor.isAutomatic()
1630           && depSet.isAutomaticExcluded()) {
1631         if (getLog().isDebugEnabled()) {
1632           getLog().debug("Excluded automatic module: " + file);
1633         }
1634       } else {
1635         if (file.compareTo(getOutputDir()) == 0) {
1636           if (depSet.isOutputIncluded()) {
1637             isIncluded = true;
1638             if (getLog().isDebugEnabled()) {
1639               getLog().debug("Included output module: " + file);
1640             }
1641           } else {
1642             if (getLog().isDebugEnabled()) {
1643               getLog().debug("Excluded output module: " + file);
1644             }
1645           }
1646         } else {
1647           isIncluded = matchesIncludes(depSet, file, descriptor)
1648               && !matchesExcludes(depSet, file, descriptor);
1649         }
1650       }
1651     }
1652 
1653     if (getLog().isDebugEnabled()) {
1654       getLog().debug(PluginUtils.getDependencyDebugInfo(file, descriptor,
1655           isIncluded));
1656     }
1657 
1658     return isIncluded;
1659   }
1660 
1661   /**
1662    * Checks whether the dependency defined by the file and
1663    * the module descriptor matches the include patterns
1664    * from the dependencyset.
1665    *
1666    * @param depSet the dependencyset
1667    * @param file the file
1668    * @param descriptor the module descriptor
1669    *
1670    * @return should the dependency be included
1671    */
1672   private boolean matchesIncludes(final DependencySet depSet, final File file,
1673       final JavaModuleDescriptor descriptor) {
1674 
1675     final String name = descriptor == null ? "" : descriptor.name();
1676 
1677     final List<String> includes = depSet.getIncludes();
1678     final List<String> includenames = depSet.getIncludeNames();
1679 
1680     boolean result = true;
1681 
1682     if (includenames == null || includenames.isEmpty()) {
1683       if (includes == null || includes.isEmpty()) {
1684         result = true;
1685       } else {
1686         result = pathMatches(includes, file.toPath());
1687       }
1688     } else {
1689       if (includes == null || includes.isEmpty()) {
1690         result = nameMatches(includenames, name);
1691       } else {
1692         result = pathMatches(includes, file.toPath())
1693             || nameMatches(includenames, name);
1694       }
1695     }
1696     return result;
1697   }
1698 
1699   /**
1700    * Checks whether the dependency defined by the file and
1701    * the module descriptor matches the exclude patterns
1702    * from the dependencyset.
1703    *
1704    * @param depSet the dependencyset
1705    * @param file the file
1706    * @param descriptor the module descriptor
1707    *
1708    * @return should the dependency be excluded
1709    */
1710   private boolean matchesExcludes(final DependencySet depSet, final File file,
1711       final JavaModuleDescriptor descriptor) {
1712 
1713     final String name = descriptor == null ? "" : descriptor.name();
1714 
1715     final List<String> excludes = depSet.getExcludes();
1716     final List<String> excludenames = depSet.getExcludeNames();
1717 
1718     boolean result = false;
1719 
1720     if (excludenames == null || excludenames.isEmpty()) {
1721       if (excludes == null || excludes.isEmpty()) {
1722         result = false;
1723       } else {
1724         result = pathMatches(excludes, file.toPath());
1725       }
1726     } else {
1727       if (excludes == null || excludes.isEmpty()) {
1728         result = nameMatches(excludenames, name);
1729       } else {
1730         result = pathMatches(excludes, file.toPath())
1731             || nameMatches(excludenames, name);
1732       }
1733     }
1734     return result;
1735   }
1736 
1737   /**
1738    * Checks if the path matches at least one of the patterns.
1739    * The pattern should be regex or glob, this is determined
1740    * by the prefix specified in the pattern.
1741    *
1742    * @param patterns the list of patterns
1743    * @param path the file path
1744    *
1745    * @return true if the path matches at least one of the patterns or
1746    *              if no patterns are specified
1747    */
1748   private boolean pathMatches(final List<String> patterns, final Path path) {
1749     for (final String pattern : patterns) {
1750       final PathMatcher pathMatcher =
1751           FileSystems.getDefault().getPathMatcher(pattern);
1752       if (pathMatcher.matches(path)) {
1753         return true;
1754       }
1755     }
1756     return false;
1757   }
1758 
1759   /**
1760    * Checks if the name matches at least one of the patterns.
1761    * The pattern should be regex only.
1762    *
1763    * @param patterns the list of patterns
1764    * @param name the name
1765    *
1766    * @return true if the name matches at least one of the patterns or
1767    *              if no patterns are specified
1768    */
1769   private boolean nameMatches(final List<String> patterns, final String name) {
1770     for (final String pattern : patterns) {
1771       final Pattern regexPattern = Pattern.compile(pattern);
1772       final Matcher nameMatcher = regexPattern.matcher(name);
1773       if (nameMatcher.matches()) {
1774         return true;
1775       }
1776     }
1777     return false;
1778   }
1779 
1780   /**
1781    * Process modules.
1782    *
1783    * @param cmdLine the command line builder
1784    *
1785    * @throws MojoExecutionException if any errors occurred
1786    */
1787   private void processModules(final CommandLineBuilder cmdLine)
1788       throws MojoExecutionException {
1789     CommandLineOption opt = null;
1790     // modulepath
1791     if (modulepath != null) {
1792       final StringBuilder path = new StringBuilder();
1793       final String pathElements = getPathElements();
1794       if (!StringUtils.isBlank(pathElements)) {
1795         path.append(StringUtils.stripToEmpty(pathElements));
1796       }
1797       final String fileSets = getFileSets();
1798       if (!StringUtils.isBlank(fileSets)) {
1799         if (path.length() != 0) {
1800           path.append(File.pathSeparator);
1801         }
1802         path.append(StringUtils.stripToEmpty(fileSets));
1803       }
1804       final String dirSets = getDirSets();
1805       if (!StringUtils.isBlank(dirSets)) {
1806         if (path.length() != 0) {
1807           path.append(File.pathSeparator);
1808         }
1809         path.append(StringUtils.stripToEmpty(dirSets));
1810       }
1811       final String dependencySets = getDependencySets();
1812       if (!StringUtils.isBlank(dependencySets)) {
1813         if (path.length() != 0) {
1814           path.append(File.pathSeparator);
1815         }
1816         path.append(StringUtils.stripToEmpty(dependencySets));
1817       }
1818       if (path.length() != 0) {
1819         opt = cmdLine.createOpt();
1820         opt.createArg().setValue("--module-path");
1821         opt.createArg().setValue(path.toString());
1822       }
1823     }
1824     // addmodules
1825     if (addmodules != null && !addmodules.isEmpty()) {
1826       opt = cmdLine.createOpt();
1827       opt.createArg().setValue("--add-modules");
1828       opt.createArg().setValue(
1829           addmodules.stream().collect(Collectors.joining(",")));
1830     }
1831     // bindservices
1832     if (bindservices) {
1833       opt = cmdLine.createOpt();
1834       opt.createArg().setValue("--bind-services");
1835     }
1836   }
1837 
1838   /**
1839    * Execute goal.
1840    *
1841    * @throws MojoExecutionException if any errors occurred
1842    */
1843   @Override
1844   public void execute() throws MojoExecutionException {
1845 
1846     // Init
1847     init(TOOL_NAME, toolhome, TOOL_HOME_BIN); // from BaseToolMojo
1848 
1849     // Check version
1850     final JavaVersion toolJavaVersion = getToolJavaVersion();
1851     if (toolJavaVersion == null
1852         || !toolJavaVersion.atLeast(JavaVersion.JAVA_9)) {
1853       throw new MojoExecutionException(MessageFormat.format(
1854           "Error: At least {0} is required to use [{1}]", JavaVersion.JAVA_9,
1855           TOOL_NAME));
1856     }
1857 
1858     // Delete temporary directory if it exists
1859     if (temp != null) {
1860       if (getLog().isDebugEnabled()) {
1861         getLog().debug(MessageFormat.format(
1862             "Temporary directory: [{0}]", temp));
1863       }
1864       if (temp.exists() && temp.isDirectory()) {
1865         try {
1866           FileUtils.deleteDirectory(temp);
1867         } catch (IOException ex) {
1868           throw new MojoExecutionException(MessageFormat.format(
1869               "Error: Unable to delete temporary directory: [{0}]", temp), ex);
1870         }
1871       }
1872     }
1873 
1874     // Resolve and fetch project dependencies
1875     projectDependencies = resolveDependencies();
1876     mainModuleDescriptor = fetchMainModuleDescriptor();
1877     // final List<File> classpathElements = fetchClasspathElements();
1878     // final List<File> modulepathElements = fetchModulepathElements();
1879     final Map<File, String> pathExceptions = fetchPathExceptions();
1880     if (!pathExceptions.isEmpty() && getLog().isWarnEnabled()) {
1881       getLog().warn("Found path exceptions: " + pathExceptions.size()
1882           + System.lineSeparator()
1883           + pathExceptions.entrySet().stream()
1884               .map(entry -> entry.getKey().toString()
1885                   + System.lineSeparator()
1886                   + entry.getValue())
1887               .collect(Collectors.joining(System.lineSeparator())));
1888     }
1889 
1890     // Build command line and populate the list of the command options
1891     final CommandLineBuilder cmdLineBuilder = new CommandLineBuilder();
1892     cmdLineBuilder.setExecutable(getToolExecutable().toString());
1893     processOptions(cmdLineBuilder);
1894     processModules(cmdLineBuilder);
1895     final List<String> optsLines = new ArrayList<>();
1896     optsLines.add("# " + TOOL_NAME);
1897     optsLines.addAll(cmdLineBuilder.buildOptionList());
1898     if (getLog().isDebugEnabled()) {
1899       getLog().debug(optsLines.stream()
1900           .collect(Collectors.joining(System.lineSeparator(),
1901               System.lineSeparator(), "")));
1902     }
1903 
1904     // Save the list of command options to the file
1905     // will be used in the tool command line
1906     final Path cmdOptsPath = getBuildDir().toPath().resolve(OPTS_FILE);
1907     try {
1908       Files.write(cmdOptsPath, optsLines, getCharset());
1909     } catch (IOException ex) {
1910       throw new MojoExecutionException(MessageFormat.format(
1911           "Error: Unable to write command options to file: [{0}]",
1912           cmdOptsPath), ex);
1913     }
1914     tempFiles.add(cmdOptsPath.toFile());
1915 
1916     // Prepare command line with command options
1917     // specified in the file created early
1918     final Commandline cmdLine = new Commandline();
1919     cmdLine.setExecutable(getToolExecutable().toString());
1920     cmdLine.createArg().setValue("@" + cmdOptsPath.toString());
1921 
1922     // Execute command line
1923     int exitCode = 0;
1924     try {
1925       exitCode = execCmdLine(cmdLine); // from BaseToolMojo
1926     } catch (CommandLineException ex) {
1927       throw new MojoExecutionException(MessageFormat.format(
1928           "Error: Unable to execute [{0}] tool", TOOL_NAME), ex);
1929     }
1930     if (exitCode != 0) {
1931       if (getLog().isErrorEnabled()) {
1932         getLog().error(System.lineSeparator()
1933             + "Command options was: "
1934             + System.lineSeparator()
1935             + optsLines.stream()
1936                 .collect(Collectors.joining(System.lineSeparator())));
1937       }
1938       throw new MojoExecutionException(MessageFormat.format(
1939           "Error: Tool execution failed [{0}] with exit code: {1}", TOOL_NAME,
1940           exitCode));
1941     }
1942 
1943     // Delete temporary files
1944     for (final File tempFile : tempFiles) {
1945       try {
1946         FileUtils.forceDelete(tempFile);
1947       } catch (IOException ex) {
1948         throw new MojoExecutionException(MessageFormat.format(
1949             "Error: Unable to delete temporary file: [{0}]", tempFile), ex);
1950       }
1951     }
1952 
1953   }
1954 
1955 }