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 |
---|---|
A simple line editor. |
|
An extended line editor. |
|
Stream Editor. A programming language based ed / ex command to perform text transformations. |
|
A screen orientated editor based on ex. |
|
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 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 = ” . s:Version_Pattern . ”
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 and the Project_Name replace the version number found between
and
.
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’ . a:Project_Name . ‘/+1 substitute /’ . s:POM_Version_Pattern . ‘/’ . a:Project_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 and
replace on the version number found between
and
.
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/,/V/ substitute /’ . s:POM_Version_Pattern . ‘/’ . a:Parent_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 and
/ replace on all lines following
and the Project_Pattern the version number found between
and
. Repeat once for the lines following
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 and then jump to the following line.
91 function Set_Depended_Version (Project_Pattern, Project_Version)
92 1
93 execute ‘/V/,/V/ global /V’ . a:Project_Pattern . ‘/+1 substitute /’ . s:POM_Version_Pattern . ‘/’ . a:Project_Version . ‘/’
94 //+1
95 execute ‘/V/,/V/ global /V’ . a:Project_Pattern . ‘/+1 substitute /’ . s:POM_Version_Pattern . ‘/’ . a:Project_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 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 /.*’ . 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 ,
or
followed by the pattern given as parameter.
Again global
is used.
149 function Set_App_Version_Number (Pattern, Version)
150 execute ‘global /’ . a:Pattern . ‘/ substitute /’ . s:Version_Pattern . ‘/’ . a:Version . ‘/e’
151 execute ‘global /’ . a:Pattern . ‘/ substitute /’ . s:Version_Pattern . ‘/’ . a:Version . ‘/e’
152 execute ‘global /’ . 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.xml
files. Provided they are places inside….
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()[0-9.()]*()/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”
45 gvim_command “:exit”
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]vimvim73gvim.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]vimvim73gvim.exe —servername “Set-Version-Number”
28 DELAY 5
29 gvim_command “:source Zeiterfassung/src/main/scripts/Set-Version-Number.vim”
30 gvim_command “:exit”
31 POPD
32 ENDLOCAL
33 QUIT 0