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 @@ -519,37 +519,18 @@ private static boolean isExternalModelBuildingRequest(ModelBuildingRequest reque
* Returns a sandboxed {@link ProfileActivationContext} for evaluating profiles in
* repository-resolved (external) models — dependency POMs, parent POMs, and imported BOMs.
* <p>
* The sandboxed context preserves system properties (so JDK/OS activation works) and
* merges the POM's own {@code <properties>} into the system properties map so that
* property-activated profiles that depend on POM-declared values still work.
* The sandboxed context preserves system properties (so JDK/OS activation works).
* User properties (consumer {@code -D} flags) are suppressed because they were not
* set for the dependency and must not accidentally activate its profiles.
* set for the dependency and must not accidentally activate its profiles. POM-defined
* properties are not exposed through the system-property lookup, so a dependency POM
* cannot activate its own property-based profiles.
* File-based profiles are pre-filtered via {@link #withoutFileActivation(List)} before
* reaching this context, so {@code getProjectDirectory()} is not relied on for file checks.
* <p>
* Model properties are merged into system properties (with system properties taking
* precedence) rather than changing the {@code PropertyProfileActivator} lookup chain,
* because changing the activator would affect ALL profile evaluations — including the
* build's own project — which can cause unintended profile activation when a POM declares
* a property that matches a profile's activation condition.
*
* @param delegate the original full context for this model build
* @return a sandboxed context suitable for external model profile activation
*/
private static ProfileActivationContext externalActivationContext(ProfileActivationContext delegate) {
// Pre-compute the merged system+project properties once per external model.
// getSystemProperties() may be called multiple times per profile (e.g. OperatingSystemProfileActivator
// calls it 3× for name/arch/version), so allocating a new HashMap on every call is O(deps × profiles ×
// |systemProperties|). Computing it eagerly here keeps the anonymous class allocation-free.
final Map<String, String> mergedSystemProps;
Map<String, String> projectProps = delegate.getProjectProperties();
if (projectProps == null || projectProps.isEmpty()) {
mergedSystemProps = delegate.getSystemProperties();
} else {
Map<String, String> merged = new HashMap<>(projectProps);
merged.putAll(delegate.getSystemProperties()); // system wins
mergedSystemProps = Collections.unmodifiableMap(merged);
}
return new ProfileActivationContext() {
@Override
public List<String> getActiveProfileIds() {
Expand All @@ -561,15 +542,9 @@ public List<String> getInactiveProfileIds() {
return delegate.getInactiveProfileIds();
}

/**
* System properties merged with project properties (system wins on conflict),
* pre-computed once to avoid repeated allocations across multiple activator calls.
* This makes POM-declared properties visible to the PropertyProfileActivator
* without modifying the activator's lookup chain for non-external models.
*/
@Override
public Map<String, String> getSystemProperties() {
return mergedSystemProps;
return delegate.getSystemProperties();
}

/** User properties are suppressed: consumer -D flags do not activate dependency profiles. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ public boolean isActive(Profile profile, ProfileActivationContext context, Model

// Lookup order: user (-D) → system (java.version, os.name, …).
// In external model builds the caller provides a sandboxed context
// (see DefaultModelBuilder.externalActivationContext()) that suppresses user properties
// and merges model properties into system properties, so consumer -D flags cannot
// activate dependency profiles while POM-declared properties still drive activation.
// (see DefaultModelBuilder.externalActivationContext()) that suppresses user and
// POM-defined properties, so consumer -D flags and dependency-local properties cannot
// activate dependency profiles.
String sysValue = context.getUserProperties().get(name);
if (sysValue == null) {
sysValue = context.getSystemProperties().get(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@
* and must not accidentally activate its profiles.</li>
* <li><b>Property (system)</b> — re-activated: system properties are platform facts
* ({@code java.version}, {@code os.name}, …).</li>
* <li><b>Property (POM-declared, from &lt;properties&gt;)</b> — re-activated: the POM's own
* properties are part of the artifact's published identity, not the consumer's environment.
* A profile conditioned on a POM-declared property is publisher-intent, not consumer-injected.</li>
* <li><b>Property (POM-declared, from &lt;properties&gt;)</b> — suppressed: a dependency POM's
* own properties must not activate its profiles.</li>
* <li><b>Negated property (-D absent)</b> — re-activated (default-on): the property is absent from
* the sandboxed context, so {@code !foo} evaluates to true, preserving the publisher's intent.</li>
* <li><b>Profile repositories</b> — honored when the profile is legitimately active (JDK, OS,
Expand Down Expand Up @@ -110,7 +109,7 @@ class ResolvedDependencyProfileActivationTest {
+ " <profile.sys.property>activated</profile.sys.property>\n"
+ " </properties>\n"
+ " </profile>\n"
// profile 4: POM-declared property activation (positive) — must fire in external builds
// profile 4: POM-declared property activation (positive) — suppressed in external builds
+ " <profile>\n"
+ " <id>pom-declared-property-condition</id>\n"
+ " <activation>\n"
Expand Down Expand Up @@ -196,13 +195,9 @@ void testProjectBuildEvaluatesAllActivators() throws Exception {
"activated",
model.getProperties().get("profile.sys.property"),
"system-property profile must fire in project build");
// POM-declared property activation is only supported in external (sandbox) builds.
// In project builds, the PropertyProfileActivator does not check model properties
// because doing so would cause unintended profile activation when a POM declares
// a property that also matches a profile's activation condition (see IT proxy profile).
assertNull(
model.getProperties().get("profile.pom.property"),
"POM-declared-property profile must NOT fire in project build (only in external)");
"POM-declared-property profile must NOT fire in project build");
assertEquals(
"activated",
model.getProperties().get("profile.negated.property"),
Expand Down Expand Up @@ -251,16 +246,14 @@ void testExternalModelSystemPropertyActivationIsPreserved() throws Exception {
}

@Test
void testExternalModelPomDeclaredPropertyActivationIsPreserved() throws Exception {
// A profile conditioned on the POM's own <properties> must fire in external builds:
// those properties are part of the artifact's published identity, not the consumer's env.
void testExternalModelPomDeclaredPropertyActivationIsSuppressed() throws Exception {
// A dependency POM's own <properties> must not activate a property-based profile.
Model model = build(
ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, systemPropertiesWithTestValues(), new Properties());

assertEquals(
"activated",
assertNull(
model.getProperties().get("profile.pom.property"),
"POM-declared-property profile must fire in external model build");
"POM-declared-property profile must NOT fire in external model build");
}

@Test
Expand Down
Loading