7

I'm building a deployment pipeline for a couple of projects that depend on each other. Each build produces a new release build with a unique version number, which is deployed to a Maven repository. The downstream projects in the pipeline are then triggered with that new version as a dependency and built in like manner.

What I need is to change a property value in the pom.xml (or all poms in a multi module project) before building the project. For example in the following code the "0.1.200" would be changed to "0.1.345" (or whatever the latest build number is). Using system properties is not an option, because the updated pom will be deployed in a Maven repository, so the change must be persistent.

<properties>
    <foo.version>0.1.200</foo.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>foo</artifactId>
        <version>${foo.version}</version>
    </dependency>
</dependencies>

Is there some Maven plugin for doing this with one command line instruction? Else I will need to write a short script (e.g in Ruby) which parses and changes all the pom.xml files in the project.

Esko Luontola
  • 73,184
  • 17
  • 117
  • 128

5 Answers5

3

Yes, there exists a maven-replacer-plugin which is capable of this.

But if you're using some CI tool (which apparently you are) you could as well just stick with a custom script for this purpose.

Andrew Logvinov
  • 21,181
  • 6
  • 52
  • 54
3

The available Maven plugins didn't fit my purpose, so I ended up writing the following Ruby script that does exactly what I need:

#!/usr/bin/env ruby
require 'rexml/document'

def change_property(pom, key, value)
  property = pom.elements["/project/properties/#{key}"]
  if property != nil
    puts "    #{key}: #{property.text} -> #{value}"
    property.text = value
  end
end

unless ARGV.length == 2
  puts "Usage: #{$0} KEY VALUE"
  exit 1
end
KEY = ARGV.shift
VALUE = ARGV.shift

Dir.glob("**/pom.xml") { |pom_path|
  puts pom_path

  pom = REXML::Document.new(File.new(pom_path))
  pom.context[:attribute_quote] = :quote
  change_property(pom, KEY, VALUE)

  File.open(pom_path, 'wb') { |file|
    pom.write(file)
  }
  puts
}
Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
1

Did you try this one?

Version Maven Plugin

Will
  • 6,179
  • 4
  • 31
  • 49
  • 2
    That plugin has operations for automatically updating dependencies to their latest versions, but I didn't notice there a way for updating a single property to a specified value. – Esko Luontola Mar 02 '13 at 16:46
  • 2
    The [versions-maven-plugin](http://mojo.codehaus.org/versions-maven-plugin/examples/update-properties.html) is capable of handling such properties. Take a look into the docs. – khmarbaise Mar 02 '13 at 17:29
  • 2
    The problem with versions:update-properties is that it automatically tries to detect what is the latest version. In a deployment pipeline that is not good, because it may accidentally cause Maven to find a too old or new version. To avoid unrepeatable builds, I require explicitness of what version to use. – Esko Luontola Mar 02 '13 at 18:32
  • mvn versions:update-properties -Dproperties=[1] -DincludeProperties={foo.version} – OhadR Oct 21 '14 at 11:58
  • @OhadR have you had confirmed success running with that syntax? It doesn't work for a colleague and myself, I get the message `Property ${foo.version}: Leaving unchanged as 1.103.0` in the output, he gets no output. Might be worth noting, we're trying to set the value to an arbitrary version that can't be checked against a server for validity. – wonderfulthunk Mar 02 '15 at 22:51
  • of course it is working, you can see in the [manuals of the plugin itself](http://mojo.codehaus.org/versions-maven-plugin/update-properties-mojo.html). BTW try to omit the -Dproperties... in addition, **do u have that version in your repository?** (run maven with debug logs) – OhadR Mar 03 '15 at 06:15
  • thanks @OhadR, I think that points to my problem. I don't have that version in my repository, it's an arbitrary version that doesn't exist yet. I think this plugin is doing too much (as far as actually validating that version exists in a remote or local repository) and we'll need to just sed the pom file with our arbitrary value. – wonderfulthunk Mar 03 '15 at 15:50
  • @wonderfulthunk exactly. this plugin does "too much" - it checks your repo, and I agree that it does not fit all use-cases. In your case I think maven-replacer-plugin is better. – OhadR Mar 03 '15 at 17:42
1

I had a similar requirement. Besides updating the property under the /project/properties node, I also need to update the property under /project/profiles/properties node, so I modify Esko's script to support updating both cases. Meanwhile, it also supports updating multiple properties in one command so you don't have to run it multiple time if you need to update multiple properties in the same pom.xml.

#!/usr/bin/env ruby
require 'rexml/document'

def change_profile_property(pom, profile_id, key, value)
  property = pom.elements["/project/profiles/profile[id='#{profile_id}']/properties/#{key}"]
  if property != nil
    puts "    #{profile_id}-#{key}: #{property.text} -> #{value}"
    property.text = value
  end
end

def change_property(pom, key, value)
  property = pom.elements["/project/properties/#{key}"]
  if property != nil
    puts "    #{key}: #{property.text} -> #{value}"
    property.text = value
  end
end

if ARGV.length == 0
  puts "Usage: #{$0} KEY=VALUE [-profile <profile id>] KEY=VALUE"
  exit 1
end

# parse the command line argument to get the key/value
global_properties = Array.new
profile_properties= Array.new

profile = nil
loop { case ARGV[0]
  when '-profile' then  ARGV.shift; profile=ARGV.shift
  when nil then break
  else 
    kv_str = ARGV.shift
    if profile == nil
      global_properties.push(kv_str)
    else
      profile_properties.push(kv_str)
    end
  end; 
}

Dir.glob("**/pom.xml") { |pom_path|
  puts pom_path

  pom = REXML::Document.new(File.new(pom_path))
  pom.context[:attribute_quote] = :quote

  # updating the global properties
  if global_properties.length != 0
    for kv in global_properties
      kv_array = kv.split('=')
        if kv_array.length == 2
          change_property(pom, kv_array[0], kv_array[1])
        end
    end
  end

  # updating the properties in profile
  if profile_properties.length != 0
    for kv in profile_properties
      kv_array = kv.split('=')
      if kv_array.length == 2
        if profile != nil
          change_profile_property(pom, profile, kv_array[0], kv_array[1])
        end
    end
    end
  end

  File.open(pom_path, 'wb') { |file|
    pom.write(file)
  }
  puts
}
Mingjiang Shi
  • 7,415
  • 2
  • 26
  • 31
0

Flatten Maven Plugin can be used to update the variables. Let me explain with an example.

<groupId>groupid</groupId>
<artifactId>artifactid</artifactId>
<version>${ServiceVersion}</version>
<packaging>pom</packaging>

<properties>
    <foo.version>${ServiceVersion}</foo.version>
</properties>

In pom.xml, I'm using "ServiceVersion" to get the value during the build. The foo.version variable will be updated too during the build. Now, the foo.version variable can be used in any of the dependencies.

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>foo</artifactId>
        <version>${foo.version}</version>
    </dependency>
</dependencies>

So, finally include the Flatten Maven Plugin in the pom.xml

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
    <updatePomFile>true</updatePomFile>
</configuration>
 <executions>
    <execution>
        <id>flatten</id>
        <phase>process-resources</phase>
        <goals>
            <goal>flatten</goal>
        </goals>
    </execution>
    <execution>
      <id>flatten.clean</id>
      <phase>clean</phase>
      <goals>
        <goal>clean</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Now provide the service version.

clean install -DServiceVersion=0.1.345
Ruthwik
  • 545
  • 1
  • 8
  • 14