0

Assuming I have a pom.xml containing a parent dependency like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>some-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
</project>

Is there a way I can replace the 1.0.0-SNAPSHOT version programmatically, maybe via a script?

My use case:

  • I have lots of pom.xml files that I need to change the parent version of
  • I'm using git-xargs to make changes across multiple repos, so I can apply a bash script to each pom.xml to change it.

For example, I can do the following, but this will update all occurrences of 1.0.0-SNAPSHOT in the pom.xml, and I want to limit it to just the some-parent artifact that has a version 1.0.0-SNAPSHOT:

git-xargs \
  --branch-name test-branch \
  --github-org <your-github-org> \
  --commit-message "Update pom.xml" \
  sed -i 's/1.0.0-SNAPSHOT/2.0.1/g' pom.xml

As per the git-xargs docs, I can use any type of script to process the pom.xml, bash, python, ruby etc: https://github.com/gruntwork-io/git-xargs#how-to-supply-commands-or-scripts-to-run

UPDATE:

The following xmlstarlet approach works up to a point:

if [[ $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:artifactId' pom.xml) == "some-parent" && $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:version' pom.xml) == "1.0.0-SNAPSHOT" ]]; then xmlstarlet edit -L -N my=http://maven.apache.org/POM/4.0.0 --update '//my:project/my:parent/my:version' --value '2.0.6' pom.xml; fi

xmlstarlet is correctly updating the xml element I want, but its also reordering the xsi:schemaLocation, xmlns and xmlns:xsi at the top of my file.

It's updating it from:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

to:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

which is no good for me.

rmf
  • 625
  • 2
  • 9
  • 39

4 Answers4

2

With :

While you can use --if and --else for xmlstarlet sel (see https://stackoverflow.com/a/35671038/2703456 for example), I don't think you can use them for xmlstarlet ed. I hardly ever use xmlstarlet, so I'm not entirely sure.
That means you'll have to do it in Bash...

$ if [[ $(xmlstarlet sel -t -v 'parent/version' pom.xml) == "1.0.0-SNAPSHOT" ]]
  then xmlstarlet ed -O -u 'parent/version' -v 2.0.1 pom.xml
  fi
<parent>
  <groupId>com.example</groupId>
  <artifactId>some-parent</artifactId>
  <version>2.0.1</version>
</parent>

With :

$ xidel -s pom.xml -e '
  if (parent/version = "1.0.0-SNAPSHOT")
  then x:replace-nodes(parent/version/text(),"2.0.1")
  else .
' --output-node-format=xml
<parent>
    <groupId>com.example</groupId>
    <artifactId>some-parent</artifactId>
    <version>2.0.1</version>
</parent>

If you want the output to include an XML declaration, for xmlstarlet remove the -O option and for xidel use --output-format=xml instead.

Reino
  • 3,203
  • 1
  • 13
  • 21
  • Thanks @Reino Is there a way to do it so that it will only replace the version if the current version is 1.0.0-SNAPSHOT, e.g. if the current version was 2.0.0-SNAPSHOT, I don't want the version replaced. Thanks – rmf Feb 05 '22 at 14:24
  • @rmf See updated answer. Btw, if you have to edit A LOT of these XML-files, then you might be interested in `xidel`'s "[EXPath File Module](https://www.benibela.de/documentation/internettools/xpath-functions.html#modulefile)". With just 1 `xidel` call you can then process all of them at once. – Reino Feb 05 '22 at 16:50
  • Not like that. It needs `/` to not replace it, `()` deletes everything. – BeniBela Feb 13 '22 at 23:46
  • @BeniBela Sorry. My mistake. Corrected. – Reino Feb 14 '22 at 22:58
2

You could use xq.

Just setting a certain element to a new value would be as simple as

xq -x '.project.parent.version = "2.0.1"' pom.xml

Setting it on condition would be

xq -x '(.project.parent | select(
  .artifactId == "some-parent" and .version == "1.0.0-SNAPSHOT"
)).version = "2.0.1"' pom.xml

Fed with the sample data, both would produce

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example</groupId>
    <artifactId>some-parent</artifactId>
    <version>2.0.1</version>
  </parent>
</project>

As you can see, the order of the attributes stays the same, however the indentation is different, especially your in-tag line breaks were swallowed. Also, the XML declaration (first line) is missing, as would comments, if present in the input. Maybe this isn't viable for you either, but consider

  • editing the logical structure (rather than its textual representation) is less error-prone,
  • all of these issues (except for the missing comments, of course) could easily be remedied using one of many XML pretty-printers available for the command-line. This is a (non-exhaustive) list of where to go from here.

For instance, let's go with tidy.

With this configuration

xq -x '…' pom.xml |
tidy -xml -iq -utf8 --indent-with-tabs yes --indent-spaces 4 --tab-size 4 \
  --indent-attributes yes --add-xml-decl yes

you would get as far as

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>some-parent</artifactId>
        <version>2.0.1</version>
    </parent>
</project>

For what I can see, this is still lacking the encoding="UTF-8" in the top-line declaration (tidy refuses to print obvious declarations), and the indentation of the attributes is still off by 1 character. If this is still an issue, try playing around with the other pretty-printers out there. Chances are you'll find the right one for the perfect finishing.

pmf
  • 24,478
  • 2
  • 22
  • 31
1

You should avoid working with xml as plain text files, but if you have no other options you may give it a try. sed ranges (/starting-regex/,/ending-regex/{commands}) allows to restrict your replacement within specific context. I.e. this oneliner:

sed -e '/<parent/,/\/parent/{/artifactId>some-parent/,/parent/{s/1.0.0-SNAPSHOT/2.0.0-SNAPSHOT/g}}'

will replace only version strings, which are between <parent and /parent strings, and between some-parent and parent lines, thus should only match artifacts some-parent. Obviously, there are multiple cases when it will fail, but for simple files this could work. Your ranges may be further nested.

Maciej Wrobel
  • 640
  • 4
  • 11
0

This works, using a combination of xmlstarlet and mvn:

#! /usr/bin/env bash
if [[ $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:artifactId' pom.xml) == "some-parent" && \
      $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:version' pom.xml) == "4.0.0-SNAPSHOT" ]]; then 
    mvn versions:update-parent -DparentVersion=[4.0.0-661]
    mvn versions:commit
fi
rmf
  • 625
  • 2
  • 9
  • 39