diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java index b64d1e07054b..37d3ed61c89e 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java @@ -21,6 +21,7 @@ import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.model.PluginContainer; /** * Interface representing a Maven project packaging. @@ -44,12 +45,19 @@ public interface Packaging extends ExtensibleEnum { */ @Nonnull default Language language() { - return getType().getLanguage(); + return type().getLanguage(); } /** * The type of main artifact produced by this packaging. */ @Nonnull - Type getType(); + Type type(); + + /** + * Returns the binding to use specifically for this packaging. + * This will be merged to the default packaging definition. + */ + @Nonnull + PluginContainer plugins(); } diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PackagingProvider.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PackagingProvider.java new file mode 100644 index 000000000000..b1ef5b5e1308 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PackagingProvider.java @@ -0,0 +1,27 @@ +/* + * 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. + */ +package org.apache.maven.api.spi; + +import org.apache.maven.api.Packaging; +import org.apache.maven.api.annotations.Consumer; +import org.apache.maven.api.annotations.Experimental; + +@Experimental +@Consumer +public interface PackagingProvider extends ExtensibleEnumProvider {} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java index 2a5b8a63b53e..4f1b9c4ed00e 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java @@ -22,27 +22,53 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.maven.api.Packaging; import org.apache.maven.api.Type; +import org.apache.maven.api.model.Dependency; +import org.apache.maven.api.model.InputLocation; +import org.apache.maven.api.model.InputSource; +import org.apache.maven.api.model.Plugin; +import org.apache.maven.api.model.PluginContainer; +import org.apache.maven.api.model.PluginExecution; import org.apache.maven.api.services.PackagingRegistry; import org.apache.maven.api.services.TypeRegistry; +import org.apache.maven.api.spi.PackagingProvider; +import org.apache.maven.lifecycle.internal.DefaultLifecyclePluginAnalyzer; import org.apache.maven.lifecycle.mapping.LifecycleMapping; +import org.apache.maven.lifecycle.mapping.LifecycleMojo; +import org.apache.maven.lifecycle.mapping.LifecyclePhase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * TODO: this is session scoped as SPI can contribute. */ @Named @Singleton -public class DefaultPackagingRegistry implements PackagingRegistry { +public class DefaultPackagingRegistry + extends ExtensibleEnumRegistries.DefaultExtensibleEnumRegistry + implements PackagingRegistry { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPackagingRegistry.class); + private final Map lifecycleMappings; private final TypeRegistry typeRegistry; @Inject - public DefaultPackagingRegistry(Map lifecycleMappings, TypeRegistry typeRegistry) { + public DefaultPackagingRegistry( + Map lifecycleMappings, + TypeRegistry typeRegistry, + List providers) { + super(providers); this.lifecycleMappings = lifecycleMappings; this.typeRegistry = typeRegistry; } @@ -57,17 +83,111 @@ public Optional lookup(String id) { if (type == null) { return Optional.empty(); } + return Optional.of(new DefaultPackaging(id, type, getPlugins(lifecycleMapping))); + } - return Optional.of(new Packaging() { - @Override - public String id() { - return id; - } + private PluginContainer getPlugins(LifecycleMapping lifecycleMapping) { + Map plugins = new HashMap<>(); + lifecycleMapping.getLifecycles().forEach((id, lifecycle) -> lifecycle + .getLifecyclePhases() + .forEach((phase, lifecyclePhase) -> parseLifecyclePhaseDefinitions(plugins, phase, lifecyclePhase))); + return PluginContainer.newBuilder().plugins(plugins.values()).build(); + } + + private void parseLifecyclePhaseDefinitions(Map plugins, String phase, LifecyclePhase goals) { + InputSource inputSource = + new InputSource(DefaultLifecyclePluginAnalyzer.DEFAULTLIFECYCLEBINDINGS_MODELID, null); + InputLocation location = new InputLocation(-1, -1, inputSource, 0); + + List mojos = goals.getMojos(); + if (mojos != null) { + for (int i = 0; i < mojos.size(); i++) { + LifecycleMojo mojo = mojos.get(i); + + // Compute goal coordinates + String groupId, artifactId, version, goal; + String[] p = mojo.getGoal().trim().split(":"); + if (p.length == 3) { + // :: + groupId = p[0]; + artifactId = p[1]; + version = null; + goal = p[2]; + } else if (p.length == 4) { + // ::: + groupId = p[0]; + artifactId = p[1]; + version = p[2]; + goal = p[3]; + } else { + // invalid + LOGGER.warn( + "Ignored invalid goal specification '{}' from lifecycle mapping for phase {}", + mojo.getGoal(), + phase); + continue; + } + + String key = groupId + ":" + artifactId; + + // Build plugin + List execs = new ArrayList<>(); + List deps = new ArrayList<>(); - @Override - public Type getType() { - return type; + Plugin existing = plugins.get(key); + if (existing != null) { + if (version == null) { + version = existing.getVersion(); + } + execs.addAll(existing.getExecutions()); + deps.addAll(existing.getDependencies()); + } + + PluginExecution execution = PluginExecution.newBuilder() + .id(getExecutionId(existing, goal)) + .priority(i - mojos.size()) + .phase(phase) + .goals(List.of(goal)) + .configuration(mojo.getConfiguration()) + .location("", location) + .location("id", location) + .location("phase", location) + .location("goals", location) + .build(); + execs.add(execution); + + if (mojo.getDependencies() != null) { + mojo.getDependencies().forEach(d -> deps.add(d.getDelegate())); + } + + Plugin plugin = Plugin.newBuilder() + .groupId(groupId) + .artifactId(artifactId) + .version(version) + .location("", location) + .location("groupId", location) + .location("artifactId", location) + .location("version", location) + .executions(execs) + .dependencies(deps) + .build(); + + plugins.put(key, plugin); } - }); + } } + + private static String getExecutionId(Plugin plugin, String goal) { + Set existingIds = plugin != null + ? plugin.getExecutions().stream().map(PluginExecution::getId).collect(Collectors.toSet()) + : Set.of(); + String base = "default-" + goal; + String id = base; + for (int index = 1; existingIds.contains(id); index++) { + id = base + '-' + index; + } + return id; + } + + private record DefaultPackaging(String id, Type type, PluginContainer plugins) implements Packaging {} }