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.internal.impl;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Optional;
31  import java.util.Set;
32  import java.util.stream.Collectors;
33  
34  import org.apache.maven.api.Packaging;
35  import org.apache.maven.api.Type;
36  import org.apache.maven.api.model.Dependency;
37  import org.apache.maven.api.model.InputLocation;
38  import org.apache.maven.api.model.Plugin;
39  import org.apache.maven.api.model.PluginContainer;
40  import org.apache.maven.api.model.PluginExecution;
41  import org.apache.maven.api.services.Lookup;
42  import org.apache.maven.api.services.PackagingRegistry;
43  import org.apache.maven.api.services.TypeRegistry;
44  import org.apache.maven.api.spi.PackagingProvider;
45  import org.apache.maven.lifecycle.mapping.LifecycleMapping;
46  import org.apache.maven.lifecycle.mapping.LifecycleMojo;
47  import org.apache.maven.lifecycle.mapping.LifecyclePhase;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  /**
52   * TODO: this is session scoped as SPI can contribute.
53   */
54  @Named
55  @Singleton
56  public class DefaultPackagingRegistry
57          extends ExtensibleEnumRegistries.DefaultExtensibleEnumRegistry<Packaging, PackagingProvider>
58          implements PackagingRegistry {
59  
60      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPackagingRegistry.class);
61  
62      private final Lookup lookup;
63  
64      private final TypeRegistry typeRegistry;
65  
66      @Inject
67      public DefaultPackagingRegistry(Lookup lookup, TypeRegistry typeRegistry, List<PackagingProvider> providers) {
68          super(providers);
69          this.lookup = lookup;
70          this.typeRegistry = typeRegistry;
71      }
72  
73      @Override
74      public Optional<Packaging> lookup(String id) {
75          id = id.toLowerCase(Locale.ROOT);
76          // TODO: we should be able to inject a Map<String, LifecycleMapping> directly,
77          // however, SISU visibility filtering can only happen when an explicit
78          // lookup is performed. The whole problem here is caused by "project extensions"
79          // which are bound to a project's classloader, without any clear definition
80          // of a "project scope"
81          LifecycleMapping lifecycleMapping =
82                  lookup.lookupOptional(LifecycleMapping.class, id).orElse(null);
83          if (lifecycleMapping == null) {
84              return Optional.empty();
85          }
86          Type type = typeRegistry.lookup(id).orElse(null);
87          if (type == null) {
88              return Optional.empty();
89          }
90          return Optional.of(new DefaultPackaging(id, type, getPlugins(lifecycleMapping)));
91      }
92  
93      private Map<String, PluginContainer> getPlugins(LifecycleMapping lifecycleMapping) {
94          Map<String, PluginContainer> lfs = new HashMap<>();
95          lifecycleMapping.getLifecycles().forEach((id, lifecycle) -> {
96              Map<String, Plugin> plugins = new HashMap<>();
97              lifecycle
98                      .getLifecyclePhases()
99                      .forEach((phase, lifecyclePhase) -> parseLifecyclePhaseDefinitions(plugins, phase, lifecyclePhase));
100             lfs.put(id, PluginContainer.newBuilder().plugins(plugins.values()).build());
101         });
102         return lfs;
103     }
104 
105     static void parseLifecyclePhaseDefinitions(Map<String, Plugin> plugins, String phase, LifecyclePhase goals) {
106         InputLocation location = DefaultLifecycleRegistry.DEFAULT_LIFECYCLE_INPUT_LOCATION;
107 
108         List<LifecycleMojo> mojos = goals.getMojos();
109         if (mojos != null) {
110             for (int i = 0; i < mojos.size(); i++) {
111                 LifecycleMojo mojo = mojos.get(i);
112 
113                 // Compute goal coordinates
114                 String groupId, artifactId, version, goal;
115                 String[] p = mojo.getGoal().trim().split(":");
116                 if (p.length == 3) {
117                     // <groupId>:<artifactId>:<goal>
118                     groupId = p[0];
119                     artifactId = p[1];
120                     version = null;
121                     goal = p[2];
122                 } else if (p.length == 4) {
123                     // <groupId>:<artifactId>:<version>:<goal>
124                     groupId = p[0];
125                     artifactId = p[1];
126                     version = p[2];
127                     goal = p[3];
128                 } else {
129                     // invalid
130                     LOGGER.warn(
131                             "Ignored invalid goal specification '{}' from lifecycle mapping for phase {}",
132                             mojo.getGoal(),
133                             phase);
134                     continue;
135                 }
136 
137                 String key = groupId + ":" + artifactId;
138 
139                 // Build plugin
140                 List<PluginExecution> execs = new ArrayList<>();
141                 List<Dependency> deps = new ArrayList<>();
142 
143                 Plugin existing = plugins.get(key);
144                 if (existing != null) {
145                     if (version == null) {
146                         version = existing.getVersion();
147                     }
148                     execs.addAll(existing.getExecutions());
149                     deps.addAll(existing.getDependencies());
150                 }
151 
152                 PluginExecution execution = PluginExecution.newBuilder()
153                         .id(getExecutionId(existing, goal))
154                         .priority(i - mojos.size())
155                         .phase(phase)
156                         .goals(List.of(goal))
157                         .configuration(mojo.getConfiguration())
158                         .location("", location)
159                         .location("id", location)
160                         .location("phase", location)
161                         .location("goals", location)
162                         .build();
163                 execs.add(execution);
164 
165                 if (mojo.getDependencies() != null) {
166                     mojo.getDependencies().forEach(d -> deps.add(d.getDelegate()));
167                 }
168 
169                 Plugin plugin = Plugin.newBuilder()
170                         .groupId(groupId)
171                         .artifactId(artifactId)
172                         .version(version)
173                         .location("", location)
174                         .location("groupId", location)
175                         .location("artifactId", location)
176                         .location("version", location)
177                         .executions(execs)
178                         .dependencies(deps)
179                         .build();
180 
181                 plugins.put(key, plugin);
182             }
183         }
184     }
185 
186     private static String getExecutionId(Plugin plugin, String goal) {
187         Set<String> existingIds = plugin != null
188                 ? plugin.getExecutions().stream().map(PluginExecution::getId).collect(Collectors.toSet())
189                 : Set.of();
190         String base = "default-" + goal;
191         String id = base;
192         for (int index = 1; existingIds.contains(id); index++) {
193             id = base + '-' + index;
194         }
195         return id;
196     }
197 
198     private record DefaultPackaging(String id, Type type, Map<String, PluginContainer> plugins) implements Packaging {}
199 }