One of the problems of larger Project is the dependency management. A great help here is Maven. Maven can also be used in Android development so that both server and mobile client can use the same build and dependency management. In one of our Project we used this set-up.

However, there was still one drawback: The version number which needed to be kept in sync in the various xml files: pom.xml, AndroidManifest.xml, strings.xml, persistence.xml, application.xml to name a few

This is best done using some Script because Script will ensure fast, consistent and repeatable result. The are many languages which could perform the task. But I suggest an Editor-Script. That is a use of an Editor with its own scripting language like VI or EMACS.

The example I will present is written in VIM-Script the language of the VIM Editor. VIM is part of the POSIX family of editors:

Editor

Description

ed

A simple line editor.

ex

An extended line editor.

sed

Stream Editor. A programming language based ed / ex command to perform text transformations.

vi

A screen orientated editor based on ex.

VIM

VI-Improved. An enhanced version of VI. Most notably VIM has a full features graphical user interface. On most unix and unix like operating systems ed, ex and vi are symbolic links to vim.

In this Blog I will not speak about the merits of VIM as an editor but about VIM abilities as a programming language. The main advantage if VIM-Script is the ability to operate on a text at a hole. Most classic languages for text processing like SED, AWK or Perl usually operate on one line at at time. With VIM-Script the hole file is loaded into a text buffer and you can operate or navigate on the whole text.

But enough of the theory. Lets get into a real live example:

Set-Version-Number.vim

Variables

Lists of modules

First we define a few variables with the list of maven modules we are planning to work on. The first module of each list is the parent module:

26 let s:Noser_Modules           = ["Noser", "Noser-apklib", "Noser-apktest", "Noser-ejb", "Noser-glassfish", "Noser-Scala-apktest", "Noser-web"]
27 let s:Zeiterfassung_Modules   = ["Zeiterfassung", "Zeiterfassung-apk", "Zeiterfassung-ear", "Zeiterfassung-ejb", "Zeiterfassung-lib", "Zeiterfassung-rar", "Zeiterfassung-web", "Noser-Scala-apktest"]

Search pattern

Next a few search patterns. s:Version_Pattern searches three times one or two digits separated by dots. s:Version_Name_Pattern is similar but the set of digits are surrounded by brackets. s:POM_Version_Pattern looks for s:Version_Pattern between the the xml <version> tags as used by pom.xml.

32 let s:Version_Pattern         =  '\d\{1,2}\.\d\{1,2}\.\d\{1,2}'
33 let s:Version_Name_Pattern    =  '\d\{1,2}\.\d\{1,2}(\d\{1,2})'
34 let s:POM_Version_Pattern     =  '<version>' . s:Version_Pattern . '<\/version>'

Functions

The function I am now describing are mostly independent from the actual project. That is you can reuse the in other projects as well.

Integer_Version

All the different xml files need the version number in different formats. The following function will Convert Version number from «1.2.3» to integer form «010203» as used by the AndroidManifest.xml.

Interesting here is the use of the substitute function. Substitute searches for pattern in the string passed and replaces the string passed a third parameter. In this case we replace the whole string with the 1st, 2nd and 3rd (\1 \2 and \3) sub-expression (between \( and \)).

39 function Integer_Version (Version)
40    let l:Mayor=substitute (a:Version, '\(\d\{1,2}\)\.\(\d\{1,2}\)\.\(\d\{1,2}\)', '\1', '')
41    let l:Minor=substitute (a:Version, '\(\d\{1,2}\)\.\(\d\{1,2}\)\.\(\d\{1,2}\)', '\2', '')
42    let l:Fix=substitute   (a:Version, '\(\d\{1,2}\)\.\(\d\{1,2}\)\.\(\d\{1,2}\)', '\3', '')
44    if strlen (l:Minor) < 2
45       let l:Minor="0" . l:Minor
46    endif
48    if strlen (l:Fix) < 2
49       let l:Fix="0" . l:Fix
50    endif
52    return  l:Mayor .  l:Minor . l:Fix
53 endfunction Integer_Version

Display_Version

The next function convert a version number from «1.2.3» to display form «1.2(3)». This can be done with a single substitute.

