Repository navigation
[3.10.x] Fix #13084: sandbox profile activation context for external model builds - #13114
Conversation
gnodet-bot
left a comment
There was a problem hiding this comment.
Backport of #13112 to maven-3.10.x. Cherry-pick is clean and the approach is consistent across both compat and impl layers.
Design note: The compat PropertyProfileActivator adds context.getProjectProperties() as a third lookup for all builds (not just external), which differs slightly from #13112 on master (where model properties were merged into system properties only in the sandbox context). This is actually more consistent: the impl layer's PropertyProfileActivator also adds model property lookup unconditionally, and the test coverage confirms the intent. POM-declared property activation now works uniformly in both project and external builds on the compat path.
No issues found. Solid backport.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
|
This branch was created from The actual fix is only in 2 commits ( Recommended fix: Close this PR, create a new branch from This comment was posted by an automated babysit agent. |
…l model profiles After the sandbox activation context introduced in apache#13114, profiles in dependency POMs that fire legitimately (JDK, OS, activeByDefault, POM-declared property) are now correctly scoped. The blanket withoutRepositories() stripping that applied to ALL active profiles in external builds was over-broad: it broke the established pattern where project → dep1 → dep2 and dep1 knows dep2 is not on Central, declaring its repository inside a JDK- or activeByDefault-activated profile. Remove the withoutRepositories() call and the dead helper method from DefaultModelBuilder. The sandbox already ensures that profiles activated by consumer -D flags or file conditions do not fire in external builds, so any profile whose repos survive to injection has activated on legitimate grounds.
…l model profiles After the sandbox activation context introduced in apache#13114, profiles in dependency POMs that fire legitimately (JDK, OS, activeByDefault, POM-declared property) are now correctly scoped. The blanket withoutRepositories() stripping that applied to ALL active profiles in external builds was over-broad: it broke the established pattern where project → dep1 → dep2 and dep1 knows dep2 is not on Central, declaring its repository inside a JDK- or activeByDefault-activated profile. Remove the withoutRepositories() call and the dead helper method from DefaultModelBuilder. The sandbox already ensures that profiles activated by consumer -D flags or file conditions do not fire in external builds, so any profile whose repos survive to injection has activated on legitimate grounds.
…el builds Backport of apache#13112 to `maven-3.10.x`. Cherry-picked and manually adapted from the master commits: - e58a96a Fix apache#13084: sandbox profile activation context for external model builds - 8ff7ca6 Address review: fix Javadoc method ref and file-activation asymmetry - f2bf110 fix: move model property lookup from activator to sandbox context The second commit (c639b4f) was empty on this branch (it only touched impl/maven-impl/ which doesn't exist on maven-3.10.x; the equivalent adjustment was already included in the first commit's ResolvedDependencyProfileActivationTest). Conflicts resolved by adapting to the flat module layout of maven-3.10.x: - Changes to compat/maven-model-builder/ cherry-picked cleanly as maven-model-builder/ - Changes to impl/maven-impl/ have no direct equivalent (not removed — confirmed n/a) - Changes to its/core-it-suite/ removed (ITs are in a separate repo on this branch)
a10fb4e to
f97b621
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of rebased/squashed commit (previously approved a10fb4e, now f97b62193a).
The squash absorbed the test-fix commit (a10fb4e) into the main commit cleanly. The design is solid: sandboxing via an anonymous ProfileActivationContext rather than modifying PropertyProfileActivator is the right call — it keeps the activator stateless and avoids unintended side effects on project builds.
One performance issue on the getSystemProperties() override.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| public Map<String, String> getSystemProperties() { | ||
| Map<String, String> projectProps = delegate.getProjectProperties(); | ||
| if (projectProps == null || projectProps.isEmpty()) { | ||
| return delegate.getSystemProperties(); | ||
| } | ||
| Map<String, String> merged = new HashMap<>(projectProps); | ||
| merged.putAll(delegate.getSystemProperties()); // system wins | ||
| return merged; |
There was a problem hiding this comment.
getSystemProperties() allocates a new HashMap on every call.
OperatingSystemProfileActivator calls this method 3× per profile (name, arch, version). PropertyProfileActivator and JdkVersionProfileActivator call it once each. In a project with deep transitive deps and profiles using OS activation, this is O(deps × profiles × |systemProperties|) heap allocation per build.
Since the merged map is derived from immutable inputs (system properties don't change mid-build, project properties don't change mid-build), it can be computed once and cached:
| public Map<String, String> getSystemProperties() { | |
| Map<String, String> projectProps = delegate.getProjectProperties(); | |
| if (projectProps == null || projectProps.isEmpty()) { | |
| return delegate.getSystemProperties(); | |
| } | |
| Map<String, String> merged = new HashMap<>(projectProps); | |
| merged.putAll(delegate.getSystemProperties()); // system wins | |
| return merged; | |
| public Map<String, String> getSystemProperties() { | |
| Map<String, String> projectProps = delegate.getProjectProperties(); | |
| if (projectProps == null || projectProps.isEmpty()) { | |
| return delegate.getSystemProperties(); | |
| } | |
| Map<String, String> merged = new HashMap<>(projectProps); | |
| merged.putAll(delegate.getSystemProperties()); // system wins | |
| return Collections.unmodifiableMap(merged); |
Alternatively, compute the map lazily once and store it in a final field of the anonymous class:
private static ProfileActivationContext externalActivationContext(ProfileActivationContext delegate) {
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());
mergedSystemProps = Collections.unmodifiableMap(merged);
}
return new ProfileActivationContext() {
// ...
@Override
public Map<String, String> getSystemProperties() {
return mergedSystemProps;
}
// ...
};
}The latter approach is preferred: one allocation per external model instead of one per getSystemProperties() call. The unmodifiableMap wrapper also makes the contract explicit — the returned map must not be mutated (currently nothing mutates it, but there's no enforcement).
There was a problem hiding this comment.
Fixed in f80e41d. Pre-computed the merged map before the anonymous class — one allocation per external model instead of one per getSystemProperties() call. Also wrapped the result in Collections.unmodifiableMap() to make the no-mutation contract explicit.
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of f80e41d33a (performance fix commit).
Prior finding: getSystemProperties() allocated a new HashMap on every call — addressed. The pre-computed mergedSystemProps is captured before the anonymous class construction, wrapped in Collections.unmodifiableMap(), and returned by value on every call. Zero allocation per getSystemProperties() invocation — correct fix.
Test coverage gap (non-blocking): testExternalModelNegatedPropertyDefaultOnIsPreserved tests the "property absent → profile fires" path, but there is no test for the suppression direction — a consumer setting skip.defaults as a system property (not -D) would suppress the dependency's default-on profile, since system properties pass through the sandbox. This is intentional behavior (system props are platform facts), but it's worth a test case to lock it in.
All other findings from prior reviews are addressed. Design is sound.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
…efault-on profile Port test coverage gap flagged in gnodet-bot re-review: - Add testExternalModelSystemPropertySuppressesDefaultOnProfile: verifies that a consumer setting skip.defaults as a *system* property suppresses the dependency's default-on (!skip.defaults) profile in external builds. System properties pass through the sandbox unchanged; this is intentional behavior but was unprotected by a test.
…efault-on profile Port test coverage gap from the apache#13114 review: - Add negated-property-default-on profile to the XML fixture - Assert it fires in project builds (skip.defaults absent from system props) - Add testExternalModelSystemPropertySuppressesDefaultOnProfile: verifies that a consumer setting skip.defaults as a *system* property suppresses the dependency's default-on profile (system props pass through the sandbox)
…le repositories in external models)
…efault-on profile Port test coverage gap from the apache#13114 review: - Add negated-property-default-on profile to the XML fixture - Assert it fires in project builds (skip.defaults absent from system props) - Add testExternalModelSystemPropertySuppressesDefaultOnProfile: verifies that a consumer setting skip.defaults as a *system* property suppresses the dependency's default-on profile (system props pass through the sandbox)
|
@cstamas Please assign appropriate label to PR according to the type of change. |
…el builds Backport of apache#13112 (master) and apache#13114 (maven-3.10.x) to maven-4.0.x. Instead of blanket-filtering file and property profiles in external model builds (dependency POMs, parent POMs, imported BOMs), evaluate them against a sandboxed ProfileActivationContext that: - Merges model properties into system property lookups (so POM-declared <properties> drive property-activated profiles in dependency builds). - Suppresses user properties (consumer -D flags must not activate dependency profiles — they were not set for that artifact). - Pre-filters file-activated profiles (returning false from exists() would incorrectly activate <missing> profiles). - Strips repository contributions from all active external profiles. Both compat and impl stacks are updated identically. The key insight is that model properties are merged into system properties inside the sandbox rather than changing PropertyProfileActivator's lookup chain, to avoid unintended activation when a project POM declares a property that coincidentally matches a profile's activation condition. Tests: ExternalModelProfileActivationTest (compat) and DefaultModelBuilderTest (impl) cover file suppression, user-property suppression, system-property preservation, POM-declared-property activation, negated-property default-on preservation, and system-property opt-out of the default-on pattern.
…lds (#13158) * Fix #13084: sandbox profile activation context for external model builds Backport of #13112 (master) and #13114 (maven-3.10.x) to maven-4.0.x. Instead of blanket-filtering file and property profiles in external model builds (dependency POMs, parent POMs, imported BOMs), evaluate them against a sandboxed ProfileActivationContext that: - Merges model properties into system property lookups (so POM-declared <properties> drive property-activated profiles in dependency builds). - Suppresses user properties (consumer -D flags must not activate dependency profiles — they were not set for that artifact). - Pre-filters file-activated profiles (returning false from exists() would incorrectly activate <missing> profiles). - Strips repository contributions from all active external profiles. Both compat and impl stacks are updated identically. The key insight is that model properties are merged into system properties inside the sandbox rather than changing PropertyProfileActivator's lookup chain, to avoid unintended activation when a project POM declares a property that coincidentally matches a profile's activation condition. Tests: ExternalModelProfileActivationTest (compat) and DefaultModelBuilderTest (impl) cover file suppression, user-property suppression, system-property preservation, POM-declared-property activation, negated-property default-on preservation, and system-property opt-out of the default-on pattern. * Address review: fix mutable map leak in compat sandbox context, pre-filter missing()-condition profiles in CONSUMER_DEPENDENCY builds
Backport of #13112 to
maven-3.10.x.Cherry-picked and manually adapted from the master commits:
Conflicts resolved by adapting to the flat module layout of maven-3.10.x:
compat/maven-model-builder/→maven-model-builder/(flat layout)impl/maven-impl/changes have no equivalent on 3.10.x (not present)its/core-it-suite/changes dropped (ITs are in a separate repo on this branch)See #13112 for the full rationale and design.