| package org.apache.maven.plugins.dependency.fromDependencies; |
| |
| /* |
| * 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.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.plugin.MojoExecutionException; |
| 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.plugins.dependency.utils.DependencyUtil; |
| import org.apache.maven.project.MavenProjectHelper; |
| import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; |
| import org.apache.maven.shared.transfer.repository.RepositoryManager; |
| import org.codehaus.plexus.util.IOUtil; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * This goal will output a classpath string of dependencies from the local repository to a file or log. |
| * |
| * @author ankostis |
| * @since 2.0-alpha-2 |
| */ |
| // CHECKSTYLE_OFF: LineLength |
| @Mojo( name = "build-classpath", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true ) |
| // CHECKSTYLE_ON: LineLength |
| public class BuildClasspathMojo |
| extends AbstractDependencyFilterMojo |
| implements Comparator<Artifact> |
| { |
| |
| /** |
| * Strip artifact version during copy (only works if prefix is set) |
| */ |
| @Parameter( property = "mdep.stripVersion", defaultValue = "false" ) |
| private boolean stripVersion = false; |
| |
| /** |
| * Strip artifact classifier during copy (only works if prefix is set) |
| */ |
| @Parameter( property = "mdep.stripClassifier", defaultValue = "false" ) |
| private boolean stripClassifier = false; |
| |
| /** |
| * The prefix to prepend on each dependent artifact. If undefined, the paths refer to the actual files store in the |
| * local repository (the stripVersion parameter does nothing then). |
| */ |
| @Parameter( property = "mdep.prefix" ) |
| private String prefix; |
| |
| /** |
| * A property to set to the content of the classpath string. |
| */ |
| @Parameter( property = "mdep.outputProperty" ) |
| private String outputProperty; |
| |
| /** |
| * The file to write the classpath string. If undefined, it just prints the classpath as [INFO]. |
| */ |
| @Parameter( property = "mdep.outputFile" ) |
| private File outputFile; |
| |
| /** |
| * If 'true', it skips the up-to-date-check, and always regenerates the classpath file. |
| */ |
| @Parameter( property = "mdep.regenerateFile", defaultValue = "false" ) |
| private boolean regenerateFile; |
| |
| /** |
| * Override the char used between the paths. This field is initialized to contain the first character of the value |
| * of the system property file.separator. On UNIX systems the value of this field is '/'; on Microsoft Windows |
| * systems it is '\'. The default is File.separator |
| * |
| * @since 2.0 |
| */ |
| @Parameter( property = "mdep.fileSeparator", defaultValue = "" ) |
| private String fileSeparator; |
| |
| /** |
| * Override the char used between path folders. The system-dependent path-separator character. This field is |
| * initialized to contain the first character of the value of the system property path.separator. This character is |
| * used to separate filenames in a sequence of files given as a path list. On UNIX systems, this character is ':'; |
| * on Microsoft Windows systems it is ';'. |
| * |
| * @since 2.0 |
| */ |
| @Parameter( property = "mdep.pathSeparator", defaultValue = "" ) |
| private String pathSeparator; |
| |
| /** |
| * Replace the absolute path to the local repo with this property. This field is ignored it prefix is declared. The |
| * value will be forced to "${M2_REPO}" if no value is provided AND the attach flag is true. |
| * |
| * @since 2.0 |
| */ |
| @Parameter( property = "mdep.localRepoProperty", defaultValue = "" ) |
| private String localRepoProperty; |
| |
| /** |
| * Attach the classpath file to the main artifact so it can be installed and deployed. |
| * |
| * @since 2.0 |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean attach; |
| |
| /** |
| * Write out the classpath in a format compatible with filtering (classpath=xxxxx) |
| * |
| * @since 2.0 |
| */ |
| @Parameter( property = "mdep.outputFilterFile", defaultValue = "false" ) |
| private boolean outputFilterFile; |
| |
| /** |
| * Either append the artifact's baseVersion or uniqueVersion to the filename. Will only be used if |
| * {@link #isStripVersion()} is {@code false}. |
| * |
| * @since 2.6 |
| */ |
| @Parameter( property = "mdep.useBaseVersion", defaultValue = "true" ) |
| private boolean useBaseVersion = true; |
| |
| /** |
| * Maven ProjectHelper |
| */ |
| @Component |
| private MavenProjectHelper projectHelper; |
| |
| @Component |
| private RepositoryManager repositoryManager; |
| |
| /** |
| * Main entry into mojo. Gets the list of dependencies and iterates to create a classpath. |
| * |
| * @throws MojoExecutionException with a message if an error occurs. |
| * @see #getResolvedDependencies(boolean) |
| */ |
| @Override |
| protected void doExecute() |
| throws MojoExecutionException |
| { |
| // initialize the separators. |
| boolean isFileSepSet = StringUtils.isNotEmpty( fileSeparator ); |
| boolean isPathSepSet = StringUtils.isNotEmpty( pathSeparator ); |
| |
| // don't allow them to have absolute paths when they attach. |
| if ( attach && StringUtils.isEmpty( localRepoProperty ) ) |
| { |
| localRepoProperty = "${M2_REPO}"; |
| } |
| |
| Set<Artifact> artifacts = getResolvedDependencies( true ); |
| |
| if ( artifacts == null || artifacts.isEmpty() ) |
| { |
| getLog().info( "No dependencies found." ); |
| } |
| |
| List<Artifact> artList = new ArrayList<>( artifacts ); |
| |
| StringBuilder sb = new StringBuilder(); |
| Iterator<Artifact> i = artList.iterator(); |
| |
| if ( i.hasNext() ) |
| { |
| appendArtifactPath( i.next(), sb ); |
| |
| while ( i.hasNext() ) |
| { |
| sb.append( isPathSepSet ? this.pathSeparator : File.pathSeparator ); |
| appendArtifactPath( i.next(), sb ); |
| } |
| } |
| |
| String cpString = sb.toString(); |
| |
| // if file separator is set, I need to replace the default one from all |
| // the file paths that were pulled from the artifacts |
| if ( isFileSepSet ) |
| { |
| // Escape file separators to be used as literal strings |
| final String pattern = Pattern.quote( File.separator ); |
| final String replacement = Matcher.quoteReplacement( fileSeparator ); |
| cpString = cpString.replaceAll( pattern, replacement ); |
| } |
| |
| // make the string valid for filtering |
| if ( outputFilterFile ) |
| { |
| cpString = "classpath=" + cpString; |
| } |
| |
| if ( outputProperty != null ) |
| { |
| getProject().getProperties().setProperty( outputProperty, cpString ); |
| if ( getLog().isDebugEnabled() ) |
| { |
| getLog().debug( outputProperty + " = " + cpString ); |
| } |
| } |
| |
| if ( outputFile == null ) |
| { |
| getLog().info( "Dependencies classpath:" + System.lineSeparator() + cpString ); |
| } |
| else |
| { |
| if ( regenerateFile || !isUpdToDate( cpString ) ) |
| { |
| storeClasspathFile( cpString, outputFile ); |
| } |
| else |
| { |
| this.getLog().info( "Skipped writing classpath file '" + outputFile + "'. No changes found." ); |
| } |
| } |
| if ( attach ) |
| { |
| attachFile( cpString ); |
| } |
| } |
| |
| /** |
| * @param cpString The classpath. |
| * @throws MojoExecutionException in case of an error. |
| */ |
| protected void attachFile( String cpString ) |
| throws MojoExecutionException |
| { |
| File attachedFile = new File( getProject().getBuild().getDirectory(), "classpath" ); |
| storeClasspathFile( cpString, attachedFile ); |
| |
| projectHelper.attachArtifact( getProject(), attachedFile, "classpath" ); |
| } |
| |
| /** |
| * Appends the artifact path into the specified StringBuilder. |
| * |
| * @param art {@link Artifact} |
| * @param sb {@link StringBuilder} |
| */ |
| protected void appendArtifactPath( Artifact art, StringBuilder sb ) |
| { |
| if ( prefix == null ) |
| { |
| String file = art.getFile().getPath(); |
| // substitute the property for the local repo path to make the classpath file portable. |
| if ( StringUtils.isNotEmpty( localRepoProperty ) ) |
| { |
| File localBasedir = repositoryManager.getLocalRepositoryBasedir( session.getProjectBuildingRequest() ); |
| |
| file = StringUtils.replace( file, localBasedir.getAbsolutePath(), localRepoProperty ); |
| } |
| sb.append( file ); |
| } |
| else |
| { |
| // TODO: add param for prepending groupId and version. |
| sb.append( prefix ); |
| sb.append( File.separator ); |
| sb.append( DependencyUtil.getFormattedFileName( art, this.stripVersion, this.prependGroupId, |
| this.useBaseVersion, this.stripClassifier ) ); |
| } |
| } |
| |
| /** |
| * Checks that new classpath differs from that found inside the old classpathFile. |
| * |
| * @param cpString |
| * @return true if the specified classpath equals to that found inside the file, false otherwise (including when |
| * file does not exists but new classpath does). |
| */ |
| private boolean isUpdToDate( String cpString ) |
| { |
| try |
| { |
| String oldCp = readClasspathFile(); |
| return ( cpString == null ? oldCp == null : cpString.equals( oldCp ) ); |
| } |
| catch ( Exception ex ) |
| { |
| this.getLog().warn( "Error while reading old classpath file '" + outputFile + "' for up-to-date check: " |
| + ex ); |
| |
| return false; |
| } |
| } |
| |
| /** |
| * It stores the specified string into that file. |
| * |
| * @param cpString the string to be written into the file. |
| * @throws MojoExecutionException |
| */ |
| private void storeClasspathFile( String cpString, File out ) |
| throws MojoExecutionException |
| { |
| // make sure the parent path exists. |
| out.getParentFile().mkdirs(); |
| |
| Writer w = null; |
| try |
| { |
| w = new BufferedWriter( new FileWriter( out ) ); |
| w.write( cpString ); |
| w.close(); |
| w = null; |
| getLog().info( "Wrote classpath file '" + out + "'." ); |
| } |
| catch ( IOException ex ) |
| { |
| throw new MojoExecutionException( "Error while writing to classpath file '" + out + "': " + ex.toString(), |
| ex ); |
| } |
| finally |
| { |
| IOUtil.close( w ); |
| } |
| } |
| |
| /** |
| * Reads into a string the file specified by the mojo param 'outputFile'. Assumes, the instance variable |
| * 'outputFile' is not null. |
| * |
| * @return the string contained in the classpathFile, if exists, or null otherwise. |
| * @throws IOException in case of an error. |
| */ |
| protected String readClasspathFile() |
| throws IOException |
| { |
| if ( outputFile == null ) |
| { |
| throw new IllegalArgumentException( "The outputFile parameter " |
| + "cannot be null if the file is intended to be read." ); |
| } |
| |
| if ( !outputFile.isFile() ) |
| { |
| return null; |
| } |
| StringBuilder sb = new StringBuilder(); |
| BufferedReader r = null; |
| |
| try |
| { |
| r = new BufferedReader( new FileReader( outputFile ) ); |
| |
| for ( String line = r.readLine(); line != null; line = r.readLine() ) |
| { |
| sb.append( line ); |
| } |
| |
| r.close(); |
| r = null; |
| |
| return sb.toString(); |
| } |
| finally |
| { |
| IOUtil.close( r ); |
| } |
| } |
| |
| /** |
| * Compares artifacts lexicographically, using pattern [group_id][artifact_id][version]. |
| * |
| * @param art1 first object |
| * @param art2 second object |
| * @return the value <code>0</code> if the argument string is equal to this string; a value less than <code>0</code> |
| * if this string is lexicographically less than the string argument; and a value greater than |
| * <code>0</code> if this string is lexicographically greater than the string argument. |
| */ |
| @Override |
| public int compare( Artifact art1, Artifact art2 ) |
| { |
| if ( art1 == art2 ) |
| { |
| return 0; |
| } |
| else if ( art1 == null ) |
| { |
| return -1; |
| } |
| else if ( art2 == null ) |
| { |
| return +1; |
| } |
| |
| String s1 = art1.getGroupId() + art1.getArtifactId() + art1.getVersion(); |
| String s2 = art2.getGroupId() + art2.getArtifactId() + art2.getVersion(); |
| |
| return s1.compareTo( s2 ); |
| } |
| |
| @Override |
| protected ArtifactsFilter getMarkedArtifactFilter() |
| { |
| return null; |
| } |
| |
| /** |
| * @param outputFile the outputFile to set |
| */ |
| public void setOutputFile( File outputFile ) |
| { |
| this.outputFile = outputFile; |
| } |
| |
| /** |
| * @param theOutputProperty the outputProperty to set |
| */ |
| public void setOutputProperty( String theOutputProperty ) |
| { |
| this.outputProperty = theOutputProperty; |
| } |
| |
| /** |
| * @param theFileSeparator the fileSeparator to set |
| */ |
| public void setFileSeparator( String theFileSeparator ) |
| { |
| this.fileSeparator = theFileSeparator; |
| } |
| |
| /** |
| * @param thePathSeparator the pathSeparator to set |
| */ |
| public void setPathSeparator( String thePathSeparator ) |
| { |
| this.pathSeparator = thePathSeparator; |
| } |
| |
| /** |
| * @param thePrefix the prefix to set |
| */ |
| public void setPrefix( String thePrefix ) |
| { |
| this.prefix = thePrefix; |
| } |
| |
| /** |
| * @param theRegenerateFile the regenerateFile to set |
| */ |
| public void setRegenerateFile( boolean theRegenerateFile ) |
| { |
| this.regenerateFile = theRegenerateFile; |
| } |
| |
| /** |
| * @return the stripVersion |
| */ |
| public boolean isStripVersion() |
| { |
| return this.stripVersion; |
| } |
| |
| /** |
| * @param theStripVersion the stripVersion to set |
| */ |
| public void setStripVersion( boolean theStripVersion ) |
| { |
| this.stripVersion = theStripVersion; |
| } |
| |
| /** |
| * @param localRepoProperty {@link #localRepoProperty} |
| */ |
| public void setLocalRepoProperty( String localRepoProperty ) |
| { |
| this.localRepoProperty = localRepoProperty; |
| } |
| } |