Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,64 @@ public String id() {

@Override
public Collection<Phase> phases() {
List<Phase> phases = new ArrayList<>(buildOwnPhases());
// Also include phases for default-phases entries that reference phases
// not defined in this lifecycle (e.g., standard lifecycle phases like
// process-sources). This preserves plugin bindings from components.xml
// <default-phases> that map goals to standard lifecycle phases.
Map<String, LifecyclePhase> lfPhases = lifecycle.getDefaultLifecyclePhases();
if (lfPhases != null) {
Set<String> ownPhaseNames = new HashSet<>(lifecycle.getPhases());
for (Map.Entry<String, LifecyclePhase> entry : lfPhases.entrySet()) {
if (!ownPhaseNames.contains(entry.getKey())) {
String phaseName = entry.getKey();
LifecyclePhase lfPhase = entry.getValue();
phases.add(new Phase() {
@Override
public String name() {
return phaseName;
}

@Override
public List<Phase> phases() {
return List.of();
}

@Override
public Stream<Phase> allPhases() {
return Stream.of(this);
}

@Override
public List<Plugin> plugins() {
Map<String, Plugin> plugins = new LinkedHashMap<>();
DefaultPackagingRegistry.parseLifecyclePhaseDefinitions(
plugins, phaseName, lfPhase);
return plugins.values().stream().toList();
}

@Override
public Collection<Link> links() {
return List.of();
}
});
}
}
}
return phases;
}

@Override
public Collection<Phase> v3phases() {
return buildOwnPhases();
}

@Override
public Collection<Alias> aliases() {
return Collections.emptyList();
}

private List<Phase> buildOwnPhases() {
List<String> names = lifecycle.getPhases();
List<Phase> phases = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
Expand Down Expand Up @@ -293,11 +351,6 @@ public Type type() {
}
return phases;
}

@Override
public Collection<Alias> aliases() {
return Collections.emptyList();
}
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.maven.internal.impl.DefaultLifecycleRegistry;
import org.apache.maven.internal.impl.DefaultLookup;
import org.apache.maven.lifecycle.mapping.LifecyclePhase;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.testing.PlexusTest;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -96,6 +100,52 @@ void testCustomLifecycle() throws ComponentLookupException {
assertEquals("etl", dl.getLifeCycles().get(3).getId());
}

/**
* Test for <a href="https://github.com/apache/maven/issues/11796">MNG-11796</a>:
* custom lifecycle with {@code <default-phases>} binding goals to standard lifecycle phases.
*/
@Test
void testCustomLifecycleDefaultPhasesForStandardPhases() throws ComponentLookupException {
// Create a custom lifecycle with default-phases mapping to a standard lifecycle phase
Map<String, LifecyclePhase> defaultPhases = new HashMap<>();
defaultPhases.put("process-sources", new LifecyclePhase("com.example:my-plugin:touch"));

Lifecycle customLifecycle = new Lifecycle("my-extension", Arrays.asList("my-dummy-phase"), defaultPhases);

List<Lifecycle> allLifecycles = new ArrayList<>();
allLifecycles.add(customLifecycle);
allLifecycles.addAll(defaultLifeCycles.getLifeCycles());

Map<String, Lifecycle> lifeCycles = allLifecycles.stream().collect(Collectors.toMap(Lifecycle::getId, l -> l));
PlexusContainer mockedPlexusContainer = mock(PlexusContainer.class);
when(mockedPlexusContainer.lookupMap(Lifecycle.class)).thenReturn(lifeCycles);

DefaultLifecycles dl = new DefaultLifecycles(
new DefaultLifecycleRegistry(
List.of(new DefaultLifecycleRegistry.LifecycleWrapperProvider(mockedPlexusContainer))),
new DefaultLookup(mockedPlexusContainer));

// Find the custom lifecycle
Lifecycle result = dl.getLifeCycles().stream()
.filter(l -> "my-extension".equals(l.getId()))
.findFirst()
.orElseThrow(() -> new AssertionError("Custom lifecycle not found"));

// Verify that getPhases() only contains the custom phase (not standard phases)
assertTrue(result.getPhases().contains("my-dummy-phase"), "Custom lifecycle should contain its own phase");

// Verify that defaultLifecyclePhases includes the standard phase binding
Map<String, LifecyclePhase> resultDefaultPhases = result.getDefaultLifecyclePhases();
assertNotNull(resultDefaultPhases, "Default lifecycle phases should not be null");
assertTrue(
resultDefaultPhases.containsKey("process-sources"),
"Default lifecycle phases should contain 'process-sources' binding");
String goalSpec = resultDefaultPhases.get("process-sources").toString();
assertTrue(
goalSpec.contains("com.example") && goalSpec.contains("my-plugin") && goalSpec.contains("touch"),
"The process-sources binding should map to the correct goal, got: " + goalSpec);
}

private Lifecycle getLifeCycleById(String id) {
return defaultLifeCycles.getLifeCycles().stream()
.filter(l -> id.equals(l.getId()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.it;

import java.io.File;

import org.junit.jupiter.api.Test;

/**
* This is a test for
* <a href="https://github.com/apache/maven/issues/11796">MNG-11796</a>.
* <p>
* Verifies that {@code <default-phases>} in a custom lifecycle's {@code components.xml}
* correctly binds plugin goals to standard lifecycle phases (e.g., {@code process-sources}).
*/
class MavenITmng11796DefaultPhasesStandardLifecycleTest extends AbstractMavenIntegrationTestCase {

/**
* Verify that a plugin extension with {@code <default-phases>} mapping a goal
* to the standard {@code process-sources} phase causes that goal to execute
* during {@code mvn compile}.
*/
@Test
void testDefaultPhasesBindToStandardLifecyclePhases() throws Exception {
File testDir = extractResources("/mng-11796-default-phases-standard-lifecycle");

// Install the extension plugin
Verifier verifier = newVerifier(new File(testDir, "extension-plugin").getAbsolutePath());
verifier.addCliArgument("install");
verifier.execute();
verifier.verifyErrorFreeLog();

// Run compile on the consumer project - the touch goal should execute at process-sources
verifier = newVerifier(new File(testDir, "consumer-project").getAbsolutePath());
verifier.addCliArgument("compile");
verifier.execute();
verifier.verifyErrorFreeLog();
verifier.verifyTextInLog("MNG-11796 touch goal executed");
verifier.verifyFilePresent("target/touch.txt");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.apache.maven.its.mng11796</groupId>
<artifactId>consumer-project</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>MNG-11796 Consumer Project</name>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.its.mng11796</groupId>
<artifactId>extension-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.apache.maven.its.mng11796</groupId>
<artifactId>extension-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>MNG-11796 Extension Plugin</name>

<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.4</version>
<executions>
<execution>
<goals>
<goal>descriptor</goal>
</goals>
<configuration>
<goalPrefix>mng11796</goalPrefix>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.its.mng11796;

import java.io.File;
import java.io.IOException;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

/**
* Creates a marker file to prove the goal was executed.
* @goal touch
*/
public class TouchMojo extends AbstractMojo {
/**
* @parameter default-value="${project.build.directory}"
*/
private File outputDirectory;

public void execute() throws MojoExecutionException {
getLog().info("MNG-11796 touch goal executed");
File touchFile = new File(outputDirectory, "touch.txt");
touchFile.getParentFile().mkdirs();
try {
touchFile.createNewFile();
} catch (IOException e) {
throw new MojoExecutionException("Failed to create touch file", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<component-set>
<components>
<component>
<role>org.apache.maven.lifecycle.Lifecycle</role>
<role-hint>mng11796</role-hint>
<implementation>org.apache.maven.lifecycle.Lifecycle</implementation>
<configuration>
<id>mng11796</id>
<phases>
<phase>mng11796-dummy-phase</phase>
</phases>
<default-phases>
<process-sources>
org.apache.maven.its.mng11796:extension-plugin:touch
</process-sources>
</default-phases>
</configuration>
</component>
</components>
</component-set>
Loading