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