View Javadoc
1   package org.apache.maven.plugins.wrapper;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
23  
24  import javax.inject.Inject;
25  
26  import java.io.BufferedWriter;
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.nio.charset.StandardCharsets;
31  import java.nio.file.DirectoryStream;
32  import java.nio.file.Files;
33  import java.nio.file.Path;
34  import java.util.Locale;
35  import java.util.Map;
36  import java.util.Properties;
37  
38  import org.apache.maven.Maven;
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
41  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
42  import org.apache.maven.execution.MavenExecutionRequest;
43  import org.apache.maven.execution.MavenSession;
44  import org.apache.maven.plugin.AbstractMojo;
45  import org.apache.maven.plugin.MojoExecutionException;
46  import org.apache.maven.plugin.MojoFailureException;
47  import org.apache.maven.plugins.annotations.Mojo;
48  import org.apache.maven.plugins.annotations.Parameter;
49  import org.apache.maven.repository.RepositorySystem;
50  import org.apache.maven.settings.Mirror;
51  import org.apache.maven.settings.Settings;
52  import org.codehaus.plexus.archiver.UnArchiver;
53  import org.codehaus.plexus.components.io.fileselectors.FileInfo;
54  import org.codehaus.plexus.components.io.fileselectors.FileSelector;
55  
56  /**
57   * Unpacks the maven-wrapper distribution files to the current project source tree.
58   *
59   * @author Robert Scholte
60   * @since 3.0.0
61   */
62  @Mojo( name = "wrapper", aggregator = true, requiresDirectInvocation = true )
63  public class WrapperMojo
64      extends AbstractMojo
65  {
66      private static final String MVNW_REPOURL = "MVNW_REPOURL";
67  
68      private static final String DEFAULT_REPOURL = "https://repo.maven.apache.org/maven2";
69  
70      // CONFIGURATION PARAMETERS
71  
72      /**
73       * The version of Maven to require, default value is the Runtime version of Maven.
74       * Can be any valid release above 2.0.9
75       */
76      @Parameter( property = "maven" )
77      private String mavenVersion;
78  
79      /**
80       * Options are:
81       * <dl>
82       *   <dt>script</dt>
83       *   <dd>only mvnw scripts</dd>
84       *   <dt>bin (default)</dt>
85       *   <dd>precompiled and packaged code</dd>
86       *   <dt>source</dt>
87       *   <dd>Java source code, will be compiled on the fly</dd>
88       * </dl>
89       *
90       * Value will be used as classifier of the downloaded file
91       */
92      @Parameter( defaultValue = "bin", property = "type" )
93      private String distributionType;
94  
95      /**
96       * Include <code>mvnwDebug*</code> scripts?
97       */
98      @Parameter( defaultValue = "false", property = "includeDebug" )
99      private boolean includeDebugScript;
100 
101     // READONLY PARAMETERS
102 
103     @Parameter( defaultValue = "${session}", readonly = true, required = true )
104     private MavenSession session;
105 
106     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
107     private Settings settings;
108 
109     // Waiting for https://github.com/eclipse/sisu.inject/pull/39 PathTypeConverter
110     @Parameter( defaultValue = "${project.basedir}", readonly = true, required = true )
111     private File basedir;
112 
113     // CONSTANTS
114 
115     private static final String WRAPPER_DISTRIBUTION_GROUP_ID = "org.apache.maven.wrapper";
116 
117     private static final String WRAPPER_DISTRIBUTION_ARTIFACT_ID = "maven-wrapper-distribution";
118 
119     private static final String WRAPPER_DISTRIBUTION_EXTENSION = "zip";
120 
121     // COMPONENTS
122 
123     @Inject
124     private RepositorySystem repositorySystem;
125 
126     @Inject
127     private Map<String, UnArchiver> unarchivers;
128 
129     @Override
130     public void execute()
131         throws MojoExecutionException, MojoFailureException
132     {
133         mavenVersion = getVersion( mavenVersion, Maven.class, "org.apache.maven/maven-core" );
134         String wrapperVersion = getVersion( null, this.getClass(), "org.apache.maven.plugins/maven-wrapper-plugin" );
135 
136         final Artifact artifact = downloadWrapperDistribution( wrapperVersion );
137         final Path wrapperDir = createDirectories( basedir.toPath().resolve( ".mvn/wrapper" ) );
138 
139         cleanup( wrapperDir );
140         unpack( artifact, basedir.toPath() );
141         replaceProperties( wrapperVersion, wrapperDir );
142     }
143 
144     private Path createDirectories( Path dir )
145         throws MojoExecutionException
146     {
147         try
148         {
149             return Files.createDirectories( dir );
150         }
151         catch ( IOException ioe )
152         {
153             throw new MojoExecutionException( ioe.getMessage(), ioe );
154         }
155     }
156 
157     private void cleanup( Path wrapperDir )
158         throws MojoExecutionException
159     {
160         try ( DirectoryStream<Path> dsClass = Files.newDirectoryStream( wrapperDir, "*.class" ) )
161         {
162             for ( Path file : dsClass )
163             {
164                 // Cleanup old compiled *.class
165                 Files.deleteIfExists( file );
166             }
167             Files.deleteIfExists( wrapperDir.resolve( "MavenWrapperDownloader.java" ) );
168             Files.deleteIfExists( wrapperDir.resolve( "maven-wrapper.jar" ) );
169         }
170         catch ( IOException ioe )
171         {
172             throw new MojoExecutionException( ioe.getMessage(), ioe );
173         }
174     }
175 
176     private Artifact downloadWrapperDistribution( String wrapperVersion )
177         throws MojoExecutionException
178     {
179         Artifact artifact =
180             repositorySystem.createArtifactWithClassifier( WRAPPER_DISTRIBUTION_GROUP_ID,
181                                                            WRAPPER_DISTRIBUTION_ARTIFACT_ID, wrapperVersion,
182                                                            WRAPPER_DISTRIBUTION_EXTENSION, distributionType );
183 
184         MavenExecutionRequest executionRequest = session.getRequest();
185 
186         ArtifactResolutionRequest resolutionRequest = new ArtifactResolutionRequest()
187             .setArtifact( artifact )
188             .setLocalRepository( session.getLocalRepository() )
189             .setRemoteRepositories( session.getCurrentProject().getPluginArtifactRepositories() )
190             .setOffline( executionRequest.isOffline() )
191             .setForceUpdate( executionRequest.isUpdateSnapshots() );
192 
193         ArtifactResolutionResult resolveResult = repositorySystem.resolve( resolutionRequest );
194 
195         if ( !resolveResult.isSuccess() )
196         {
197             if ( executionRequest.isShowErrors() )
198             {
199                 for ( Exception e : resolveResult.getExceptions() )
200                 {
201                     getLog().error( e.getMessage(), e );
202                 }
203             }
204             throw new MojoExecutionException( "artifact: " + artifact + " not resolved." );
205         }
206 
207         return artifact;
208     }
209 
210     private void unpack( Artifact artifact, Path targetFolder )
211     {
212         UnArchiver unarchiver = unarchivers.get( WRAPPER_DISTRIBUTION_EXTENSION );
213         unarchiver.setDestDirectory( targetFolder.toFile() );
214         unarchiver.setSourceFile( artifact.getFile() );
215         if ( !includeDebugScript )
216         {
217             unarchiver.setFileSelectors( new FileSelector[] { new FileSelector()
218             {
219                 @Override
220                 public boolean isSelected( FileInfo fileInfo )
221                 {
222                     return !fileInfo.getName().contains( "Debug" );
223                 }
224             } } );
225         }
226         unarchiver.extract();
227         getLog().info( "Unpacked " + buffer().strong( distributionType ) + " type wrapper distribution " + artifact );
228     }
229 
230     /**
231      * As long as the content only contains the license and the distributionUrl, we can simply replace it.
232      * No need to look for other properties, restore them, respecting comments, etc.
233      *
234      * @param wrapperVersion the wrapper version
235      * @param targetFolder the folder containing the wrapper.properties
236      * @throws MojoExecutionException if writing fails
237      */
238     private void replaceProperties( String wrapperVersion, Path targetFolder )
239         throws MojoExecutionException
240     {
241         String repoUrl = getRepoUrl();
242 
243         String distributionUrl =
244             repoUrl + "/org/apache/maven/apache-maven/" + mavenVersion + "/apache-maven-" + mavenVersion + "-bin.zip";
245         String wrapperUrl = repoUrl + "/org/apache/maven/wrapper/maven-wrapper/" + wrapperVersion + "/maven-wrapper-"
246             + wrapperVersion + ".jar";
247 
248         Path wrapperPropertiesFile = targetFolder.resolve( "maven-wrapper.properties" );
249 
250         getLog().info( "Configuring .mvn/wrapper/maven-wrapper.properties to use "
251             + buffer().strong( "Maven " + mavenVersion ) + " and download from " + repoUrl );
252 
253         final String license = "# Licensed to the Apache Software Foundation (ASF) under one%n"
254             + "# or more contributor license agreements.  See the NOTICE file%n"
255             + "# distributed with this work for additional information%n"
256             + "# regarding copyright ownership.  The ASF licenses this file%n"
257             + "# to you under the Apache License, Version 2.0 (the%n"
258             + "# \"License\"); you may not use this file except in compliance%n"
259             + "# with the License.  You may obtain a copy of the License at%n"
260             + "#%n"
261             + "#   https://www.apache.org/licenses/LICENSE-2.0%n"
262             + "#%n"
263             + "# Unless required by applicable law or agreed to in writing,%n"
264             + "# software distributed under the License is distributed on an%n"
265             + "# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY%n"
266             + "# KIND, either express or implied.  See the License for the%n"
267             + "# specific language governing permissions and limitations%n"
268             + "# under the License.%n";
269 
270         try ( BufferedWriter out = Files.newBufferedWriter( wrapperPropertiesFile, StandardCharsets.UTF_8 ) )
271         {
272             out.append( String.format( Locale.ROOT, license ) );
273             out.append( "distributionUrl=" + distributionUrl + System.lineSeparator() );
274             out.append( "wrapperUrl=" + wrapperUrl + System.lineSeparator() );
275         }
276         catch ( IOException ioe )
277         {
278             throw new MojoExecutionException( "Can't create maven-wrapper.properties", ioe );
279         }
280     }
281 
282     private String getVersion( String defaultVersion, Class<?> clazz, String path )
283     {
284         String version = defaultVersion;
285         if ( version == null )
286         {
287             Properties props = new Properties();
288             try ( InputStream is = clazz.getResourceAsStream( "/META-INF/maven/" + path + "/pom.properties" ) )
289             {
290                 if ( is != null )
291                 {
292                     props.load( is );
293                     version = props.getProperty( "version" );
294                 }
295             }
296             catch ( IOException e )
297             {
298                 // noop
299             }
300         }
301         return version;
302     }
303 
304     /**
305      * Determine the repository URL to download Wrapper and Maven from.
306      */
307     private String getRepoUrl()
308     {
309         // default
310         String repoUrl = DEFAULT_REPOURL;
311 
312         // adapt to also support MVNW_REPOURL as supported by mvnw scripts from maven-wrapper
313         String mvnwRepoUrl = System.getenv( MVNW_REPOURL );
314         if ( mvnwRepoUrl != null && !mvnwRepoUrl.isEmpty() )
315         {
316             repoUrl = mvnwRepoUrl;
317             getLog().debug( "Using repo URL from " + MVNW_REPOURL + " environment variable." );
318         }
319         // otherwise mirror from settings
320         else if ( settings.getMirrors() != null && !settings.getMirrors().isEmpty() )
321         {
322             for ( Mirror current : settings.getMirrors() )
323             {
324                 if ( "*".equals( current.getMirrorOf() ) )
325                 {
326                     repoUrl = current.getUrl();
327                     break;
328                 }
329             }
330             getLog().debug( "Using repo URL from * mirror in settings file." );
331         }
332 
333         getLog().debug( "Determined repo URL to use as " + repoUrl );
334 
335         return repoUrl;
336     }
337 }