View Javadoc
1   package org.apache.maven.plugins.deploy;
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 java.io.File;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  import org.apache.maven.RepositoryUtils;
29  import org.apache.maven.artifact.ArtifactUtils;
30  import org.apache.maven.plugin.MojoExecutionException;
31  import org.apache.maven.plugin.MojoFailureException;
32  import org.apache.maven.plugin.descriptor.PluginDescriptor;
33  import org.apache.maven.plugins.annotations.LifecyclePhase;
34  import org.apache.maven.plugins.annotations.Mojo;
35  import org.apache.maven.plugins.annotations.Parameter;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.project.artifact.ProjectArtifact;
38  import org.apache.maven.project.artifact.ProjectArtifactMetadata;
39  import org.eclipse.aether.deployment.DeployRequest;
40  import org.eclipse.aether.repository.RemoteRepository;
41  import org.eclipse.aether.util.artifact.SubArtifact;
42  
43  /**
44   * Deploys an artifact to remote repository.
45   * 
46   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
47   * @author <a href="mailto:jdcasey@apache.org">John Casey (refactoring only)</a>
48   */
49  @Mojo( name = "deploy", defaultPhase = LifecyclePhase.DEPLOY, threadSafe = true )
50  public class DeployMojo
51      extends AbstractDeployMojo
52  {
53      private static final Pattern ALT_LEGACY_REPO_SYNTAX_PATTERN = Pattern.compile( "(.+?)::(.+?)::(.+)" );
54  
55      private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile( "(.+?)::(.+)" );
56  
57      @Parameter( defaultValue = "${project}", readonly = true, required = true )
58      private MavenProject project;
59  
60      @Parameter( defaultValue = "${reactorProjects}", required = true, readonly = true )
61      private List<MavenProject> reactorProjects;
62  
63      @Parameter( defaultValue = "${plugin}", required = true, readonly = true )
64      private PluginDescriptor pluginDescriptor;
65  
66      /**
67       * Whether every project should be deployed during its own deploy-phase or at the end of the multimodule build. If
68       * set to {@code true} and the build fails, none of the reactor projects is deployed.
69       * <strong>(experimental)</strong>
70       * 
71       * @since 2.8
72       */
73      @Parameter( defaultValue = "false", property = "deployAtEnd" )
74      private boolean deployAtEnd;
75  
76      /**
77       * Specifies an alternative repository to which the project artifacts should be deployed (other than those specified
78       * in &lt;distributionManagement&gt;). <br/>
79       * Format: <code>id::url</code>
80       * <dl>
81       * <dt>id</dt>
82       * <dd>The id can be used to pick up the correct credentials from the settings.xml</dd>
83       * <dt>url</dt>
84       * <dd>The location of the repository</dd>
85       * </dl>
86       * <b>Note:</b> In version 2.x, the format was <code>id::<i>layout</i>::url</code> where <code><i>layout</i></code>
87       * could be <code>default</code> (ie. Maven 2) or <code>legacy</code> (ie. Maven 1), but since 3.0.0 the layout part
88       * has been removed because Maven 3 only supports Maven 2 repository layout.
89       */
90      @Parameter( property = "altDeploymentRepository" )
91      private String altDeploymentRepository;
92  
93      /**
94       * The alternative repository to use when the project has a snapshot version.
95       *
96       * <b>Note:</b> In version 2.x, the format was <code>id::<i>layout</i>::url</code> where <code><i>layout</i></code>
97       * could be <code>default</code> (ie. Maven 2) or <code>legacy</code> (ie. Maven 1), but since 3.0.0 the layout part
98       * has been removed because Maven 3 only supports Maven 2 repository layout.
99       * @since 2.8
100      * @see DeployMojo#altDeploymentRepository
101      */
102     @Parameter( property = "altSnapshotDeploymentRepository" )
103     private String altSnapshotDeploymentRepository;
104 
105     /**
106      * The alternative repository to use when the project has a final version.
107      *
108      * <b>Note:</b> In version 2.x, the format was <code>id::<i>layout</i>::url</code> where <code><i>layout</i></code>
109      * could be <code>default</code> (ie. Maven 2) or <code>legacy</code> (ie. Maven 1), but since 3.0.0 the layout part
110      * has been removed because Maven 3 only supports Maven 2 repository layout.
111      * @since 2.8
112      * @see DeployMojo#altDeploymentRepository
113      */
114     @Parameter( property = "altReleaseDeploymentRepository" )
115     private String altReleaseDeploymentRepository;
116 
117     /**
118      * Set this to 'true' to bypass artifact deploy
119      * Since since 3.0.0-M2 it's not anymore a real boolean as it can have more than 2 values:
120      * <ul>
121      *     <li><code>true</code>: will skip as usual</li>
122      *     <li><code>releases</code>: will skip if current version of the project is a release</li>
123      *     <li><code>snapshots</code>: will skip if current version of the project is a snapshot</li>
124      *     <li>any other values will be considered as <code>false</code></li>
125      * </ul>
126      * @since 2.4
127      */
128     @Parameter( property = "maven.deploy.skip", defaultValue = "false" )
129     private String skip = Boolean.FALSE.toString();
130 
131     private enum State
132     {
133         SKIPPED, DEPLOYED, TO_BE_DEPLOYED
134     }
135 
136     private static final String DEPLOY_PROCESSED_MARKER = DeployMojo.class.getName() + ".processed";
137 
138     private static final String DEPLOY_ALT_RELEASE_DEPLOYMENT_REPOSITORY =
139         DeployMojo.class.getName() + ".altReleaseDeploymentRepository";
140 
141     private static final String DEPLOY_ALT_SNAPSHOT_DEPLOYMENT_REPOSITORY =
142         DeployMojo.class.getName() + ".altSnapshotDeploymentRepository";
143 
144     private static final String DEPLOY_ALT_DEPLOYMENT_REPOSITORY =
145         DeployMojo.class.getName() + ".altDeploymentRepository";
146 
147     private void putState( State state )
148     {
149         getPluginContext().put( DEPLOY_PROCESSED_MARKER, state.name() );
150     }
151 
152     private void putPluginContextValue( String key, String value )
153     {
154         if ( value != null )
155         {
156             getPluginContext().put( key, value );
157         }
158     }
159 
160     private String getPluginContextValue( Map<String, Object> pluginContext, String key )
161     {
162         return (String) pluginContext.get( key );
163     }
164 
165     private State getState( Map<String, Object> pluginContext )
166     {
167         return State.valueOf( getPluginContextValue( pluginContext, DEPLOY_PROCESSED_MARKER ) );
168     }
169 
170     private boolean hasState( MavenProject project )
171     {
172         Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project );
173         return pluginContext.containsKey( DEPLOY_PROCESSED_MARKER );
174     }
175 
176     public void execute()
177         throws MojoExecutionException, MojoFailureException
178     {
179         if ( Boolean.parseBoolean( skip )
180             || ( "releases".equals( skip ) && !ArtifactUtils.isSnapshot( project.getVersion() ) )
181             || ( "snapshots".equals( skip ) && ArtifactUtils.isSnapshot( project.getVersion() ) )
182         )
183         {
184             getLog().info( "Skipping artifact deployment" );
185             putState( State.SKIPPED );
186         }
187         else
188         {
189             failIfOffline();
190             warnIfAffectedPackagingAndMaven( project.getPackaging() );
191 
192             if ( !deployAtEnd )
193             {
194                 deploy( session.getRepositorySession(),
195                         processProject( project,
196                         altSnapshotDeploymentRepository, altReleaseDeploymentRepository, altDeploymentRepository ) );
197                 putState( State.DEPLOYED );
198             }
199             else
200             {
201                 putPluginContextValue( DEPLOY_ALT_RELEASE_DEPLOYMENT_REPOSITORY, altReleaseDeploymentRepository );
202                 putPluginContextValue( DEPLOY_ALT_SNAPSHOT_DEPLOYMENT_REPOSITORY, altSnapshotDeploymentRepository );
203                 putPluginContextValue( DEPLOY_ALT_DEPLOYMENT_REPOSITORY, altDeploymentRepository );
204                 putState( State.TO_BE_DEPLOYED );
205                 getLog().info( "Deferring deploy for " + project.getGroupId()
206                         + ":" + project.getArtifactId() + ":" + project.getVersion() + " at end" );
207             }
208         }
209 
210         if ( allProjectsMarked() )
211         {
212             for ( MavenProject reactorProject : reactorProjects )
213             {
214                 Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, reactorProject );
215                 State state = getState( pluginContext );
216                 if ( state == State.TO_BE_DEPLOYED )
217                 {
218                     String altReleaseDeploymentRepository =
219                         getPluginContextValue( pluginContext, DEPLOY_ALT_RELEASE_DEPLOYMENT_REPOSITORY );
220                     String altSnapshotDeploymentRepository =
221                         getPluginContextValue( pluginContext, DEPLOY_ALT_SNAPSHOT_DEPLOYMENT_REPOSITORY );
222                     String altDeploymentRepository =
223                         getPluginContextValue( pluginContext, DEPLOY_ALT_DEPLOYMENT_REPOSITORY );
224 
225                     deploy( session.getRepositorySession(),
226                             processProject( reactorProject,
227                             altSnapshotDeploymentRepository, altReleaseDeploymentRepository, altDeploymentRepository )
228                     );
229                 }
230             }
231         }
232     }
233 
234     private boolean allProjectsMarked()
235     {
236         for ( MavenProject reactorProject : reactorProjects )
237         {
238             if ( !hasState( reactorProject ) )
239             {
240                 return false;
241             }
242         }
243         return true;
244     }
245 
246     private DeployRequest processProject( final MavenProject project,
247                                           final String altSnapshotDeploymentRepository,
248                                           final String altReleaseDeploymentRepository,
249                                           final String altDeploymentRepository )
250             throws MojoExecutionException, MojoFailureException
251     {
252         DeployRequest request = new DeployRequest();
253         request.setRepository( getDeploymentRepository( project,
254                 altSnapshotDeploymentRepository, altReleaseDeploymentRepository, altDeploymentRepository ) );
255 
256         org.apache.maven.artifact.Artifact mavenMainArtifact = project.getArtifact();
257         String packaging = project.getPackaging();
258         File pomFile = project.getFile();
259         boolean isPomArtifact = "pom".equals( packaging );
260         boolean pomArtifactAttached = false;
261 
262         if ( pomFile != null )
263         {
264             request.addArtifact( RepositoryUtils.toArtifact( new ProjectArtifact( project ) ) );
265             pomArtifactAttached = true;
266         }
267 
268         if ( !isPomArtifact )
269         {
270             File file = mavenMainArtifact.getFile();
271             if ( file != null && file.isFile() )
272             {
273                 org.eclipse.aether.artifact.Artifact mainArtifact = RepositoryUtils.toArtifact( mavenMainArtifact );
274                 request.addArtifact( mainArtifact );
275 
276                 if ( !pomArtifactAttached )
277                 {
278                     for ( Object metadata : mavenMainArtifact.getMetadataList() )
279                     {
280                         if ( metadata instanceof ProjectArtifactMetadata )
281                         {
282                             request.addArtifact( new SubArtifact(
283                                     mainArtifact,
284                                     "",
285                                     "pom"
286                             ).setFile( ( (ProjectArtifactMetadata) metadata ).getFile() ) );
287                             pomArtifactAttached = true;
288                         }
289                     }
290                 }
291             }
292             else if ( !project.getAttachedArtifacts().isEmpty() )
293             {
294                 throw new MojoExecutionException( "The packaging plugin for this project did not assign "
295                         + "a main file to the project but it has attachments. Change packaging to 'pom'." );
296             }
297             else
298             {
299                 throw new MojoExecutionException( "The packaging for this project did not assign "
300                         + "a file to the build artifact" );
301             }
302         }
303 
304         if ( !pomArtifactAttached )
305         {
306             throw new MojoExecutionException( "The POM could not be attached" );
307         }
308 
309         for ( org.apache.maven.artifact.Artifact attached : project.getAttachedArtifacts() )
310         {
311             getLog().debug( "Attaching for install: " + attached.getId() );
312             request.addArtifact( RepositoryUtils.toArtifact( attached ) );
313         }
314 
315         return request;
316     }
317 
318     /**
319      * Visible for testing.
320      */
321     RemoteRepository getDeploymentRepository( final MavenProject project,
322                                               final String altSnapshotDeploymentRepository,
323                                               final String altReleaseDeploymentRepository,
324                                               final String altDeploymentRepository )
325 
326         throws MojoExecutionException, MojoFailureException
327     {
328         RemoteRepository repo = null;
329 
330         String altDeploymentRepo;
331         if ( ArtifactUtils.isSnapshot( project.getVersion() ) && altSnapshotDeploymentRepository != null )
332         {
333             altDeploymentRepo = altSnapshotDeploymentRepository;
334         }
335         else if ( !ArtifactUtils.isSnapshot( project.getVersion() ) && altReleaseDeploymentRepository != null )
336         {
337             altDeploymentRepo = altReleaseDeploymentRepository;
338         }
339         else
340         {
341             altDeploymentRepo = altDeploymentRepository;
342         }
343 
344         if ( altDeploymentRepo != null )
345         {
346             getLog().info( "Using alternate deployment repository " + altDeploymentRepo );
347 
348             Matcher matcher = ALT_LEGACY_REPO_SYNTAX_PATTERN.matcher( altDeploymentRepo );
349 
350             if ( matcher.matches() )
351             {
352                 String id = matcher.group( 1 ).trim();
353                 String layout = matcher.group( 2 ).trim();
354                 String url = matcher.group( 3 ).trim();
355 
356                 if ( "default".equals( layout ) )
357                 {
358                     getLog().warn( "Using legacy syntax for alternative repository. "
359                             + "Use \"" + id + "::" + url + "\" instead." );
360                     repo = getRemoteRepository( id, url );
361                 }
362                 else
363                 {
364                     throw new MojoFailureException( altDeploymentRepo,
365                             "Invalid legacy syntax and layout for repository.",
366                             "Invalid legacy syntax and layout for alternative repository. Use \""
367                                     + id + "::" + url + "\" instead, and only default layout is supported."
368                     );
369                 }
370             }
371             else
372             {
373                 matcher = ALT_REPO_SYNTAX_PATTERN.matcher( altDeploymentRepo );
374 
375                 if ( !matcher.matches() )
376                 {
377                     throw new MojoFailureException( altDeploymentRepo,
378                             "Invalid syntax for repository.",
379                             "Invalid syntax for alternative repository. Use \"id::url\"."
380                     );
381                 }
382                 else
383                 {
384                     String id = matcher.group( 1 ).trim();
385                     String url = matcher.group( 2 ).trim();
386 
387                     repo = getRemoteRepository( id, url );
388                 }
389             }
390         }
391 
392         if ( repo == null )
393         {
394             repo = RepositoryUtils.toRepo( project.getDistributionManagementArtifactRepository() );
395         }
396 
397         if ( repo == null )
398         {
399             String msg = "Deployment failed: repository element was not specified in the POM inside"
400                 + " distributionManagement element or in -DaltDeploymentRepository=id::url parameter";
401 
402             throw new MojoExecutionException( msg );
403         }
404 
405         return repo;
406     }
407 
408 }