58 function Display_Version (Version)
59    return substitute (a:Version, '\(\d\{1,2}\)\.\(\d\{1,2}\)\.\(\d\{1,2}\)', '\1.\2(\3)', '')
60 endfunction Display_Version

Set_Module_Version

Set the module version number inside the pom.xml. On the line following the first line containing <artifactId> and the Project_Name replace the version number found between <version> and </version>.

Interesting here the use of execute command. It evaluates a string expression and then executes the result a a new command. execute is needed quite often as Vim-Script is optimized for interactive use where literal expression are more important then variables or arithmetic expressions.

66 function Set_Module_Version (Project_Name, Project_Version)
67    1
68    execute '/\V<artifactId>' . a:Project_Name . '/+1 substitute /' . s:POM_Version_Pattern . '/<version>' . a:Project_Version . '<\/version>/'
70    return
71 endfunction Set_Module_Version

Set_Parent_Version

Set the parent-module version number inside the pom.xml. On the lines between <parent> and </parent> replace on the version number found between <version> and </version>.

Interesting here the use of search pattern as range. the given command is then executed for each line inside the range.

77 function Set_Parent_Version (Parent_Version)
78    1
79    execute '/\V<parent>/,/\V<\/parent>/ substitute /' . s:POM_Version_Pattern . '/<version>' . a:Parent_Version . '<\/version>/e'
81    return
82 endfunction Set_Parent_Version

Set_Depended_Version

Set the dependent version numbers inside the pom.xml. For all lines between <dependencies> and </dependencies>/ replace on all lines following <artifactId> and the Project_Pattern the version number found between <version> and </version>. Repeat once for the lines following </dependencies> for EBJ dependencies.

Interesting here the ability to directly jump to any line of the text as shown in line 92 and 94. In line 95 we first search for </dependencies> and then jump to the following line.

91 function Set_Depended_Version (Project_Pattern, Project_Version)
92    1
93    execute '/\V<dependencies>/,/\V<\/dependencies>/ global /\V<artifactId>' . a:Project_Pattern . '/+1 substitute /' . s:POM_Version_Pattern . '/<version>' . a:Project_Version . '<\/version>/'
94 /<\/dependencies>/+1
95    execute '/\V<dependencies>/,/\V<\/dependencies>/ global /\V<artifactId>' . a:Project_Pattern . '/+1 substitute /' . s:POM_Version_Pattern . '/<version>' . a:Project_Version . '<\/version>/'
97    return
98 endfunction Set_Depended_Version

Set_Property_Version_Number

Set the version numbers inside the property section of the pom.xml. Replaces the version number between the XML tags a:Module.

This is just a simple substitute on the hole file as indicated by the % range.

103 function Set_Property_Version_Number (Module, Version)
104    execute '% substitute /\(<' . a:Module . '>\)' . s:Version_Pattern .  '\(<\/' . a:Module . '>\)/\1' . a:Version . '\2/e'
106    return
107 endfunction Set_Property_Version_Number

Set_Droid_Version_Number

Replaces the version number in the AndroidManifest.xml replacing all version number after android:versionCode=" and android:versionName=".

112 function! Set_Droid_Version_Number (Version)
113    let l:Version=Integer_Version (a:Version)
114    let l:Name=Display_Version (a:Version)
116    % substitute /'/"/ge
118    execute '% substitute /\(android:versionCode="\)\d*\("\)/\1' . l:Version . '\2/e'
119    execute '% substitute /\(android:versionName="\)' . s:Version_Name_Pattern . '\("\)/\1' . l:Name . '\2/e'
120 endfunction Set_Droid_Version_Number

Set_HTML_Version_Number

Replaces version numbers in html help files by replacing everything which might be an old version number with the current version number

125 function Set_HTML_Version_Number (Version)
126     let l:Version=Display_Version (a:Version)
128     if strlen (l:Version) > 0
129         execute '% substitute /' . s:Version_Name_Pattern . '/' . l:Version . '/e'
130     endif
131 endfunction Set_HTML_Version_Number

Set_PO_Version_Number

Replaces the version number in a Java EE persistent modules. This is done by replacing anything that looks like a version number on line which contain <jar-file> followed by the pattern given as parameter.

