| package org.apache.maven.plugins.jlink; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.maven.model.Dependency; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.project.MavenProject; |
| import org.codehaus.plexus.archiver.Archiver; |
| import org.codehaus.plexus.archiver.ArchiverException; |
| import org.codehaus.plexus.archiver.zip.ZipArchiver; |
| import org.codehaus.plexus.util.FileUtils; |
| import org.codehaus.plexus.util.cli.Commandline; |
| |
| /** |
| * The JLink goal is intended to create a Java Run Time Image file based on |
| * {@link http://openjdk.java.net/jeps/282}, {@link http://openjdk.java.net/jeps/220}. |
| * |
| * @author Karl Heinz Marbaise <a href="mailto:khmarbaise@apache.org">khmarbaise@apache.org</a> |
| */ |
| // TODO: Check if the resolution scope is correct? |
| // CHECKSTYLE_OFF: LineLength |
| @Mojo( name = "jlink", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true ) |
| // CHECKSTYLE_ON: LineLength |
| public class JLinkMojo |
| extends AbstractJLinkMojo |
| { |
| private static final String JMOD_PACKAGING = "jmod"; |
| |
| private static final String JMODS = "jmods"; |
| |
| private static final String JAR_PACKAGING = "jar"; |
| |
| /** |
| * This is intended to strip debug information out. The command line equivalent of <code>jlink</code> is: |
| * <code>-G, --strip-debug</code> strip debug information. |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean stripDebug; |
| |
| /** |
| * Here you can define the compression of the resources being used. The command line equivalent is: |
| * <code>-c, --compress=<0|1|2></code>. |
| */ |
| @Parameter |
| private Integer compression; |
| |
| /** |
| * Limit the universe of observable modules. The following gives an example of the configuration which can be used |
| * in the <code>pom.xml</code> file. |
| * |
| * <pre> |
| * <limitModules> |
| * <limitModule>mod1</limitModule> |
| * <limitModule>xyz</limitModule> |
| * . |
| * . |
| * </limitModules> |
| * </pre> |
| * |
| * This configuration is the equivalent of the command line option: |
| * <code>--limit-modules <mod>[,<mod>...]</code> |
| */ |
| @Parameter |
| private List<String> limitModules; |
| |
| /** |
| * By using the --add-modules you can define the root modules to be resolved. The configuration in |
| * <code>pom.xml</code> file can look like this: |
| * |
| * <pre> |
| * <addModules> |
| * <addModule>mod1</addModule> |
| * <addModule>first</addModule> |
| * . |
| * . |
| * </addModules> |
| * </pre> |
| * |
| * The command line equivalent for jlink is: <code>--add-modules <mod>[,<mod>...]</code>. |
| */ |
| @Parameter |
| private List<String> addModules; |
| |
| /** |
| * Define the plugin module path to be used. There can be defined multiple entries separated by either {@code ;} or |
| * {@code :}. The jlink command line equivalent is: <code>--plugin-module-path <modulepath></code> |
| */ |
| @Parameter |
| private String pluginModulePath; |
| |
| /** |
| * The output directory for the resulting Run Time Image. The created Run Time Image is stored in non compressed |
| * form. This will later being packaged into a <code>zip</code> file. <code>--output <path></code> |
| */ |
| // TODO: is this a good final location? |
| @Parameter( defaultValue = "${project.build.directory}/maven-jlink", required = true, readonly = true ) |
| private File outputDirectoryImage; |
| |
| @Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true ) |
| private File outputDirectory; |
| |
| /** |
| * The byte order of the generated Java Run Time image. <code>--endian <little|big></code>. If the endian is |
| * not given the default is: <code>native</code>. |
| */ |
| // TODO: Should we define either little or big as default? or should we left as it. |
| @Parameter |
| private String endian; |
| |
| // TODO: Check if we should allow to extend the modulePaths by manual additions in the pom file? |
| @Parameter( defaultValue = "${project.compileClasspathElements}", readonly = true, required = true ) |
| private List<String> modulePaths; |
| |
| /** |
| * Add the option <code>--bind-services</code> or not. |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean bindServices; |
| |
| /** |
| * You can disable a plugin by using this option. <code>--disable-plugin pluginName</code>. |
| */ |
| @Parameter |
| private String disablePlugin; |
| |
| /** |
| * <code>--ignore-signing-information</code> |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean ignoreSigningInformation; |
| |
| /** |
| * This will suppress to have the <code>includes</code> directory in the resulting Java Run Time Image. The JLink |
| * command line equivalent is: <code>--no-header-files</code> |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean noHeaderFiles; |
| |
| /** |
| * This will suppress to have the <code>man</code> directory in the resulting Java Run Time Image. The JLink command |
| * line equivalent is: <code>--no-man-pages</code> |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean noManPages; |
| |
| /** |
| * Suggest providers that implement the given service types from the module path. |
| * |
| * <pre> |
| * <suggestProviders> |
| * <suggestProvider>name-a</suggestProvider> |
| * <suggestProvider>name-b</suggestProvider> |
| * . |
| * . |
| * </suggestProviders> |
| * </pre> |
| * |
| * The jlink command linke equivalent: <code>--suggest-providers [<name>,...]</code> |
| */ |
| @Parameter |
| private List<String> suggestProviders; |
| |
| /** |
| * This will turn on verbose mode. The jlink command line equivalent is: <code>--verbose</code> |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean verbose; |
| |
| /** |
| * The JAR archiver needed for archiving the environments. |
| */ |
| @Component( role = Archiver.class, hint = "zip" ) |
| private ZipArchiver zipArchiver; |
| |
| /** |
| * Name of the generated ZIP file. |
| */ |
| @Parameter( defaultValue = "${project.build.finalName}", readonly = true ) |
| private String finalName; |
| |
| public void execute() |
| throws MojoExecutionException, MojoFailureException |
| { |
| |
| String jLinkExec = getExecutable(); |
| |
| getLog().info( "Toolchain in maven-jlink-plugin: jlink [ " + jLinkExec + " ]" ); |
| |
| // TODO: Find a more better and cleaner way? |
| File jLinkExecuteable = new File( jLinkExec ); |
| |
| // Really Hacky...do we have a better solution to find the jmods directory of the JDK? |
| File jLinkParent = jLinkExecuteable.getParentFile().getParentFile(); |
| File jmodsFolder = new File( jLinkParent, JMODS ); |
| |
| getLog().debug( " Parent: " + jLinkParent.getAbsolutePath() ); |
| getLog().debug( " jmodsFolder: " + jmodsFolder.getAbsolutePath() ); |
| |
| failIfParametersAreNotInTheirValidValueRanges(); |
| |
| ifOutputDirectoryExistsDelteIt(); |
| |
| List<Dependency> dependencies = getSession().getCurrentProject().getDependencies(); |
| |
| List<MavenProject> modulesToAdd = new ArrayList<>(); |
| // if ( dependencies.isEmpty() ) |
| // { |
| // // Do we need to do something if no dependencies have been defined ? |
| // // WARNING / ERROR or failure? |
| // } |
| getLog().info( "The following dependencies will be linked into the runtime image:" ); |
| for ( Dependency dependency : dependencies ) |
| { |
| // We will support "jmod" as well as "jar" |
| // TODO: Think about jmod's cause they can contain config files etc. ? What todo with them? Are they already |
| // handled by jlink ? |
| if ( JAR_PACKAGING.equals( dependency.getType() ) || JMOD_PACKAGING.equals( dependency.getType() ) ) |
| { |
| MavenProject mp = findDependencyInProjects( dependency ); |
| getLog().info( " -> " + mp.getId() ); |
| // TODO: What about module name != artifactId which has been |
| // defined in module-info.java file! |
| // This would mean to read the module-info information from the jmod/jar file for example to |
| // get the appropriate information. |
| modulesToAdd.add( mp ); |
| } |
| } |
| |
| handleAddModules( modulesToAdd ); |
| |
| handleModulePath( jmodsFolder, modulesToAdd ); |
| |
| Commandline cmd; |
| try |
| { |
| cmd = createJLinkCommandLine(); |
| } |
| catch ( IOException e ) |
| { |
| throw new MojoExecutionException( e.getMessage() ); |
| } |
| cmd.setExecutable( jLinkExec ); |
| |
| executeCommand( cmd, outputDirectoryImage ); |
| |
| File createZipArchiveFromImage = createZipArchiveFromImage( outputDirectory, outputDirectoryImage ); |
| |
| if ( projectHasAlreadySetAnArtifact() ) |
| { |
| throw new MojoExecutionException( "You have to use a classifier " |
| + "to attach supplemental artifacts to the project instead of replacing them." ); |
| } |
| |
| getProject().getArtifact().setFile( createZipArchiveFromImage ); |
| } |
| |
| private String getExecutable() |
| throws MojoFailureException |
| { |
| String jLinkExec; |
| try |
| { |
| jLinkExec = getJLinkExecutable(); |
| } |
| catch ( IOException e ) |
| { |
| throw new MojoFailureException( "Unable to find jlink command: " + e.getMessage(), e ); |
| } |
| return jLinkExec; |
| } |
| |
| private boolean projectHasAlreadySetAnArtifact() |
| { |
| if ( getProject().getArtifact().getFile() != null ) |
| { |
| return getProject().getArtifact().getFile().isFile(); |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| private File createZipArchiveFromImage( File outputDirectory, File outputDirectoryImage ) |
| throws MojoExecutionException |
| { |
| zipArchiver.addDirectory( outputDirectoryImage ); |
| |
| File resultArchive = getArchiveFile( outputDirectory, finalName, null, "zip" ); |
| |
| zipArchiver.setDestFile( resultArchive ); |
| try |
| { |
| zipArchiver.createArchive(); |
| } |
| catch ( ArchiverException e ) |
| { |
| getLog().error( e.getMessage(), e ); |
| throw new MojoExecutionException( e.getMessage(), e ); |
| } |
| catch ( IOException e ) |
| { |
| getLog().error( e.getMessage(), e ); |
| throw new MojoExecutionException( e.getMessage(), e ); |
| } |
| |
| return resultArchive; |
| |
| } |
| |
| private void handleAddModules( List<MavenProject> modulesToAdd ) |
| { |
| if ( addModules == null ) |
| { |
| addModules = new ArrayList<>(); |
| } |
| |
| for ( MavenProject module : modulesToAdd ) |
| { |
| // TODO: Check if this is the correct way? |
| // This implies the artifactId is equal to moduleName which might not always be the case! |
| addModules.add( module.getArtifactId() ); |
| } |
| } |
| |
| /** |
| * @param jmodsFolder The folder where to find the jmods of the JDK. |
| * @param modulesToAdd The modules to be added. |
| */ |
| private void handleModulePath( File jmodsFolder, List<MavenProject> modulesToAdd ) |
| { |
| if ( modulePaths == null ) |
| { |
| modulePaths = new ArrayList<>(); |
| } |
| |
| // The jmods directory of the JDK |
| modulePaths.add( jmodsFolder.getAbsolutePath() ); |
| |
| for ( MavenProject mavenProject : modulesToAdd ) |
| { |
| File output = new File( mavenProject.getBuild().getDirectory(), JMODS ); |
| modulePaths.add( output.getAbsolutePath() ); |
| } |
| } |
| |
| private MavenProject findDependencyInProjects( Dependency dep ) |
| { |
| List<MavenProject> sortedProjects = getSession().getProjectDependencyGraph().getSortedProjects(); |
| MavenProject result = null; |
| for ( MavenProject mavenProject : sortedProjects ) |
| { |
| if ( dep.getGroupId().equals( mavenProject.getGroupId() ) |
| && dep.getArtifactId().equals( mavenProject.getArtifactId() ) |
| && dep.getVersion().equals( mavenProject.getVersion() ) ) |
| { |
| result = mavenProject; |
| } |
| } |
| return result; |
| } |
| |
| private void failIfParametersAreNotInTheirValidValueRanges() |
| throws MojoFailureException |
| { |
| if ( compression != null && ( compression < 0 || compression > 2 ) ) |
| { |
| String message = |
| "The given compression parameters " + compression + " is not in the valid value range from 0..2"; |
| getLog().error( message ); |
| throw new MojoFailureException( message ); |
| } |
| |
| if ( endian != null && ( !"big".equals( endian ) || !"little".equals( endian ) ) ) |
| { |
| String message = |
| "The given endian parameters " + endian + " is not in the valid value either 'little' or 'big'."; |
| getLog().error( message ); |
| throw new MojoFailureException( message ); |
| } |
| |
| // CHECK if this assumption here is correct? |
| // if ( modulePaths != null && ( !modulePaths.isEmpty() ) ) |
| // { |
| // |
| // // FIXME: Need to check if the given paths exists? and if they are |
| // // folders? |
| // // String message = "The given module-paths parameter " + modulePath.getAbsolutePath() |
| // // + " is not a directory or does not exist."; |
| // // getLog().error( message ); |
| // // throw new MojoFailureException( message ); |
| // } |
| } |
| |
| private void ifOutputDirectoryExistsDelteIt() |
| throws MojoExecutionException |
| { |
| if ( outputDirectoryImage.exists() ) |
| { |
| // Delete the output folder of JLink before we start |
| // otherwise JLink will fail with a message "Error: directory already exists: ..." |
| try |
| { |
| getLog().debug( "Deleting existing " + outputDirectoryImage.getAbsolutePath() ); |
| FileUtils.forceDelete( outputDirectoryImage ); |
| } |
| catch ( IOException e ) |
| { |
| getLog().error( "IOException", e ); |
| throw new MojoExecutionException( "Failure during deletion of " + outputDirectoryImage.getAbsolutePath() |
| + " occured." ); |
| } |
| } |
| } |
| |
| private Commandline createJLinkCommandLine() |
| throws IOException |
| { |
| File file = new File( outputDirectoryImage.getParentFile(), "jlinkArgs" ); |
| if ( !getLog().isDebugEnabled() ) |
| { |
| file.deleteOnExit(); |
| } |
| file.getParentFile().mkdirs(); |
| file.createNewFile(); |
| |
| PrintStream argsFile = new PrintStream( file ); |
| |
| if ( stripDebug ) |
| { |
| argsFile.println( "--strip-debug" ); |
| } |
| |
| if ( bindServices ) |
| { |
| argsFile.println( "--bind-services" ); |
| } |
| |
| if ( endian != null ) |
| { |
| argsFile.println( "--bind-services" ); |
| argsFile.println( endian ); |
| } |
| if ( ignoreSigningInformation ) |
| { |
| argsFile.println( "--ignore-signing-information" ); |
| } |
| if ( compression != null ) |
| { |
| argsFile.println( "--compression" ); |
| argsFile.println( compression ); |
| } |
| |
| if ( disablePlugin != null ) |
| { |
| argsFile.println( "--disable-plugin" ); |
| argsFile.append( '"' ).append( disablePlugin ).println( '"' ); |
| |
| } |
| if ( modulePaths != null ) |
| { |
| //@formatter:off |
| argsFile.println( "--module-path" ); |
| argsFile |
| .append( '"' ) |
| .append( getPlatformDependSeparateList( modulePaths ) |
| .replace( "\\", "\\\\" ) |
| ).println( '"' ); |
| //@formatter:off |
| } |
| |
| if ( noHeaderFiles ) |
| { |
| argsFile.println( "--no-header-files" ); |
| } |
| |
| if ( noManPages ) |
| { |
| argsFile.println( "--no-man-pages" ); |
| } |
| |
| if ( hasSuggestProviders() ) |
| { |
| argsFile.println( "--suggest-providers" ); |
| String sb = getCommaSeparatedList( suggestProviders ); |
| argsFile.println( sb ); |
| } |
| |
| if ( hasLimitModules() ) |
| { |
| argsFile.println( "--limit-modules" ); |
| String sb = getCommaSeparatedList( limitModules ); |
| argsFile.println( sb ); |
| } |
| |
| if ( hasModules() ) |
| { |
| argsFile.println( "--add-modules" ); |
| // This must be name of the module and *NOT* the name of the |
| // file! Can we somehow pre check this information to fail early? |
| String sb = getCommaSeparatedList( addModules ); |
| argsFile.append( '"' ).append( sb.replace( "\\", "\\\\" ) ).println( '"' ); |
| } |
| |
| if ( pluginModulePath != null ) |
| { |
| argsFile.println( "--plugin-module-path" ); |
| StringBuilder sb = convertSeparatedModulePathToPlatformSeparatedModulePath( pluginModulePath ); |
| argsFile.append( '"' ).append( sb.toString().replace( "\\", "\\\\" ) ).println( '"' ); |
| } |
| |
| if ( outputDirectory != null ) |
| { |
| argsFile.println( "--output" ); |
| argsFile.println( outputDirectoryImage ); |
| } |
| |
| if ( verbose ) |
| { |
| argsFile.println( "--verbose" ); |
| } |
| argsFile.close(); |
| |
| Commandline cmd = new Commandline(); |
| cmd.createArg().setValue( '@' + file.getAbsolutePath() ); |
| |
| return cmd; |
| } |
| |
| private boolean hasSuggestProviders() |
| { |
| return suggestProviders != null && !suggestProviders.isEmpty(); |
| } |
| |
| private boolean hasLimitModules() |
| { |
| return limitModules != null && !limitModules.isEmpty(); |
| } |
| |
| private boolean hasModules() |
| { |
| return addModules != null && !addModules.isEmpty(); |
| } |
| } |