org.apache.maven.plugins.jmod.JModCreateMojo Maven / Gradle / Ivy
package org.apache.maven.plugins.jmod;
/*
* 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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.maven.artifact.Artifact;
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.apache.maven.shared.utils.StringUtils;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.java.DefaultJavaToolChain;
import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
import org.codehaus.plexus.languages.java.jpms.LocationManager;
import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.cli.Commandline;
/**
* The create
goal is intended to create jmod
files which can be used for later linking via
* maven-jlink-plugin. The jmod
files
* can not be used as usual dependencies on the classpath only in relationship with maven-jlink-plugin
.
*
* @author Karl Heinz Marbaise [email protected]
*/
// CHECKSTYLE_OFF: LineLength
@Mojo( name = "create", requiresDependencyResolution = ResolutionScope.RUNTIME, defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true )
// CHECKSTYLE_ON: LineLength
public class JModCreateMojo
extends AbstractJModMojo
{
private static final String JMODS = "jmods";
private List classpathElements;
private List modulepathElements;
@Parameter( defaultValue = "${project.compileClasspathElements}", readonly = true, required = true )
private List compilePath;
@Component
private LocationManager locationManager;
/**
* Specifies one or more directories containing native commands to be copied. The given directories are relative to
* the current base directory. If no entry is defined the default is src/main/cmds
used.
*
*
* <cmds>
* <cmd>...</cmd>
* <cmd>...</cmd>
* .
* .
* </cmds>
*
*
* All files from those directories will be copied into the resulting directory bin
within the jmod
* file.
*
* JMod
command line equivalent: --cmds <path>
.
*/
@Parameter
private List cmds;
private static final String DEFAULT_CMD_DIRECTORY = "src/main/cmds";
/**
* Specifies one or more directories containing configuration files to be copied. Location of user-editable config
* files. If no configuration is given the src/main/configs
location is used as default. If this
* directory does not exist the whole will be ignored.
*
*
* <configs>
* <config>...</config>
* <config>...</config>
* .
* .
* </configs>
*
*
* All files from those directories will be copied into the resulting directory config
within the jmod
* file.
*
* jmod command line equivalent: --config <path>
.
*/
@Parameter
private List configs;
private static final String DEFAULT_CONFIG_DIRECTORY = "src/main/configs";
/**
* Exclude files matching the pattern list. Each element using one the following forms: <glob-pattern>,
* glob:<glob-pattern> or regex:<regex-pattern>
*
*
* <excludes>
* <exclude>...</exclude>
* <exclude>...</exclude>
* .
* .
* </excludes>
*
*/
@Parameter
private List excludes;
/**
* Define the main class which is recorded in the module-info.class
file.
*/
@Parameter
private String mainClass;
/**
* Specifies one or more directories containing native libraries to be copied (The given directories are relative to
* project base directory). If no configuration is given in <> file the location src/main/libs
* will be used. If the default location does not exist the whole configuration will be ignored.
*
*
* <libs>
* <lib>...</lib>
* <lib>...</lib>
* .
* .
* </libs>
*
*
* All files from those directories will be copied into the resulting directory lib
within the jmod
* file.
*
*/
@Parameter
private List libs;
private static final String DEFAULT_LIB_DIRECTORY = "src/main/libs";
/**
* Define the module version of the jmod file.
*/
@Parameter( defaultValue = "${project.version}" )
private String moduleVersion;
/**
* --do-not-resolve-by-default
Exclude from the default root set of modules
*/
@Parameter( defaultValue = "false" )
private boolean doNotResolveByDefault;
/**
* Define the locations of header files. The default location is src/main/headerfiles
. If the the
* default location does not exist in the current project it will be ignored. The given directories are relative to
* the project base directory. If an entry is defined the definition of all locations is needed.
*
*
* <headerFiles>
* <headerFile>...</headerFile>
* <headerFile>...</headerFile>
* .
* .
* </headerFiles>
*
*
* All files from those directories will be copied into the resulting directory includes
within the
* jmod file.
*
* jmod command line equivalent --header-files <path>
*/
@Parameter
private List headerFiles;
private static final String DEFAULT_HEADER_FILES_DIRECTORY = "src/main/headerfiles";
/**
* Define the locations of man pages. The default location is src/main/manpages
. The given man pages
* locations are relative to the project base directory.
*
*
* <manPages>
* <manPage>...</manPage>
* <manPage>...</manPage>
* .
* .
* </manPages>
*
*
* All files from those directories will be copied into the resulting directory man
within the jmod
* file.
*
* jmod command line equivalent --man-pages <path>
*/
@Parameter
private List manPages;
private static final String DEFAULT_MAN_PAGES_DIRECTORY = "src/main/manpages";
/**
* This is only the name of the jmod file in the target directory.
*/
@Parameter( defaultValue = "${project.artifactId}", required = true, readonly = true )
private String outputFileName;
/**
* Define the location of legal notices. The default location is src/main/legalnotices
. The given man
* pages locations are relative to the project base directory.
*
*
* <legalNotices>
* <legalNotice>...</legalNotice>
* <legalNotice>...</legalNotice>
* .
* .
* </legalNotices>
*
*
* All files from those directories will be copied into the resulting directory legal
within the jmod
* file.
*
* jmod command line equivalent --legal-notices <path>
*/
@Parameter
private List legalNotices;
private static final String DEFAULT_LEGAL_NOTICES_DIRECTORY = "src/main/legalnotices";
/**
* --target-platform <target-platform>
Target platform TODO: Which values are valid?
*/
@Parameter
private String targetPlatform;
/**
* Hint for a tool to issue a warning if the module is resolved. The valid values are:
*
* - deprecated
* - deprecated-for-removal
* - incubating
*
*/
@Parameter
private String warnIfResolved;
@Parameter( defaultValue = "${project.build.outputDirectory}", required = true, readonly = true )
private File targetClassesDirectory;
@Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true )
private File outputDirectory;
// calculated based on jmod(.exe)/../..
private File javaHome;
public void execute()
throws MojoExecutionException, MojoFailureException
{
String jModExecutable;
try
{
jModExecutable = getJModExecutable();
}
catch ( IOException e )
{
throw new MojoFailureException( "Unable to find jmod command: " + e.getMessage(), e );
}
File jModExecuteableFile = new File( jModExecutable );
javaHome = jModExecuteableFile.getParentFile().getParentFile();
File jmodsFolderJDK = new File( javaHome, JMODS );
getLog().debug( "Parent: " + javaHome.getAbsolutePath() );
getLog().debug( "jmodsFolder: " + jmodsFolderJDK.getAbsolutePath() );
preparePaths();
failIfParametersAreNotInTheirValidValueRanges();
getLog().info( "Toolchain in jmod-maven-plugin: jmod [ " + jModExecutable + " ]" );
// We need to put the resulting x.jmod files into jmods folder otherwise is
// seemed to be not working.
// Check why?
File modsFolder = new File( outputDirectory, "jmods" );
File resultingJModFile = new File( modsFolder, outputFileName + ".jmod" );
deleteOutputIfAlreadyExists( resultingJModFile );
// create the jmods folder...
modsFolder.mkdirs();
Commandline cmd;
try
{
cmd = createJModCreateCommandLine( resultingJModFile );
}
catch ( IOException e )
{
throw new MojoExecutionException( e.getMessage() );
}
cmd.setExecutable( jModExecutable );
executeCommand( cmd, outputDirectory );
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( resultingJModFile );
}
private void deleteOutputIfAlreadyExists( File resultingJModFile )
throws MojoFailureException
{
if ( resultingJModFile.exists() && resultingJModFile.isFile() )
{
try
{
getLog().debug( "Deleting the existing " + resultingJModFile.getAbsolutePath() + " file." );
FileUtils.forceDelete( resultingJModFile );
}
catch ( IOException e )
{
String message = "Failure during deleting of file " + resultingJModFile.getAbsolutePath();
getLog().error( message );
throw new MojoFailureException( message );
}
}
}
private void failIfParametersAreNotInTheirValidValueRanges()
throws MojoFailureException
{
if ( warnIfResolved != null )
{
String x = warnIfResolved.toLowerCase().trim();
if ( !"deprecated".equals( x ) && "deprecated-for-removal".equals( x ) && "incubating".equals( x ) )
{
String message = "The parameter warnIfResolved does not contain a valid value. "
+ "Valid values are 'deprecated', 'deprecated-for-removal' or 'incubating'.";
getLog().error( message );
throw new MojoFailureException( message );
}
}
throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( cmds, DEFAULT_CMD_DIRECTORY ),
"cmd" );
throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( configs,
DEFAULT_CONFIG_DIRECTORY ),
"config" );
throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( libs, DEFAULT_LIB_DIRECTORY ),
"lib" );
throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( headerFiles,
DEFAULT_HEADER_FILES_DIRECTORY ),
"headerFile" );
throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( legalNotices,
DEFAULT_LEGAL_NOTICES_DIRECTORY ),
"legalNotice" );
throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( manPages,
DEFAULT_MAN_PAGES_DIRECTORY ),
"manPage" );
}
private void throwExceptionIfNotExistOrNotADirectory( List configurations, String partialMessage )
throws MojoFailureException
{
for ( String configLocation : configurations )
{
File dir = new File( configLocation );
if ( !dir.exists() || !dir.isDirectory() )
{
String message = "The directory " + configLocation + " for " + partialMessage
+ " parameter does not exist " + "or is not a directory. ";
getLog().error( message );
throw new MojoFailureException( message );
}
}
}
private List getCompileClasspathElements( MavenProject project )
{
List list = new ArrayList( project.getArtifacts().size() + 1 );
if ( targetClassesDirectory.exists() )
{
list.add( new File( project.getBuild().getOutputDirectory() ) );
}
for ( Artifact a : project.getArtifacts() )
{
list.add( a.getFile() );
}
return list;
}
private void preparePaths()
{
boolean hasModuleDescriptor = false;
// Assuming that the module-info.java is already compiled by compiler plugin so only
// check if the module-info.class file exists.
File moduleInfo = new File( targetClassesDirectory, "module-info.class" );
if ( moduleInfo.exists() && moduleInfo.isFile() )
{
getLog().debug( "We have found a module-info.class file." );
hasModuleDescriptor = true;
}
Collection dependencyArtifacts = getCompileClasspathElements( getProject() );
if ( hasModuleDescriptor )
{
// For now only allow named modules. Once we can create a graph with ASM we can specify exactly the modules
// and we can detect if auto modules are used. In that case, MavenProject.setFile() should not be used, so
// you cannot depend on this project and so it won't be distributed.
modulepathElements = new ArrayList();
classpathElements = new ArrayList();
ResolvePathsResult resolvePathsResult;
try
{
ResolvePathsRequest request =
ResolvePathsRequest.ofFiles( dependencyArtifacts ).setMainModuleDescriptor( moduleInfo );
Toolchain toolchain = getToolchain();
if ( toolchain != null && toolchain instanceof DefaultJavaToolChain )
{
request.setJdkHome( new File( ( (DefaultJavaToolChain) toolchain ).getJavaHome() ) );
}
resolvePathsResult = locationManager.resolvePaths( request );
JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
for ( Map.Entry entry : resolvePathsResult.getModulepathElements().entrySet() )
{
getLog().debug( "File: " + entry.getKey().getAbsolutePath() + " " + entry.getValue().name() );
if ( ModuleNameSource.FILENAME.equals( entry.getValue() ) )
{
final String message = "Required filename-based automodules detected. "
+ "Please don't publish this project to a public artifact repository!";
if ( moduleDescriptor.exports().isEmpty() )
{
// application
getLog().info( message );
}
else
{
// library
writeBoxedWarning( message );
}
break;
}
}
for ( File file : resolvePathsResult.getClasspathElements() )
{
getLog().debug( "classpathElements: File: " + file.getPath() );
classpathElements.add( file.getPath() );
}
for ( File file : resolvePathsResult.getModulepathElements().keySet() )
{
getLog().debug( "modulepathElements: File: " + file.getPath() );
if ( file.isDirectory() )
{
modulepathElements.add( file.getPath() );
}
else
{
modulepathElements.add( file.getParent() );
}
}
}
catch ( IOException e )
{
getLog().warn( e.getMessage() );
}
}
else
{
modulepathElements = Collections.emptyList();
classpathElements = new ArrayList();
for ( File file : dependencyArtifacts )
{
classpathElements.add( file.getPath() );
}
}
}
private Commandline createJModCreateCommandLine( File resultingJModFile )
throws IOException
{
File file = new File( outputDirectory, "jmodCreateArgs" );
if ( !getLog().isDebugEnabled() )
{
file.deleteOnExit();
}
file.getParentFile().mkdirs();
file.createNewFile();
PrintStream argsFile = new PrintStream( file );
argsFile.println( "create" );
if ( moduleVersion != null )
{
argsFile.println( "--module-version" );
argsFile.println( moduleVersion );
}
List classPaths;
if ( classpathElements != null )
{
classPaths = new ArrayList<>( classpathElements );
}
else
{
classPaths = new ArrayList<>( 1 );
}
if ( targetClassesDirectory.exists() )
{
classPaths.add( targetClassesDirectory.getAbsolutePath() );
}
argsFile.println( "--class-path" );
argsFile .append( '"' )
.append( getPlatformSeparatedList( classPaths ).replace( "\\", "\\\\" ) )
.println( '"' );
if ( excludes != null && !excludes.isEmpty() )
{
argsFile.println( "--exclude" );
String commaSeparatedList = getCommaSeparatedList( excludes );
argsFile.append( '"' ).append( commaSeparatedList.replace( "\\", "\\\\" ) ).println( '"' );
}
List configList = handleConfigurationListWithDefault( configs, DEFAULT_CONFIG_DIRECTORY );
if ( !configList.isEmpty() )
{
argsFile.println( "--config" );
// Should we quote the paths?
argsFile.println( getPlatformSeparatedList( configList ) );
}
if ( StringUtils.isNotBlank( mainClass ) )
{
argsFile.println( "--main-class" );
argsFile.println( mainClass );
}
List cmdsList = handleConfigurationListWithDefault( cmds, DEFAULT_CMD_DIRECTORY );
if ( !cmdsList.isEmpty() )
{
argsFile.println( "--cmds" );
argsFile.println( getPlatformSeparatedList( cmdsList ) );
}
List libsList = handleConfigurationListWithDefault( libs, DEFAULT_LIB_DIRECTORY );
if ( !libsList.isEmpty() )
{
argsFile.println( "--libs" );
argsFile.println( getPlatformSeparatedList( libsList ) );
}
List headerFilesList =
handleConfigurationListWithDefault( headerFiles, DEFAULT_HEADER_FILES_DIRECTORY );
if ( !headerFilesList.isEmpty() )
{
argsFile.println( "--header-files" );
argsFile.println( getPlatformSeparatedList( headerFilesList ) );
}
List legalNoticesList =
handleConfigurationListWithDefault( legalNotices, DEFAULT_LEGAL_NOTICES_DIRECTORY );
if ( !legalNoticesList.isEmpty() )
{
argsFile.println( "--legal-notices" );
argsFile.println( getPlatformSeparatedList( legalNoticesList ) );
}
List manPagesList = handleConfigurationListWithDefault( manPages, DEFAULT_MAN_PAGES_DIRECTORY );
if ( !manPagesList.isEmpty() )
{
argsFile.println( "--man-pages" );
argsFile.println( getPlatformSeparatedList( manPagesList ) );
}
List modulePaths = new ArrayList<>( modulepathElements );
modulePaths.add( new File( javaHome, JMODS ).getAbsolutePath() );
if ( modulePaths != null )
{
//@formatter:off
argsFile.println( "--module-path" );
argsFile
.append( '"' )
.append( getPlatformSeparatedList( modulePaths ).replace( "\\", "\\\\" ) )
.println( '"' );
//@formatter:off
}
if ( targetPlatform != null )
{
argsFile.println( "--target-platform" );
argsFile.println( targetPlatform );
}
if ( warnIfResolved != null )
{
argsFile.println( "--warn-if-resolved" );
argsFile.println( warnIfResolved );
}
if ( doNotResolveByDefault )
{
argsFile.println( "--do-not-resolve-by-default" );
}
argsFile.println( resultingJModFile.getAbsolutePath() );
argsFile.close();
Commandline cmd = new Commandline();
cmd.createArg().setValue( '@' + file.getAbsolutePath() );
return cmd;
}
private boolean isConfigurationDefinedInPOM( List configuration )
{
return configuration != null && !configuration.isEmpty();
}
private List handleConfigurationListWithDefault( List configuration, String defaultLocation )
{
List commands = new ArrayList();
if ( isConfigurationDefinedInPOM( configuration ) )
{
commands.addAll( configuration );
}
else
{
if ( doDefaultsExist( defaultLocation ) )
{
commands.add( defaultLocation );
}
}
commands = resolveAgainstProjectBaseDir( commands );
return commands;
}
private List resolveAgainstProjectBaseDir( List relativeDirectories )
{
List result = new ArrayList<>();
for ( String configLocation : relativeDirectories )
{
File dir = new File( getProject().getBasedir(), configLocation );
result.add( dir.getAbsolutePath() );
}
return result;
}
private boolean doDefaultsExist( String defaultLocation )
{
boolean result = false;
File dir = new File( getProject().getBasedir(), defaultLocation );
if ( dir.exists() && dir.isDirectory() )
{
result = true;
}
return result;
}
private String getPlatformSeparatedList( Collection paths )
{
StringBuilder sb = new StringBuilder();
for ( String module : paths )
{
if ( sb.length() > 0 )
{
sb.append( File.pathSeparatorChar );
}
sb.append( module );
}
return sb.toString();
}
private void writeBoxedWarning( String message )
{
String line = StringUtils.repeat( "*", message.length() + 4 );
getLog().warn( line );
getLog().warn( "* " + MessageUtils.buffer().strong( message ) + " *" );
getLog().warn( line );
}
}