[maven-2] Maven2 property that indicates the parent directory

I have a multi-modules project, like this one:

main-project/
    module1/
    module2/
        sub-module1/
        sub-module2/
        sub-module3/
        ...
    module3/
    module4/
    ...

I need to define a set of properties (that are dependent of the environment on which I want to release my project) in Maven2. I will not use <properties> as there is lots of properties... Thus, I use the Properties Maven2 plugin.

The properties files are located in the main-project/ directory. How can I set the correct directory in the main pom.xml, in order to specify to any children where to find the properties file?

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
    <version>1.0-alpha-1</version>
    <executions>
        <execution>
            <phase>initialize</phase>
            <goals>
                <goal>read-project-properties</goal>
            </goals>
            <configuration>
                <files>
                    <file>???/env_${env}.properties</file>
                </files>
            </configuration>
        </execution>
    </executions>
</plugin>

If I set only <file>env_${env}.properties</file>, then when Maven2 compiles the first module, it will not find the main-project/env_dev.properties file. If I set <file>../env_${env}.properties</file>, then an error will be raised at the parent level, or at any sub-module level...

This question is related to maven-2 properties

The answer is


The following small profile worked for me. I needed such a configuration for CheckStyle, which I put into the config directory in the root of the project, so I can run it from the main module and from submodules.

<profile>
    <id>root-dir</id>
    <activation>
        <file>
            <exists>${project.basedir}/../../config/checkstyle.xml</exists>
        </file>
    </activation>
    <properties>
        <project.config.path>${project.basedir}/../config</project.config.path>
    </properties>
</profile>

It won't work for nested modules, but I'm sure it can be modified for that using several profiles with different exists's. (I have no idea why there should be "../.." in the verification tag and just ".." in the overriden property itself, but it works only in that way.)


In my case it works like this:

...
<properties>
  <main_dir>${project.parent.relativePath}/..</main_dir>
</properties>
...

<plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>properties-maven-plugin</artifactId>
        <version>1.0-alpha-1</version>
        <executions>
          <execution>
            <phase>initialize</phase>
            <goals>
              <goal>read-project-properties</goal>
            </goals>
            <configuration>
              <files>
                 <file>${main_dir}/maven_custom.properties</file>
              </files>
            </configuration>
          </execution>
        </executions>
</plugin>

Another alternative:

in the parent pom, use:

<properties>
   <rootDir>${session.executionRootDirectory}</rootDir>
<properties>

In the children poms, you can reference this variable.

Main caveat: It forces you to always execute command from the main parent pom directory. Then if you want to run commands (test for example) only for some specific module, use this syntax:

mvn test --projects

The configuration of surefire to parametize a "path_to_test_data" variable may then be:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${surefire.plugin.version}</version>
    <configuration>
        <systemPropertyVariables>
            <path_to_test_data>${rootDir}/../testdata</path_to_test_data>
        </systemPropertyVariables>
    </configuration>
</plugin>

You are in project C, project C is submodule of B and B is submodule of A. You try to reach module D's src/test/config/etc directory from project C. D is also submodule of A. The following expression makes this possible to get the URI path:

-Dparameter=file:/${basedir}/../../D/src/test/config/etc

Use directory-maven-plugin with directory-of goal.

Unlike other suggestions:

  • This solution works for multi-module projects.
  • It works whether you build the whole project or a sub-module.
  • It works whether you run maven from the root folder or a sub-module.
  • There's no need to set a relative path property in each and every sub-module!

The plugin lets you set a property of your choice to the absolute-path of any of the project's modules. In my case I set it to the root module... In my project root pom:

<plugin>
    <groupId>org.commonjava.maven.plugins</groupId>
    <artifactId>directory-maven-plugin</artifactId>
    <version>0.1</version>
    <executions>
        <execution>
            <id>directories</id>
            <goals>
                <goal>directory-of</goal>
            </goals>
            <phase>initialize</phase>
            <configuration>
                <property>myproject.basedir</property>
                <project>
                    <groupId>com.my.domain</groupId>
                    <artifactId>my-root-artifact</artifactId>
                </project>
            </configuration>
        </execution>
    </executions>
</plugin>

From then on, ${myproject.basedir} in any sub-module pom always has the path of the project root module. And of course, you can set the property to any module, not just the root...


So the problem as I see it is that you can't get the absolute path to a parent directory in maven.

