Repository navigation
Resolve Maven pin properties through profiles and nesting - #2020
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Conversation
|
Burn-down agent: merge conflict needs a decision (left unmerged, nothing pushed). This PR now conflicts with its base
Question: should this PR be rebuilt on #2005's split — keep Generated by Claude Code |
Closes the two Maven false-attestation gaps #2005 left open in VEX discovery. 1. Profile <properties> redefining a pin's property. A live <version>${v}</version> that the root <properties> resolve to the Socket pin resolves something else whenever an active profile's <properties> redefine v (or a property v names): Maven merges active profile properties over the pom's own before interpolating. Vendored mode refuses to pin over such a property (version_override); discovery attested it. PomScope now reads each profile's <properties>, and every PomDep carries version_alternatives: the other versions its build_version takes under any profile or set of profiles active together (user properties and project.* model references stay fixed; a value some set leaves unresolved, cyclic or too deep counts as the text as written). profile_override now walks live main-jar declarations too (same main_jar_declarations scope and management-governs filter): one whose build version is the pin but has an alternative off it marks the ref unattested with the existing UnattestedKind::MavenProfileUnpatched (CLI note and CLI_CONTRACT.md updated), in hosted and the pre-v5 vendored wiring alike. Profile declarations and the shared classifier check (unpatched_classifier_copy) count every alternative as a version the declaration may resolve. A <profiles> declaration's own ${v} reads its own profile's <properties> first. 2. Property versions resolved one level deep. ${a} with <a>${b}</a> now resolves through every property it names, in both views PomDep keeps: `version` (as the pom spells it: model, own profile, root properties) and `build_version` (.mvn/maven.config user properties first at every level; project.* stays the model's). Limits are the vendored reactor's: at most 16 levels, and a cycle, undefined name or unterminated ${ never resolves (the version stays as written, which is never a pin). A partly literal version (${a}.${b}-x) resolves too, and ${project.version} resolves from the model in both views. The vendored reactor's interpolator (interpolate_by and the duplicate body of Reactor::interpolate_with) moves to formats/maven as the pure interpolate_strict, built on interpolate_each, which enumerates the values a name may take (capped at 16 results). Both now detect cycles explicitly and bound work (1024 lookups, 4096 bytes), so a pom whose properties each name the next many times over fails closed instead of expanding exponentially. Reactor::interpolate drops its always-zero depth argument. Vendored behavior is otherwise unchanged. Tests (each fails before this change): - vex::discover::maven::tests::profile_properties_redefining_a_pin_property_are_not_attested - vex::discover::maven::tests::profile_declaration_reads_its_own_profile_properties - vex::discover::maven::tests::version_property_resolves_through_nested_properties (16-deep chain resolves, 17 does not; two cycles; nested user overrides) - vex::discover::maven::tests::vendored_ref_under_profile_properties_is_not_attested - formats::maven::tests::{version_properties_resolve_recursively_and_fail_closed, profile_properties_give_version_alternatives, interpolation_is_bounded} The golden corpus is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d7b4b40 to
ec89bd9
Compare
The base branch was force-pushed (review fixes added; MavenConfigOverride split out of profile overrides, build_version beside version, classifier handling moved into unpatched_classifier_copy). Resolved against the old base commit d7b4b40 so only this PR's changes are re-applied: - coordinate_override now runs from bypasses after profile_override, and reads build_version (as the base's live-declaration checks do) and is_executable_classifier instead of the removed list. - coordinate_alternatives reads the no-profile baseline with user properties (lookup's new build flag), matching lookup_any. - CLI_CONTRACT and the profile note keep both sides' wording. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011f9aePp9J8NPBFw7TPjhfT
Closes the last Maven property false-attestation gap in VEX discovery after #2020: a <groupId> / <artifactId> written as ${prop} that a profile's <properties> redefine. Coordinates were read only through the lenient interpolator, from the root <properties> (with .mvn/maven.config user properties over them), so discovery attested two shapes the build may not resolve: (a) The declaration carrying the pin names another package whenever a profile redefining its coordinate property is active; the pin then no longer applies to the patched GA, which resolves from its other declarations or transitively, unpinned. (b) Another declaration (live, or inside a profile whose own properties retarget it) becomes the patched GA under a profile, at its own version off the pin. formats/maven: interpolate_each_keeping is interpolate_each with the lenient reading's semantics (an undefined name, cycle, chain past 16 levels or unterminated ${ stays as written, so may_name reads it as any text; None only past the work bounds). PomDep now carries coordinate_alternatives: every (groupId, artifactId) pair the declaration may name under any profile or set of profiles (its own profile's properties first for a profile declaration), minus what it reads with no profile active. User properties and project.* model references stay fixed, as for versions. ScopedDep.group / artifact (the lenient base the hosted rewriter also reads) are unchanged; they gain the raw texts. vex/discover/maven: bypasses() also runs coordinate_override (after profile_override), reported as the existing MavenProfileUnpatched, for (a) the live main-jar declaration carrying the pin (build_version == pin) with any alternative, and (b) any declaration with an alternative that may be the GA's main jar (as main_jar_declarations reads one: literal, or may_shadow; pom / sources / javadoc types skipped) at a version other than the pin among its resolutions(), or literally an executable classifier copy off the pin or with no version of its own. As in profile_override, a managed declaration counts only where management governs the pin, and a main jar without a version takes the live pin and does not withhold. Applies to hosted and the pre-v5 vendored wiring. Coordinates resolve in the build's view (user properties first). CLI note and CLI_CONTRACT.md updated. vendor/jvm/maven_reactor: the v5 vendored planner had the same gap. coordinates_elsewhere enumerates inheritors and every profile on the chain (one at a time, as version_override does). (a) The existing coordinates_overridden check now weighs profiles too, not only inheritors: a declaration a profile moves off the GA is left as is and its root unpinned. (b) New warn_moved_coordinates: a declaration that names another artifact in its pom but the GA in an inheritor or under a profile is warned (coordinates_overridden) and, when versioned, unpins its root and is uncertain, so attestable_checked no longer attests it. This also covers the inheritor form of (b), which was likewise unseen. Tests (each fails before this change): - vex::discover::maven::tests::profile_properties_moving_the_pinned_coordinates_are_not_attested - vex::discover::maven::tests::profile_properties_turning_a_declaration_into_the_pinned_ga_are_not_attested - vendor::jvm::maven_reactor::tests::a_profile_moving_the_declared_coordinates_is_left_alone - vendor::jvm::maven_reactor::tests::a_profile_turning_a_declaration_into_the_patched_artifact_is_weighed New API: formats::maven::tests::{keeping_interpolation_leaves_unresolved_references, profile_properties_give_coordinate_alternatives}. Guard: vex::discover::maven::tests::unrelated_coordinate_properties_do_not_withhold. User-property variants (a chain through -D, -D beating the profile) are in each. The golden corpus is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review findings on the profile / nested property resolution (#2020): 1. Model references moved by properties (false attestation). The model (${project.version} from <version>${revision}</version>) was bound already resolved from the root properties, and profiles were assumed unable to change it. Model references are now bound raw and resolved wherever they are read, with that read's user properties and active profiles: a profile <revision> moves ${project.version} (unattested), and a -Drevision=<pin> only .mvn/maven.config supplies makes the spelled and built versions differ, so it is diagnosed, never a ref. 2. Attributed / spaced property tags (false attestation). read_properties took the whole tag as the name, so <v combine.self="override"> and <v > were skipped. The name now ends at whitespace or '/', closing tags may carry whitespace, and <v/> defines an empty value. 3. Nested references in a profile value resolved against the root. Each combination of profiles is now resolved as one property view (the profiles merged over the pom's, a later one winning), so a profile's ${w} reads its own <w> first. 4. Profile <properties> parse errors failed the whole scope, including the hosted rewriter's. PomScope::with_user_properties no longer reads profile properties; parse_pom_with calls read_profiles(), and an unreadable one withholds every ${...} pin (MovedBy::Unreadable) while the rest of the pom is read. 5. The shared cross-product budget collapsed ordinary multi-profile poms. interpolate_each and its 16-result / 1024-lookup product are gone: profile_combinations walks the names reachable from the version, keeps only the profiles that define one (at most 8, else fail closed), and every combination of those is resolved on its own budget. 6. The detail named the lexicographically first alternative. Alternatives are listed smallest combination first and carry the profile ids (MovedBy), and the detail names them and the version they give. 7. Two precedence implementations. formats::maven::properties now holds lookup_property (Maven's order: model, user properties, each pom's properties with its active profiles merged in, bare ${version}), PomLayer and profile_combinations; PomScope and the vendored Reactor::lookup_with / version_override both use them, so vendoring now weighs the same combinations VEX does (it previously tried one profile at a time) and fails closed past the same bound. 8. Two interpolators. The round-based coordinate interpolate is gone: coordinates and model references go through the same expansion as versions, in a keep-unresolved mode (interpolate_lenient). Coordinate alternatives stay for #2026; PomScope::resolve_in_build takes the interpolation so it can reuse it. 9. Cleanups: resolutions() drops its from-profile flag (the classifier check derives it), PomScope::fixed and the profile-set guard are gone, and readers that never weigh profiles do not scan them. Tests (each failed before this commit): - vex::discover::maven::tests::model_references_move_with_the_properties_they_are_spelled_through - vex::discover::maven::tests::profile_properties_with_attributes_are_read - vex::discover::maven::tests::profile_values_resolve_through_their_own_profile - vex::discover::maven::tests::unreadable_profile_properties_withhold_only_property_pins - vex::discover::maven::tests::many_profiles_that_keep_the_pin_are_attested - vex::discover::maven::tests::profile_property_detail_names_the_profile_and_version - vendor::jvm::maven_reactor::tests::profiles_that_move_the_version_only_together_are_weighed plus formats::maven::properties unit tests and extended formats::maven::tests::profile_properties_give_version_alternatives. The golden corpus is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes the last Maven property false-attestation gap in VEX discovery after #2020: a <groupId> / <artifactId> written as ${prop} that a profile's <properties> redefine. Coordinates were read only with no profile active, so discovery attested two shapes the build may not resolve: (a) The declaration carrying the pin names another package whenever a profile redefining its coordinate property is active; the pin then no longer applies to the patched GA, which resolves from its other declarations or transitively, unpinned. (b) Another declaration (live, or inside a profile whose own properties retarget it) becomes the patched GA under a profile, at its own version off the pin. Both readers now resolve coordinates through #2020's shared helpers (formats::maven::properties): the groupId and artifactId are joined with COORDINATE_SEPARATOR (U+0001, which XML 1.0 forbids) so one profile_combinations walk and one interpolation per combination read both under the same active profiles, through interpolate_keeping (the keep-unresolved interpolate_lenient as an Interpolate), and split_coordinates splits the result. formats/maven: PomScope::coordinate_alternatives runs the joined text through resolve_in_build; PomDep carries group_text / artifact_text and coordinate_alternatives (CoordinateAlternative: group, artifact, MovedBy), every pair some combination gives other than the no-profile reading (ScopedDep.group / artifact, unchanged), plus a profile declaration's own-profile reading. More than 8 relevant profiles or an unreadable profile fail closed (the text as written, which may_name reads as any text). vex/discover/maven: bypasses() runs coordinate_override after profile_override, reported as MavenProfileUnpatched (detail names the profiles), for (a) the live main-jar declaration carrying the pin (build_version == pin) with any alternative, and (b) any declaration with an alternative that may be the GA's main jar (literal or may_shadow; pom / sources / javadoc types skipped) at a version other than the pin among its resolutions(), or literally an executable classifier copy off the pin or with no version of its own. As in profile_override, a managed declaration counts only where management governs the pin, and a versionless main jar takes the live pin. CLI note and CLI_CONTRACT.md updated. vendor/jvm/maven_reactor: the v5 planner had the same gap. interpolate_with and profile_override take the Interpolate to use (interpolate_strict for versions, as before), and coordinates_elsewhere reads the joined coordinates in each inheritor and under each profile combination on the chain through them. (a) The existing coordinates_overridden check now weighs profiles, not only inheritors. (b) New warn_moved_coordinates: a declaration that names another artifact in its pom but may be the GA in an inheritor or under a profile combination is warned (coordinates_overridden) and, when versioned, unpins its root and is uncertain, so attestable_checked no longer attests it. This also covers the inheritor form of (b). Tests (each fails before this change): - vex::discover::maven::tests::profile_properties_moving_the_pinned_coordinates_are_not_attested - vex::discover::maven::tests::profile_properties_turning_a_declaration_into_the_pinned_ga_are_not_attested - vendor::jvm::maven_reactor::tests::a_profile_moving_the_declared_coordinates_is_left_alone - vendor::jvm::maven_reactor::tests::a_profile_turning_a_declaration_into_the_patched_artifact_is_weighed - formats::maven::tests::profile_properties_give_coordinate_alternatives Guard: vex::discover::maven::tests::unrelated_coordinate_properties_do_not_withhold. User-property variants (a chain through -D, -D beating the profile) are in each. The golden corpus is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…property-overrides Resolve the classifier-copy conflict by keeping both sides: this branch's per-resolution profile flags (flagged/resolutions) and the base's rule that a managed entry naming the GA only through an undefined property never stands in for the copy's version alone. Co-Authored-By: Claude <noreply@anthropic.com>
…ation' into fix/maven-discovery-property-overrides # Conflicts: # crates/socket-patch-cli/CLI_CONTRACT.md # crates/socket-patch-core/src/vex/discover/maven.rs
Stacked on #2005, including its review fix (9765178). This closes the two Maven false-attestation gaps #2005 left open in VEX discovery. It also gives VEX discovery and the vendored planner one property resolver.
1. Profile
<properties>redefining a pin's propertyTake a live
<version>${v}</version>whose build resolution is the Socket pin. If a profile's<properties>redefinev, or any propertyvnames, Maven resolves something else while that profile is active. Maven activates any number of profiles together, so combinations count too. Vendored mode refuses to pin over such a property (version_override), but discovery attested it.parse_pom_withreads every profile's<properties>.PomScope::resolve_in_buildthen lists each value a combination of profiles gives the version.PomDepcarries those values asversion_alternatives, smallest combination first. Each alternative records the profile ids that give it.resolutions(d)helper plugs these into Stop attesting Maven pins a profile or user property overrides #2005's helpers in three places:unpatched_classifier_copy: classifier copies and their classifier-keyed management.profile_override, profile declarations: every version they can take is checked against the pin.profile_override, live declarations: live main-jar declarations are walked too, with the samemain_jar_declarationsscope and management-governs filter.UnattestedKind::MavenProfileUnpatched. The detail names the profiles and the version they give. This applies to hosted pins and the pre-v5 vendored wiring alike.<profiles>declaration's own${v}reads its own profile first.2. Property versions resolved one level deep
${a}with<a>${b}</a>now resolves through every property it names. That includes${project.version}, which is bound raw (for example${revision}) and resolved wherever it is read. Both of #2005's views use it:version(as the pom spells it): the model, then the declaration's own profile, then the root<properties>.build_version:.mvn/maven.config-Duser properties first, at every level.Limits: at most 16 levels, and a cycle, an undefined name or an unterminated
${never resolves. The version then stays as written, which is never a pin.One resolver:
formats::maven::properties(pure)lookup_property/PomLayer: Maven's lookup order. The model comes first, then user properties, then each pom's properties with its active profiles merged in (nearest pom first), then the bare${version}.profile_combinations: the shared enumeration of profile combinations.interpolate_strict/interpolate_lenient: one expansion with explicit cycle detection, depth 16, 1024 lookups and 4096 bytes. Strict is for versions. Lenient, for coordinates and model references, keeps an unresolved${…}visible formay_name.read_properties: the<properties>parser.PomScopeand the vendoredReactor::lookup_with/version_overrideboth use this module. Vendoring now weighs the same profile combinations VEX does, where it used to try one profile at a time, and fails closed past the same bound. The old round-based coordinate interpolator andinterpolate_eachare gone.resolve_in_buildtakes the interpolation function, so #2026 can reuse it for coordinate alternatives.Review findings
All 9 are confirmed and fixed. Each behavior finding has a test that failed before
854c3f682.<revision>moves${project.version}(unattested). A-Drevision=<pin>supplied only bymaven.confignow makes the spelled and built versions differ, so it is diagnosed, never a ref. Test:model_references_move_with_the_properties_they_are_spelled_through./, closing tags may carry whitespace, and<v/>defines an empty value. Tests:profile_properties_with_attributes_are_readandproperties::tests::properties_read_attributed_and_spaced_tags.profile_values_resolve_through_their_own_profile.<properties>parse error failed the whole scope.with_user_propertiesno longer scans profiles, so the hosted rewriter is unaffected. Discovery records the error and withholds only${…}pins (MovedBy::Unreadable); a literal pin is still attested. Test:unreadable_profile_properties_withhold_only_property_pins, plus a formats assertion.many_profiles_that_keep_the_pin_are_attested(5 profiles × a 5-deep chain → attested).profile_property_detail_names_the_profile_and_version.lookup_propertyandprofile_combinations. Test:vendor::jvm::maven_reactor::tests::profiles_that_move_the_version_only_together_are_weighed, which failed before because the planner tried one profile at a time.resolutions()drops its from-profile flag (the classifier check derives it),PomScope::fixedand the redundant guard are gone, and readers that never weigh profiles don't scan them.Two behavior changes come from sharing the resolver:
${project.*}lookups fall through to user and pom properties when the model has no value, as in Maven.Tests
Earlier tests:
profile_properties_redefining_a_pin_property_are_not_attestedprofile_declaration_reads_its_own_profile_propertiesversion_property_resolves_through_nested_propertiesvendored_ref_under_profile_properties_is_not_attestedformats::maven::tests::{version_properties_resolve_recursively_and_fail_closed, profile_properties_give_version_alternatives, interpolation_is_bounded}Changed expectation after the rebase onto #2005's review fix. In
version_property_resolves_through_nested_properties, a pin supplied only by-Dp1=<pin>inmaven.config, through a nested property, now expects no attestation. A Socket version that onlymaven.configsupplies is notpom.xmlwiring, so it is diagnosed, never a ref.Runs on
854c3f682:cargo test -p socket-patch-core --lib -- vex::discover:: formats::maven vendor::jvm::passes 726.patch::redirect::together withcrawlers::mavenpasses 953.committed_fixture_corpus_matches_goldenpasses).cargo test -p socket-patch-cli --lib vex_sourcespasses 14.cargo clippy --locked --workspace --all-features -- -D warningsandcargo fmt --all -- --checkare clean.Not done
Generated by Claude Code