POM Expression evaluation

Expressions in the pom generally look like this:

${foo}

or

${bar.baz}

The bar in the last example can be a special token: pom, env, or the legacy project which is deprecated
and means the same as pom.
In this case bar is called a prefix and the baz expression is evaluated against the project model
using reflection, so

${pom.version}

evaluates to the value of the <project><version> tag.

If the prefix is env, the expression evaluates to the value of System.getEnv( "baz" );

In all other cases, if the prefix is NOT pom or env, the current context is consulted.
It typically contains the basedir value, the system properties, and any key/value pairs specified with the -Dkey=value commandline option.

When the context does not contain the value for the expression, the POM properties are consulted.

Maven 2.0.x behaviour

In Maven 2.0.x the prefix was discarded if it was pom, project, or env. The remaining expression was evaluated using the following algorithm:

  • check the context; if the key/value exists, use that. If the key exists but the value is null, skip this term, and leave the _${expression} as is.
  • check the model properties. If that value exists, use that.
  • check the model using reflection. If that yields a value, use that.
  • check the environment variables
  • if the expression refers to itself abort. This check is broken in several ways:
    • it tests for string equality but both pom.* and project.* refer to the same object. so "${pom.foo}" evaluating to "${project.foo}
      " will introduce recursion:
    • since it discards the prefix,
      <scm><connection>${scm.connection}</connection></scm>
      
      will introduce recursion.
    • it only checks 1 level deep:
      <properties><foo>${bar}</foo><bar>${foo}</foo></properties>
      
      . Even if this works, try adding another level,
      or even this:
      <properties><foo>${pom.bar}</foo><bar>${foo}</bar></properties>
      
      .

Some unexpected results can occur, and have caused some bugs. Poms containing _${pom.version} or even _${version}
can break the build if -Dversion=foo is supplied on the commandline.

So, expressions like ${version} or ${artifactId}, or even $_{pom.version} can cause all sorts of problems.

Final algorithm.

The algoritm is simple, were it not for backwards compatibility check due to the enormous amount of faulty poms in the repository.
Therefore, we'll use the new approach but still support legacy (broken) poms, though the behaviour may be a little bit different.

The first problem we need to solve is the fact that both ${pom.version} and _${version} are resolved using the same algorithm.
This will solve the problem of commandline arguments and system properties inadvertedly overriding pom attributes, like _${version}.
Only if the expression has a pom. or project. prefix, reflection will be used.

Here's the complete algorithm:

  • determine the prefix: there is only a prefix if the expresssion starts with pom., project., or env.. In all other cases there is no prefix (it equals the empty string).
  • if the prefix is project., log warning about project. prefix deprecation and replace it with the pom. prefix.
  • if the expression has a prefix of pom., evaluate using reflection. If no result found, continue.
  • if the expression has a prefix of env., evaluate using environment vars. If no result found, continue.
  • check the context for the full expression, including prefix. If there was a key, but the value was null, don't touch this expression, but leave it as an expression in the original string, and continue to the next expression. If the key didn't exist, continue.
  • check the model properties for the full expression, including prefix. If the value is null, continue.
  • if all this still didn't yield a result, fall back to legacy behaviour: apply the prefix-stripped expression on the model; both ${version} and _${pom.version} will result in _<project><version>. If this approach yields a result, log a warning about deprecation.

The above algorithm won't fix broken poms that use _${version} as a means to refer to the project version. Technically that expression explicitly tells us NOT to look in the pom
since it doesn't have the pom. prefix. The same goes for groupId and artifactId and similar. Either we make these 'special' tokens, or we just enforce the use of ${pom.X}.

The differences between 2.0 and 2.1 are illustrated by this pom:

<project>
  ...
  <version>1.0</version>
  ...
  <dependencies>
    <dependency>
      ..
      <artifactId>A</artifactId>
      <version>${pom.version}</version>
    </dependency>
    <dependency>
      ..
      <artifactId>B</artifactId>
      <version>${version}</version>
    </dependency>
    <dependency>
      ..
      <artifactId>C</artifactId>
      <version>${env.version}</version>
    </dependency>
    <dependency>
      ..
      <artifactId>D</artifactId>
      <version>${pom.env.version}</version>
    </dependency>
  <dependencies>

  <properties>
    <pom.version>2.0</pom.version>
    <version>3.0</version>
    <env.version>4.0</env.version>
    <pom.env.version>5.0</pom.env.version>
  </properties>
</project>
-Dversion=6.0

on the commandline, and

export version=7.0

as an environment var.

The results are:

Maven version

A

B

C

D

2.0.x

6.0

3.0

6.0

4.0

2.1

1.0

6.0

7.0

5.0

The fact that B is 3.0 for maven 2.0.x and not 6.0 is because -D doesn't override pom properties (unless that's fixed).

So this is quite disturbing. I think that the expected behaviour is displayed in 2.1.

  • No labels

1 Comment

  1. if the prefix is project., log warning about project.

    I would rather prefer to keep "project" as the normal prefix, just as you realized in r553144. Those kind of expressions are used to query the project model which users usually recognize as the pom.xml. Now, if the pom.xml has "project" as its root element why should I start an expression with "pom"? What would be the rationale for this oddity when navigating through the XML tree?