<rant> I've heard this talked about as an anti-pattern, but for every anti-pattern there is real, legitimate use case for it, and I'm sick of maven telling me I can only follow their patterns.</rant>

So the work around I found was to use antrun. Try this in the child pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>getMainBaseDir</id>
            <phase>validate</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <exportAntProperties>true</exportAntProperties>
                <target>
                    <!--Adjust the location below to your directory structure -->
                    <property name="main.basedir" location="./.." />
                    <echo message="main.basedir=${main.basedir}"/>
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

If you run mvn verify you should see something like this:

main:
     [echo] main.basedir=C:\src\parent.project.dir.name

You can then use ${main.basedir} in any of the other plugins, etc. Took me a while to figure this out, so hope it helps someone else.


I think that if you use the extension pattern used in the example for findbugs plugin & multimodule you may be able to set global properties related to absolute paths. It uses a top

example for multi module

The top level pom has an unrelated build-config project and a app-parent for the modules of the multimodule project. The app-parent uses extension to link itself to the build-config project and obtain resources from it. This is used to carry common config files to the modules. It may be a conduit for properties as well. You could write the top dir to a property file consumed by the build-config. (it seems too complex)

The problem is that a new top level must be added to the multi-module project to make this work. I tried to side step with a truly unrelated build-config project but it was kludgy and seemed brittle.


I accessed the dir above using ${basedir}..\src\


I've found a solution to solve my problem: I search the properties files using the Groovy Maven plugin.

As my properties file is necessarily in current directory, in ../ or in ../.., I wrote a small Groovy code that checks these three folders.

Here is the extract of my pom.xml:

<!-- Use Groovy to search the location of the properties file. -->
<plugin>
    <groupId>org.codehaus.groovy.maven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>1.0-rc-5</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
                <source>
                    import java.io.File;
                    String p = project.properties['env-properties-file'];
                    File f = new File(p); 
                    if (!f.exists()) {
                        f = new File("../" + p);
                        if (!f.exists()) {
                            f = new File("../../" + p);
                        }
                    }
                    project.properties['env-properties-file-by-groovy'] = f.getAbsolutePath();
            </source>
            </configuration>
        </execution>
    </executions>
</plugin>
<!-- Now, I can load the properties file using the new 'env-properties-file-by-groovy' property. -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
    <version>1.0-alpha-1</version>
    <executions>
        <execution>
            <phase>initialize</phase>
            <goals>
                <goal>read-project-properties</goal>
            </goals>
            <configuration>
                <files>
                    <file>${env-properties-file-by-groovy}</file>
                </files>
            </configuration>
        </execution>
    </executions>
</plugin>

This is working, but I don't really like it.

So, if you have a better solution, do not hesitate to post!


I've found a solution to solve this problem: use ${parent.relativePath}

<parent>
    <artifactId>xxx</artifactId>
    <groupId>xxx</groupId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>..</relativePath>
</parent>
<build>
    <filters>
        <filter>${parent.relativePath}/src/main/filters/filter-${env}.properties</filter>
    </filters>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

I just improve the groovy script from above to write the property in the root parent properties file:

import java.io.*;
String p = project.properties['env-properties-file']
File f = new File(p)
if (f.exists()) {
try{
FileWriter fstream = new FileWriter(f.getAbsolutePath())
BufferedWriter out = new BufferedWriter(fstream)
String propToSet = f.getAbsolutePath().substring(0, f.getAbsolutePath().lastIndexOf(File.separator))
if (File.separator != "/") {
propToSet = propToSet.replace(File.separator,File.separator+File.separator+File.separator)
}
out.write("jacoco.agent = " + propToSet + "/lib/jacocoagent.jar")
out.close()
}catch (Exception e){
}
}
String ret = "../"
while (!f.exists()) {
f = new File(ret + p)
ret+= "../"
}
project.properties['env-properties-file-by-groovy'] = f.getAbsolutePath()

In an answer to another question I showed how the maven-properties-plugin could be extended to use external property descriptors defined in Maven dependencies.

