Skip to content

${...} expressions in CLI-supplied plugin parameter values are wiped to empty at CLI parse time (Maven 3 resolves them at mojo configuration) #12507

Description

@ascheman

Affected versions

Reproduced on both master (4.1.0-SNAPSHOT, 671031e) and maven-4.0.x tip (4.0.0-SNAPSHOT, e1f8234). On 4.0.0-rc-5 the symptom is masked by the launcher bug #11978 (evalbad substitution), already fixed for rc-6 — this issue is about what happens once Maven actually runs.

Reproduction

Minimal pom-packaging project, then (quoted so the shell passes ${...} literally):

mvn deploy '-DaltDeploymentRepository=test::file://${project.build.directory}/deploy'
  • Maven 3.9.16 (deploy-plugin pinned to 3.1.3 via direct goal invocation): resolves the expression, deploys to target/deploy/…
  • Maven 4 (same deploy-plugin 3.1.3): the expression evaluates to the empty string → repository URL becomes file:///deployCould not transfer artifact … from/to test (file:///deploy): /deploy: Read-only file system

Further probes — all wiped to empty: ${project.build.directory}, ${project.basedir}, and even ${user.dir} (a plain system property).

Analysis

The values are destroyed at CLI parse time, before any project context exists:

  • BaseParser.populateUserProperties() interpolates all user-specified -D values against only the other user properties plus the small extraInterpolationSource() paths map — no system properties, no project context (impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java:441).
  • It uses the Interpolator.interpolate(Map, UnaryOperator) convenience overload, which defaults to defaultsToEmpty = true (api/maven-api-core/…/Interpolator.java:51-53), so DefaultInterpolator replaces every unresolvable placeholder with "" (impl/maven-impl/…/model/DefaultInterpolator.java:351-353).
  • The later v3-compat PluginParameterExpressionEvaluator would resolve these expressions fine (it merges user + system properties and recursively evaluates embedded ${...}), but it only ever sees the already-emptied value.

This is independent of (and complementary to) the recent launcher quoting fixes: #11978/#11983 fixed the shell layer so ${...} arguments arrive intact in the JVM — the failing IT below passes arguments programmatically via the Verifier (no shell involved) and still shows the wipe. Notably, on rc-5 the launcher bug masked this issue with a loud bad substitution shell error; with the launcher fix shipping in rc-6, users will instead hit this silent empty-substitution first.

The parse-time interpolation itself was introduced deliberately by #2480 ("User properties are not interpolated for paths") to support ${session.topDirectory}/${session.rootDirectory} in -D values — the regression is the destructive defaultsToEmpty=true for every other expression, and the asymmetry with the options interpolation (which does include systemProperties::get in its callback chain, BaseParser.java:178-184).

A minimal fix candidate: pass defaultsToEmpty=false for user-specified properties (leaving unresolved placeholders literal for later mojo-parameter evaluation), possibly plus systemProperties::get in the callback chain. Happy to turn that into a PR.

Impact

A common CI pattern (file-based staging deploy via altDeploymentRepository) breaks, and the failure mode is nasty: no error, Maven silently targets a wrong absolute path (/deploy). maven-assembly-plugin already had to work around this in its ITs (apache/maven-assembly-plugin#1325). Any -D-supplied plugin parameter whose value embeds ${...} is affected, not just deploy.

Test coverage

No existing core IT covers this. I have a failing IT ready to contribute (two cases: ${user.dir} and ${project.build.directory} in a CLI-supplied parameter, asserting Maven 3 semantics via maven-it-plugin-configuration); it fails on current master with expected: <PRE-…-POST> but was: <PRE--POST>. PR follows.

cc @gnodet — given the launcher fix lands in rc-6, this becomes the first user-visible failure mode for the ${...}-in--D pattern there.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions