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 @@ -225,73 +225,101 @@ public String id() {
return lifecycle.getId();
}

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

@Override
public Collection<Phase> phases() {
List<Phase> phases = new ArrayList<>(buildOwnPhases());
// Include phases from getDefaultLifecyclePhases() that bind to phases
// from other lifecycles (e.g. process-sources from the default lifecycle).
// In Maven 3, <default-phases> could bind plugin goals to standard lifecycle
// phases. These cross-lifecycle bindings must be preserved so they survive
// the round-trip conversion back to a legacy Lifecycle.
Map<String, LifecyclePhase> defaultPhases = lifecycle.getDefaultLifecyclePhases();
if (defaultPhases != null) {
Set<String> ownPhases = new HashSet<>(lifecycle.getPhases());
for (String phaseName : defaultPhases.keySet()) {
if (!ownPhases.contains(phaseName)) {
phases.add(createPhase(phaseName, null));
}
}
}
return phases;
}

private List<Phase> buildOwnPhases() {
List<String> names = lifecycle.getPhases();
List<Phase> phases = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
String prev = i > 0 ? names.get(i - 1) : null;
phases.add(new Phase() {
@Override
public String name() {
return name;
}

@Override
public List<Phase> phases() {
return List.of();
}
phases.add(createPhase(name, prev));
}
return phases;
}

@Override
public Stream<Phase> allPhases() {
return Stream.concat(
Stream.of(this), phases().stream().flatMap(Lifecycle.Phase::allPhases));
private Phase createPhase(String name, String prev) {
return new Phase() {
@Override
public String name() {
return name;
}

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

@Override
public Stream<Phase> allPhases() {
return Stream.concat(
Stream.of(this), phases().stream().flatMap(Lifecycle.Phase::allPhases));
}

@Override
public List<Plugin> plugins() {
Map<String, LifecyclePhase> lfPhases = lifecycle.getDefaultLifecyclePhases();
LifecyclePhase phase = lfPhases != null ? lfPhases.get(name) : null;
if (phase != null) {
Map<String, Plugin> plugins = new LinkedHashMap<>();
DefaultPackagingRegistry.parseLifecyclePhaseDefinitions(plugins, name, phase);
return plugins.values().stream().toList();
}
return List.of();
}

@Override
public List<Plugin> plugins() {
Map<String, LifecyclePhase> lfPhases = lifecycle.getDefaultLifecyclePhases();
LifecyclePhase phase = lfPhases != null ? lfPhases.get(name) : null;
if (phase != null) {
Map<String, Plugin> plugins = new LinkedHashMap<>();
DefaultPackagingRegistry.parseLifecyclePhaseDefinitions(plugins, name, phase);
return plugins.values().stream().toList();
}
@Override
public Collection<Link> links() {
if (prev == null) {
return List.of();
} else {
return List.of(new Link() {
@Override
public Kind kind() {
return Kind.AFTER;
}

@Override
public Pointer pointer() {
return new Pointer() {
@Override
public String phase() {
return prev;
}

@Override
public Type type() {
return Type.PROJECT;
}
};
}
});
}

@Override
public Collection<Link> links() {
if (prev == null) {
return List.of();
} else {
return List.of(new Link() {
@Override
public Kind kind() {
return Kind.AFTER;
}

@Override
public Pointer pointer() {
return new Pointer() {
@Override
public String phase() {
return prev;
}

@Override
public Type type() {
return Type.PROJECT;
}
};
}
});
}
}
});
}
return phases;
}
};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,23 @@
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.assertFalse;
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 +101,49 @@ void testCustomLifecycle() throws ComponentLookupException {
assertEquals("etl", dl.getLifeCycles().get(3).getId());
}

@Test
void testCustomLifecycleWithCrossLifecycleDefaultPhases() throws ComponentLookupException {
// Simulates a plugin that registers a custom lifecycle via components.xml
// with <default-phases> binding goals to standard lifecycle phases (e.g. process-sources)
// rather than to phases of the custom lifecycle itself. This is the Maven 3 mechanism
// for extension plugins to bind goals to standard phases without requiring <executions>.
Map<String, LifecyclePhase> defaultPhases = new HashMap<>();
defaultPhases.put("process-sources", new LifecyclePhase("com.example:my-plugin:1.0:touch"));

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

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

Map<String, Lifecycle> lifeCycles = myLifecycles.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));

Lifecycle resolved = dl.getLifeCycles().stream()
.filter(l -> "my-custom-lifecycle".equals(l.getId()))
.findFirst()
.orElseThrow();

// Cross-lifecycle default phase bindings must survive the round-trip conversion
Map<String, LifecyclePhase> resolvedDefaultPhases = resolved.getDefaultLifecyclePhases();
assertNotNull(resolvedDefaultPhases);
assertTrue(
resolvedDefaultPhases.containsKey("process-sources"),
"Cross-lifecycle binding to 'process-sources' should be preserved");

// The lifecycle's own phase list should NOT include cross-lifecycle phases
assertFalse(
resolved.getPhases().contains("process-sources"),
"Cross-lifecycle phase should not appear in the lifecycle's own phase list");
assertTrue(resolved.getPhases().contains("custom-phase"), "Lifecycle's own phase should be present");
}

private Lifecycle getLifeCycleById(String id) {
return defaultLifeCycles.getLifeCycles().stream()
.filter(l -> id.equals(l.getId()))
Expand Down
Loading