You could extend that idea to have multiple descriptor jars, each with the environment name as part of the artifactId, containing a ${env}.properties. Then you can use the property to select the appropriate jar and properties file, for example:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>properties-ext-maven-plugin</artifactId>
  <version>0.0.1</version>
  <executions>
    <execution>
      <id>read-properties</id>
      <phase>initialize</phase>
      <goals>
        <goal>read-project-properties</goal>
      </goals>
    </execution>
  </executions>                              
  <configuration>
    <filePaths>
      <!--assume the descriptor project has a file in the root of the jar -->
      <filePath>${env}.properties</filePath>
    </filePaths>
  </configuration> 
  <dependencies>
    <!-- reference the properties jar for the particular environment-->
    <dependency>
      <groupId>some.descriptor.group</groupId>
      <artifactId>env-${env}-descriptor</artifactId>
      <version>0.0.1</version>
    </dependency>
  </dependencies>
</plugin>

At least in current maven version (3.6.0) you can make use of ${maven.multiModuleProjectDirectory}


<plugins>
  <plugin>
    <groupId>org.codehaus.groovy.maven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>1.0</version>
    <executions>
      <execution>
        <phase>validate</phase>
        <goals>
          <goal>execute</goal>
        </goals>
        <configuration>
          <source>
            import java.io.File
            project.properties.parentdir = "${pom.basedir}"
            while (new File(new File(project.properties.parentdir).parent, 'pom.xml').exists()) {
                project.properties.parentdir = new File(project.properties.parentdir).parent
            }
          </source>
        </configuration>
      </execution>
    </executions>
  </plugin>
  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
    <version>1.0-alpha-2</version>
    <executions>
      <execution>
        <phase>initialize</phase>
        <goals>
          <goal>read-project-properties</goal>
        </goals>
        <configuration>
          <files>
            <file>${parentdir}/build.properties</file>
          </files>
        </configuration>
      </execution>
    </executions>
  </plugin>
  ...

This extends romaintaz's answer, which is awesome in that solves the problem and also clearly points out maven's missing functionality. I picked up a later version of the plugin, and added the case where the project could be more than 3 levels deep.

<pluginManagement>
  <plugins>
    ..
    <plugin>
      <groupId>org.codehaus.gmaven</groupId>
      <artifactId>groovy-maven-plugin</artifactId>
      <version>2.0</version>
    </plugin>
    ..
  </plugins>
</pluginManagement>

I elected not to use a property to define the filename. Note if the build.properties is not found this will spin forever. I added a .git dir detection, but didn't want to over complicate the response so it's not shown here.

  <plugin>
      <groupId>org.codehaus.gmaven</groupId>
      <artifactId>groovy-maven-plugin</artifactId>
      <executions>
          <execution>
              <phase>validate</phase>
              <goals>
                  <goal>execute</goal>
              </goals>
              <configuration>
                 <source>
                    import java.io.File;
                    String p = "build.properties";
                    while(true) {
                      File f = new File(p); 
                      if(f.exists()) {
                        project.properties['project-properties-file'] = f.getAbsolutePath();
                        break;
                      }
                      else {
                        p = "../${p}";
                      }
                    }
                </source>
              </configuration>
          </execution>
      </executions>
  </plugin>

I needed to solve similar problem for local repository placed in the main project of multi-module project. Essentially the real path was ${basedir}/lib. Finally I settled on this in my parent.pom:

<repository>
    <id>local-maven-repo</id>
    <url>file:///${basedir}/${project.parent.relativePath}/lib</url>
</repository>

That basedir always shows to current local module, there is no way to get path to "master" project (Maven's shame). Some of my submodules are one dir deeper, some are two dirs deeper, but all of them are direct submodules of the parent that defines the repo URL.

So this does not resolve the problem in general. You may always combine it with Clay's accepted answer and define some other property - works fine and needs to be redefined only for cases where the value from parent.pom is not good enough. Or you may just reconfigure the plugin - which you do only in POM artifacts (parents of other sub-modules). Value extracted into property is probably better if you need it on more places, especially when nothing in the plugin configuration changes.

Using basedir in the value was the essential part here, because URL file://${project.parent.relativePath}/lib did not want to do the trick (I removed one slash to make it relative). Using property that gives me good absolute path and then going relative from it was necessary.

When the path is not URL/URI, it probably is not such a problem to drop basedir.


Did you try ../../env_${env}.properties ?

Normally we do the following when module2 is on the same level as the sub-modules

<modules>
    <module>../sub-module1</module>
    <module>../sub-module2</module>
    <module>../sub-module3</module>
</modules>

I would think the ../.. would let you jump up two levels. If not, you might want to contact the plug in authors and see if this is a known issue.