package org.apache.maven.plugin.nar;
/*
* 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 static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.TERMINATE;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemLoopException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.cli.Commandline;
/**
* @author Mark Donszelmann
*/
public final class NarUtil
{
private NarUtil()
{
// never instantiate
}
public static String getOS( String defaultOs )
{
String os = defaultOs;
// adjust OS if not given
if ( os == null )
{
os = System.getProperty( "os.name" );
String name = os.toLowerCase();
if ( name.startsWith( "windows" ) )
{
os = OS.WINDOWS;
}
if ( name.startsWith( "linux" ) )
{
os = OS.LINUX;
}
if ( name.equals( "mac os x" ) )
{
os = OS.MACOSX;
}
}
return os;
}
public static String getArchitecture( String architecture )
{
return architecture;
}
public static Linker getLinker( Linker linker )
{
Linker link = linker;
if ( link == null )
{
link = new Linker();
}
return link;
}
public static String getLinkerName(MavenProject project, String architecture, String os, Linker linker )
throws MojoFailureException, MojoExecutionException
{
return getLinker( linker ).getName( NarProperties.getInstance(project), getArchitecture( architecture ) + "." + getOS( os ) + "." );
}
public static AOL getAOL(MavenProject project, String architecture, String os, Linker linker, String aol )
throws MojoFailureException, MojoExecutionException
{
// adjust aol
return aol == null ? new AOL( getArchitecture( architecture ), getOS( os ), getLinkerName( project, architecture, os,
linker ) )
: new AOL( aol );
}
// FIXME, should go to AOL.
/* NOT USED ?
public static String getAOLKey( String architecture, String os, Linker linker )
throws MojoFailureException, MojoExecutionException
{
// construct AOL key prefix
return getArchitecture( architecture ) + "." + getOS( os ) + "." + getLinkerName( architecture, os, linker )
+ ".";
}
*/
public static String getAOLKey( String aol )
{
// FIXME, this may not always work correctly
return replace( "-", ".", aol );
}
public static File getJavaHome( File javaHome, String os )
{
File home = javaHome;
// adjust JavaHome
if ( home == null )
{
home = new File( System.getProperty( "java.home" ) );
if ( !getOS( os ).equals( OS.MACOSX ) )
{
home = new File( home, ".." );
}
}
return home;
}
public static void makeExecutable( File file, final Log log )
throws MojoExecutionException, MojoFailureException
{
if ( !file.exists() )
{
return;
}
if ( file.isDirectory() )
{
File[] files = file.listFiles();
for ( int i = 0; i < files.length; i++ )
{
makeExecutable( files[i], log );
}
}
if ( file.isFile() && file.canRead() && file.canWrite() && !file.isHidden() )
{
// chmod +x file
int result = runCommand( "chmod", new String[] { "+x", file.getPath() }, null, null, log );
if ( result != 0 )
{
throw new MojoExecutionException( "Failed to execute 'chmod +x " + file.getPath() + "'"
+ " return code: \'" + result + "\'." );
}
}
}
public static void runRanlib( File file, final Log log )
throws MojoExecutionException, MojoFailureException
{
if ( !file.exists() )
{
return;
}
if ( file.isDirectory() )
{
File[] files = file.listFiles();
for ( int i = 0; i < files.length; i++ )
{
runRanlib( files[i], log );
}
}
if ( file.isFile() && file.canWrite() && !file.isHidden() && file.getName().endsWith( ".a" ) )
{
// ranlib file
int result = runCommand( "ranlib", new String[] { file.getPath() }, null, null, log );
if ( result != 0 )
{
throw new MojoExecutionException( "Failed to execute 'ranlib " + file.getPath() + "'"
+ " return code: \'" + result + "\'." );
}
}
}
static void runInstallNameTool( File[] files, final Log log )
throws MojoExecutionException, MojoFailureException
{
Set libs = findInstallNameToolCandidates( files, log );
for ( Iterator i = libs.iterator(); i.hasNext(); )
{
File subjectFile = ( File )i.next();
String subjectName = subjectFile.getName();
String subjectPath = subjectFile.getPath();
int idResult = runCommand(
"install_name_tool",
new String[] { "-id", subjectPath, subjectPath },
null, null, log );
if ( idResult != 0 )
{
throw new MojoExecutionException(
"Failed to execute 'install_name_tool -id "
+ subjectPath
+ " "
+ subjectPath
+ "'"
+ " return code: \'"
+ idResult
+ "\'." );
}
for ( Iterator j = libs.iterator(); j.hasNext(); )
{
File dependentFile = ( File )j.next();
String dependentPath = dependentFile.getPath();
if (dependentPath == subjectPath) continue;
int changeResult = runCommand(
"install_name_tool",
new String[] { "-change", subjectName, subjectPath, dependentPath },
null, null, log );
if ( changeResult != 0 )
{
throw new MojoExecutionException(
"Failed to execute 'install_name_tool -change "
+ subjectName
+ " "
+ subjectPath
+ " "
+ dependentPath
+ "'"
+ " return code: \'"
+ changeResult
+ "\'." );
}
}
}
}
static Set findInstallNameToolCandidates( File[] files, final Log log )
throws MojoExecutionException, MojoFailureException
{
HashSet candidates = new HashSet();
for (int i = 0; i < files.length; i++)
{
File file = files[i];
if ( !file.exists() )
{
continue;
}
if ( file.isDirectory() )
{
candidates.addAll(findInstallNameToolCandidates( file.listFiles(), log ));
}
String fileName = file.getName();
if ( file.isFile() && file.canWrite()
&& ( fileName.endsWith( ".so" ) || fileName.endsWith( ".dylib" ) || (fileName.endsWith( ".jnilib" ))))
{
candidates.add(file);
}
}
return candidates;
}
public static void makeLink( File file, final Log log )
throws MojoExecutionException, MojoFailureException
{
if ( !file.exists() )
{
return;
}
if ( file.isDirectory() )
{
File[] files = file.listFiles();
for ( int i = 0; i < files.length; i++ )
{
makeLink( files[i], log );
}
}
if ( file.isFile() && file.canRead() && file.canWrite() && !file.isHidden()
&& file.getName().matches( ".*\\.so(\\.\\d+)+$" ) )
{
File sofile =
new File( file.getParent(), file.getName().substring( 0, file.getName().indexOf( ".so" ) + 3 ) );
if ( !sofile.exists() )
{
// ln -s lib.so.xx lib.so
int result =
runCommand( "ln", new String[] { "-s", file.getName(), sofile.getPath() }, null, null, log );
if ( result != 0 )
{
throw new MojoExecutionException( "Failed to execute 'ln -s " + file.getName() + " "
+ sofile.getPath() + "'" + " return code: \'" + result + "\'." );
}
}
}
}
/**
* Returns the Bcel Class corresponding to the given class filename
*
* @param filename the absolute file name of the class
* @return the Bcel Class.
* @throws IOException
*/
public static JavaClass getBcelClass( String filename )
throws IOException
{
ClassParser parser = new ClassParser( filename );
return parser.parse();
}
/**
* Returns the header file name (javah) corresponding to the given class file name
*
* @param filename the absolute file name of the class
* @return the header file name.
*/
public static String getHeaderName( String basename, String filename )
{
String base = basename.replaceAll( "\\\\", "/" );
String file = filename.replaceAll( "\\\\", "/" );
if ( !file.startsWith( base ) )
{
throw new IllegalArgumentException( "Error " + file + " does not start with " + base );
}
String header = file.substring( base.length() + 1 );
header = header.replaceAll( "/", "_" );
header = header.replaceAll( "\\.class", ".h" );
return header;
}
/**
* Replaces target with replacement in string. For jdk 1.4 compatiblity.
*
* @param target
* @param replacement
* @param string
* @return
*/
public static String replace( CharSequence target, CharSequence replacement, String string )
{
return Pattern.compile( quote( target.toString() )/*
* , Pattern.LITERAL jdk 1.4
*/).matcher( string ).replaceAll(
/* Matcher. jdk 1.4 */quoteReplacement( replacement.toString() ) );
}
/* for jdk 1.4 */
private static String quote( String s )
{
final String escQ = "\\Q";
final String escE = "\\E";
int slashEIndex = s.indexOf( escE );
if ( slashEIndex == -1 )
{
return escQ + s + escE;
}
StringBuffer sb = new StringBuffer( s.length() * 2 );
sb.append( escQ );
slashEIndex = 0;
int current = 0;
while ( ( slashEIndex = s.indexOf( escE, current ) ) != -1 )
{
sb.append( s.substring( current, slashEIndex ) );
current = slashEIndex + 2;
sb.append( escE );
sb.append( "\\" );
sb.append( escE );
sb.append( escQ );
}
sb.append( s.substring( current, s.length() ) );
sb.append( escE );
return sb.toString();
}
/* for jdk 1.4 */
private static String quoteReplacement( String s )
{
if ( ( s.indexOf( '\\' ) == -1 ) && ( s.indexOf( '$' ) == -1 ) )
{
return s;
}
StringBuffer sb = new StringBuffer();
for ( int i = 0; i < s.length(); i++ )
{
char c = s.charAt( i );
if ( c == '\\' )
{
sb.append( '\\' );
sb.append( '\\' );
}
else if ( c == '$' )
{
sb.append( '\\' );
sb.append( '$' );
}
else
{
sb.append( c );
}
}
return sb.toString();
}
public static final String DEFAULT_EXCLUDES =
"**/*~,**/#*#,**/.#*,**/%*%,**/._*," + "**/CVS,**/CVS/**,**/.cvsignore," + "**/SCCS,**/SCCS/**,**/vssver.scc,"
+ "**/.svn,**/.svn/**,**/.DS_Store," + "**/.git,**/.git/**,**.gitignore," + "**/.hg,**/.hg/**,**.hgignore";
public static int copyDirectoryStructure( File sourceDirectory, File destinationDirectory, String includes,
String excludes, final Log log )
throws IOException
{
if ( !sourceDirectory.exists() )
{
throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." );
}
List files = FileUtils.getFiles( sourceDirectory, includes, excludes );
String sourcePath = sourceDirectory.getAbsolutePath();
int copied = 0;
for ( Iterator i = files.iterator(); i.hasNext(); )
{
File file = (File) i.next();
Path pathFile = file.toPath();
String dest = file.getAbsolutePath();
dest = dest.substring( sourcePath.length() + 1 );
File destination = new File( destinationDirectory, dest );
if ( Files.isSymbolicLink( pathFile ) )
{
// create destination symbolic link
// does destination directory exist ?
{
final File parent = destination.getParentFile();
if ( parent != null && !parent.exists() )
{
parent.mkdirs();
}
}
Path linkTarget = Files.readSymbolicLink( pathFile );
log.debug( "Create symbolic link: " + destination + ", target=" + linkTarget );
if ( destination.exists() ) destination.delete();
Files.createSymbolicLink( destination.toPath(), linkTarget );
}
else if ( file.isFile() )
{
// destination = destination.getParentFile();
log.debug( "Copy file " + pathFile + " to " + destination );
FileUtils.copyFile( file, destination );
copied++;
// copy executable bit
try
{
// 1.6 only so coded using introspection
// destination.setExecutable( file.canExecute(), false );
Method canExecute = file.getClass().getDeclaredMethod( "canExecute", new Class[] {} );
Method setExecutable =
destination.getClass().getDeclaredMethod( "setExecutable",
new Class[] { boolean.class, boolean.class } );
setExecutable.invoke( destination, new Object[] {
(Boolean) canExecute.invoke( file, new Object[] {} ), Boolean.FALSE } );
}
catch ( SecurityException e )
{
// ignored
}
catch ( NoSuchMethodException e )
{
// ignored
}
catch ( IllegalArgumentException e )
{
// ignored
}
catch ( IllegalAccessException e )
{
// ignored
}
catch ( InvocationTargetException e )
{
// ignored
}
}
else if ( file.isDirectory() )
{
if ( !destination.exists() && !destination.mkdirs() )
{
throw new IOException( "Could not create destination directory '" + destination.getAbsolutePath()
+ "'." );
}
copied += copyDirectoryStructure( file, destination, includes, excludes, log );
}
else
{
throw new IOException( "Unknown file type: " + file.getAbsolutePath() );
}
}
return copied;
}
public static String getEnv( String envKey, String alternateSystemProperty, String defaultValue )
{
String envValue = null;
try
{
envValue = System.getenv( envKey );
if ( envValue == null && alternateSystemProperty != null )
{
envValue = System.getProperty( alternateSystemProperty );
}
}
catch ( Error e )
{
// JDK 1.4?
if ( alternateSystemProperty != null )
{
envValue = System.getProperty( alternateSystemProperty );
}
}
if ( envValue == null )
{
envValue = defaultValue;
}
return envValue;
}
public static String addLibraryPathToEnv( String path, Map environment, String os )
{
String pathName = null;
char separator = ' ';
if ( os.equals( OS.WINDOWS ) )
{
pathName = "Path";
separator = ';';
}
else if ( os.equals( OS.MACOSX ) )
{
pathName = "DYLD_LIBRARY_PATH";
separator = ':';
}
else
{
pathName = "LD_LIBRARY_PATH";
separator = ':';
}
String value = environment != null ? (String) environment.get( pathName ) : null;
if ( value == null )
{
value = NarUtil.getEnv( pathName, pathName, null );
}
String libPath = path;
libPath = libPath.replace( File.pathSeparatorChar, separator );
if ( value != null )
{
value += separator + libPath;
}
else
{
value = libPath;
}
if ( environment != null )
{
environment.put( pathName, value );
}
return pathName + "=" + value;
}
public static int runCommand( String cmd, String[] args, File workingDirectory, String[] env, final Log log )
throws MojoExecutionException, MojoFailureException
{
return runCommand( cmd, args, workingDirectory, env, new TextStream()
{
public void println( String text )
{
log.info( text );
}
}, new TextStream()
{
public void println( String text )
{
log.error( text );
}
}, new TextStream()
{
public void println( String text )
{
log.debug( text );
}
} );
}
public static int runCommand( String cmd, String[] args, File workingDirectory, String[] env, TextStream out,
TextStream err, TextStream dbg )
throws MojoExecutionException, MojoFailureException
{
Commandline cmdLine = new Commandline();
try
{
dbg.println( "RunCommand: " + cmd );
cmdLine.setExecutable( cmd );
if ( args != null )
{
for ( int i = 0; i < args.length; i++ )
{
dbg.println( " '" + args[i] + "'" );
}
cmdLine.addArguments( args );
}
if ( workingDirectory != null )
{
dbg.println( "in: " + workingDirectory.getPath() );
cmdLine.setWorkingDirectory( workingDirectory );
}
if ( env != null )
{
dbg.println( "with Env:" );
for ( int i = 0; i < env.length; i++ )
{
String[] nameValue = env[i].split( "=", 2 );
if ( nameValue.length < 2 )
{
throw new MojoFailureException( " Misformed env: '" + env[i] + "'" );
}
dbg.println( " '" + nameValue[0] + "=" + nameValue[1] + "'" );
cmdLine.addEnvironment( nameValue[0], nameValue[1] );
}
}
Process process = cmdLine.execute();
StreamGobbler errorGobbler = new StreamGobbler( process.getErrorStream(), err );
StreamGobbler outputGobbler = new StreamGobbler( process.getInputStream(), out );
errorGobbler.start();
outputGobbler.start();
process.waitFor();
dbg.println( "ExitValue: " + process.exitValue() );
final int timeout = 5000;
errorGobbler.join( timeout );
outputGobbler.join( timeout );
return process.exitValue();
}
catch ( Exception e )
{
throw new MojoExecutionException( "Could not launch " + cmdLine, e );
}
}
private static final class StreamGobbler
extends Thread
{
private InputStream is;
private TextStream ts;
private StreamGobbler( InputStream is, TextStream ts )
{
this.is = is;
this.ts = ts;
}
public void run()
{
try
{
BufferedReader reader = new BufferedReader( new InputStreamReader( is ) );
String line = null;
while ( ( line = reader.readLine() ) != null )
{
ts.println( line );
}
reader.close();
}
catch ( IOException e )
{
// e.printStackTrace()
StackTraceElement[] stackTrace = e.getStackTrace();
for ( int i = 0; i < stackTrace.length; i++ )
{
ts.println( stackTrace[i].toString() );
}
}
}
}
/**
* (Darren) this code lifted from mvn help:active-profiles plugin Recurses
* into the project's parent poms to find the active profiles of the
* specified project and all its parents.
*
* @param project
* The project to start with
* @return A list of active profiles
*/
static List collectActiveProfiles(MavenProject project) {
List profiles = project.getActiveProfiles();
if (project.hasParent()) {
profiles.addAll(collectActiveProfiles(project.getParent()));
}
return profiles;
}
/**
* A {@code FileVisitor} that copies a file-tree ("cp -r"). Java 7 specific, needed to re-create symb links instead
* of creating files.
* @author Oracle Java Tutorial
* @author Oodrive
*/
static class TreeCopier implements FileVisitor<Path> {
private final Path source;
private final Path target;
private final Log log;
TreeCopier(Path source, Path target, Log log) {
this.source = source;
this.target = target;
this.log = log;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// Before visiting entries in a directory, we create the directory if necessary
CopyOption[] options = new CopyOption[] { COPY_ATTRIBUTES };
try {
Path newdir = target.resolve(source.relativize(dir));
Files.copy(dir, newdir, options);
}
catch (FileAlreadyExistsException x) {
// Ignored
}
return CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
CopyOption[] options = new CopyOption[] { COPY_ATTRIBUTES, REPLACE_EXISTING, NOFOLLOW_LINKS };
Files.copy(file, target.resolve(source.relativize(file)), options);
return CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
// Fix up modification time of directory when done
if (exc == null) {
Path newdir = target.resolve(source.relativize(dir));
FileTime time = Files.getLastModifiedTime(dir);
Files.setLastModifiedTime(newdir, time);
}
return CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
if (exc instanceof FileSystemLoopException) {
log.error("Cycle detected: " + file);
}
else {
log.error("Unable to copy: " + file, exc);
}
return TERMINATE;
}
}
}