Interesting the use of global command which executes a command for all line where a specific search pattern matches a regular expression. global has also given the well known command grep its name. grep stands for «global /regular expression/ print» and g/hello/p will indeed print all lines containing “hello”. with Vim-Script.

136 function Set_PO_Version_Number (Pattern, Version)
137    execute 'global /<jar-file>.*' . a:Pattern  .  '/ substitute /' . s:Version_Pattern . '/' . a:Version . '/e'
139    return
140 endfunction Set_PO_Version_Number

Set_App_Version_Number

replaces the version number in Java EE application modules. This is done by replacing anything that looks like a version number on line which contain <java>, <ejb> or <web-uri> followed by the pattern given as parameter.

Again global is used.

149 function Set_App_Version_Number (Pattern, Version)
150    execute 'global /<java>'    . a:Pattern . '/ substitute /' . s:Version_Pattern . '/' . a:Version . '/e'
151    execute 'global /<ejb>'     . a:Pattern . '/ substitute /' . s:Version_Pattern . '/' . a:Version . '/e'
152    execute 'global /<web-uri>' . a:Pattern . '/ substitute /' . s:Version_Pattern . '/' . a:Version . '/e'
154    return
155 endfunction Set_AppL_Version_Number

Set_String_Version_Number

Replaces the version number in android string.xmlfiles. Provided they are places inside<string name=”Version_Name”>…</string>.

160 function Set_String_Version_Number (Version)
161    let l:Version=Display_Version (a:Version)
163    % substitute /'/"/ge
165    if strlen (l:Version) > 0
166       execute '% substitute /\V\(<string name="Version_Name">\)\[0-9.()]\*\(<\/string>\)/\1' . l:Version . '\2/e'
167    endif
169    return
170 endfunction Set_String_Version_Number

The actual work

After having defined all functions needed we can start the actual work. First we change to the project directory.

Interesting here the use of shell variables. They can be easyly accessed by using a $ sign.

172 cd $PROJECT_HOME

Noser Library parent-module

Set the property version and module version on the first of the Noser modules.

Note the the module names and the directory names co-relate which makes it possible to open the pom file by concatenating the module name and “/pom.xml”.

178 execute "edit" s:Noser_Modules[0] . "/pom.xml"
179 0,$ foldopen!
180 call Set_Property_Version_Number ("NOSER_VERSION",       $NOSER_VERSION)
181 call Set_Module_Version          ( s:Noser_Modules[0],   $NOSER_VERSION)
182 update

Noser Library child-module

Set the parent-module, module and dependent module names.

Note that when no depended module name is found no error is reported.

184 for s:Module in s:Noser_Modules[1:]
185    execute "edit" s:Module . "/pom.xml"
186    0,$ foldopen!
187    call Set_Parent_Version    ($NOSER_VERSION)
188    call Set_Module_Version    (s:Module,  $NOSER_VERSION)
189    call Set_Depended_Version  ("Noser",   $NOSER_VERSION)
190    update

Set the version name in any AndroidManifest*.xml found. If none a found then the loop is skipped. Since the search is rather fast no additional optimizations are performed.

Interesting the use if split and glob to get a list a files matching a wild card pattern.

192    for s:File in split (glob (s:Module ."/**/AndroidManifest*.xml"))
193       execute "edit" s:File
194       call Set_Droid_Version_Number ($NOSER_VERSION)
195       update
196    endfor
197 endfor

Application parent-module

Similar to Noser parent-module. Only this time we set both the own version number and the version number of the Noser library as we depend on the library.

199 execute "edit" s:Zeiterfassung_Modules[0] . "/pom.xml"
200 0,$ foldopen!
201 call Set_Property_Version_Number ("NOSER_VERSION",             $NOSER_VERSION)
202 call Set_Property_Version_Number ("ZEITERFASSUNG_VERSION",     $ZEITERFASSUNG_VERSION)
203 call Set_Module_Version          (s:Zeiterfassung_Modules[0],  $ZEITERFASSUNG_VERSION)
204 call Set_Depended_Version        ("Zeiterfassung",             $ZEITERFASSUNG_VERSION)
205 call Set_Depended_Version        ("Noser",                     $NOSER_VERSION)
206 update

Application child-modules

Same as before but again with two calls to Set_Depended_Version.

211 for s:Module in s:Zeiterfassung_Modules[1:]
212    execute "edit" s:Module . "/pom.xml"
213    0,$ foldopen!
214    call Set_Parent_Version    ($ZEITERFASSUNG_VERSION)
215    call Set_Module_Version    (s:Module,           $ZEITERFASSUNG_VERSION)
216    call Set_Depended_Version  ("Zeiterfassung",    $ZEITERFASSUNG_VERSION)
217    call Set_Depended_Version  ("Noser",            $NOSER_VERSION)
218    update

Test if a strings.xml file exist and replace the version number.

220    if filewritable (s:Module . "/res/values/strings.xml")
221       execute "edit" s:Module . "/res/values/strings.xml"
222       call Set_String_Version_Number ($ZEITERFASSUNG_VERSION)
223       update
224    endif

Replace the version number in all html files found.

226    for s:File in split (glob (s:Module . "/src/**/*.html"))
227       execute "edit" s:File
228       call Set_HTML_Version_Number ($ZEITERFASSUNG_VERSION)
229       update
230    endfor

Replace the version number in all Java EE persistent modules found.

232    for s:File in split (glob (s:Module . "/src/**/persistence.xml"))
233       execute "edit" s:File
234       call Set_PO_Version_Number ("Zeiterfassung", $ZEITERFASSUNG_VERSION)
235       update
236    endfor

Replace the version number in all Java EE application definitions found.

238    for s:File in split (glob (s:Module . "/src/**/application.xml"))
239       execute "edit" s:File
240       call Set_App_Version_Number ("Noser",           $NOSER_VERSION)
241       call Set_App_Version_Number ("Zeiterfassung",   $ZEITERFASSUNG_VERSION)
242       update
243    endfor

Set the version name in any AndroidManifest*.xml found.

250    for s:File in split (glob (s:Module ."/**/AndroidManifest*.xml"))
251       execute "edit" s:File
252       call Set_Droid_Version_Number ($ZEITERFASSUNG_VERSION)
253       update
254    endfor
255 endfor

Finish off

The wall command saves all unsaved changes and finish ends parsing of the script. That’s all folks. But wait, not quite. You need to execute the script as well.

350 wall
351 finish

Set-Version-Number.command

One can either call the script from within VIM using the source command or remote control vim with a shell script and vim remote control feature. Here an example for Z-Shell on Mac OS X:

 1 #!/opt/local/bin/zsh
28 
29 source ${PROJECT_HOME}/Utilities/Setup.command
30 
31 setopt No_X_Trace;
32 setopt CSH_Null_Glob;
33 
34 alias gvim_command='/opt/local/bin/gvim --servername "Set-Version-Number" --remote-send'
35 
36 pushd ${PROJECT_HOME}
40     /opt/local/bin/gvim --servername "Set-Version-Number" &
41 
42     sleep 5
43 
44     gvim_command ":source Utilities/src/main/scripts/Set-Version-Number.vim<CR>"
45     gvim_command ":exit<CR>"
46 popd;

Set-Version-Number.cmd

For Windows we use TakeCommand (http://jpsoft.com) for our daily scripting needs. Here the same start script for Windows:

11 @ECHO OFF
12 
13 IF NOT "%@eval[2 + 2]%" == "4" ( echo ^e[42mYou need TakeCommand [http://www.jpsoft.com] to execute this batch file.^e[m & EXIT /B 1)
14 
15 SETLOCAL
22     ALIAS gvim_command=%[opt]\vim\vim73\gvim.exe --servername "Set-Version-Number" --remote-send
23 
24     PUSHD "%[PROJECT_HOME]"
25         REM Start needs the real path and not an alias
26         REM
27         START /PGM %[opt]\vim\vim73\gvim.exe  --servername "Set-Version-Number"
28         DELAY 5
29         gvim_command ":source Zeiterfassung/src/main/scripts/Set-Version-Number.vim<CR>"
30         gvim_command ":exit<CR>"
31     POPD
32 ENDLOCAL
33 QUIT 0