Numerowanie wersji oprogramowania
Nadawanie numerów wersji kolejnym wydaniom bibliotek, czy też aplikacji z oczywistych względów wydaje się być co najmniej wskazanym. Pozostaje jednak pytanie, jak to robić?
Zasadniczo, zagadnienie to rozłożyć można na dwa mniejsze:
Pierwsze, to logika/strategia wersjonowania kolejnych wydań? Skłania nas ona do odpowiedzenia sobie na kilka pytań, np.: jaki format numeracji wersji przyjmiemy, np.: a’la JBoss, czy może a’la Microsoft? Czy będzie składać się on z 2, 3, 4, a może 5 liczb? Czy uwzględnimy w nim numer rewizji, czy może numer build’a? Czy będziemy używać oznaczeń rodzajów wydań? Jeśli tak, to czy będą one znaczące, np.: 1.0.0-Alpha < 1.0.0-RC? itp…
Drugie, to strona techniczna, tj.: czy numer wersji umieścimy po prostu jako plik tekstowy w katalogu aplikacji lub pliku Jar? Czy nazwy kolejnych plików Jar lub katalogów będą zawierać numer wersji? Czy może w plikach Jar będziemy umieszczać numer wersji tylko i wyłącznie w Manifeście? No i oczywiście, w jaki sposób będziemy zapamiętywać, zliczać i umieszczać kolejne numery wersji?…
Odpowiedzi na wszystkie te pytania zależeć będą zapewne głównie od Waszych indywidualnych upodobań lub ewentualnych wymagań, dlatego też nie zamierzam nikogo usilnie przekonywać do stosowania takiego, a nie innego schematu. Postaram się za to przedstawić, jak z tym problemem można poradzić sobie w dość prosty sposób, jako przykład podając praktykowaną przeze mnie strategię.
A zatem:
1. Format zapisu numeru
Wybrałem zapis trój-liczbowy, gdyż uważam, iż trzy liczby w zupełności wystarczą do oznaczenia małych i dużych poprawek oraz kolejnych wydań. Dodatkowo, za numerem wersji umieszczam ‘nieznaczące’ informacje, takie jak numer rewizji i ewentualnie rodzaj wydania. Przykładowo: 1.2.2 (347) lub 1.3.7 (545) Beta. Określenie, jakie informacje są znaczące, a jakie nie, miało dla mnie znaczenie, gdyż potrzebowałem tej informacji, aby zaimplementować programową obsługę rozróżniania poszczególnych numerów … ale o tym innym razem.
2. Miejsce umieszczania numeru wersji
Numer wersji umieszczam w każdym tworzonym przeze mnie pliku Jar, w pliku Manifestu, wykorzystując do tego celu atrybut Implementation-Version. Dodatkowo, numer ten umieszczam w nazwie każdego budowanego plik Jar biblioteki, paczki dystrybucyjnej (zip, tar.bz), czy też pliku instalatora aplikacji. Jednakże, katalogi w archiwach, jak i rozpakowywane przez instalator nie są przeze mnie numerowane.
3. Sposób zliczania i umieszczania numeru wersji
Do zautomatyzowania kompilacji, budowania Jar’ów i paczek wykorzystuje Ant’a. Dlatego też, naturalnym wydaje się, iż wykorzystuje jego dobrodziejstwa, do odwalenia za mnie brudnej roboty z numerowaniem 🙂 Nie bawię się jednak w automagiczne zliczanie numerów wersji, gdyż ewentualne ich zmiany zazwyczaj występują dość rzadko i do tego w sposób nie dający się w prosto zautomatyzować. Dlatego też, sam numer wersji przechowuje zapisany w Ant’owym propertisie:
1
<property name="version" value="0.0.9" />
Numer rewizji wyciągam z SVN’a w poniższy sposób (wymaga zainstalowanej konsolowej wersji klienta):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<exec executable="cmd" failonerror="false" osfamily="Windows" resultproperty="svnOk">
<arg line="/c svn info . --xml > svn_info.xml" />
</exec>
<exec executable="/bin/bash" failonerror="false" osfamily="Unix" resultproperty="svnOk">
<arg line='-c "svn info --xml > svn_info.xml"' />
</exec>
<if>
<equals arg1="${svnOk}" arg2="0" />
<then>
<xmlproperty file="svn_info.xml" prefix="svn" />
<property name="revision" value="${svn.info.entry.commit(revision)}" />
</then>
<else>
<property name="revision" value="unknown" />
</else>
</if>
<echo message="Current revision: ${revision}" />
Po czym sklejam sobie wszystko ładnie:
1
2
3
4
5
6
7
8
9
10
11
12
13
<property name="stage" value="Beta" />
<if>
<equals arg1="${stage}" arg2="" />
<then>
<property name="version.with.stage" value="${version}" />
<property name="version.full" value="${version} (${revision})" />
</then>
<else>
<property name="version.with.stage" value="${version}-${stage}" />
<property name="version.full" value="${version} (${revision}) ${stage}" />
</else>
</if>
I gdy posiadam już pełen numer wersji, mogę umieścić go w pliku MANIFEST.MF budowanego pliku Jar. W przypadku NetBeans’a do pliku build.xml dodaję poniższego Task’a:
1
2
3
4
5
6
7
8
9
10
11
<target name="-pre-init">
<manifest file="MANIFEST.MF" mode="replace">
<attribute name="Built-By" value="${user.name}"/>
<attribute name="Implementation-Title" value="${application.title}"/>
<attribute name="Implementation-Version" value="${version.full}"/>
<attribute name="Implementation-Vendor" value="${application.vendor}"/>
<attribute name="Implementation-URL" value="${application.homepage}"/>
</manifest>
<property name="manifest.file" value="MANIFEST.MF" />
</target>
Dzięki temu, za każdym razem, gdy budowany będzie plik Jar, zostanie umieszczony w nim stworzony automatycznie plik Manifestu (choć bywają z tym czasem problemy :/). W przypadku Eclipse’a postępuje podobnie, umieszczając blok manifest (bez atrybutów file i mode) w bloku jar budującym plik Jar aplikacji lub biblioteki.
W ostatnim etapie buduję paczkę dystrybucyjną, wykorzystując do tego poniższy fragment kodu:
1
2
3
4
5
6
7
8
9
<move file="${dist.jar}" tofile="${dist.dir}/${application.title}-${version.with.stage}.jar" />
<zip destfile="${application.title}-${version.with.stage}.zip" update="true" >
<zipfileset dir="${dist.dir}" prefix="${application.title}" />
</zip>
<tar destfile="${application.title}-${version.with.stage}.tar.bz" compression="bzip2">
<tarfileset dir="${dist.dir}" prefix="${application.title}" />
</tar>
W kolejnych wpisach przedstawię, jak można z poziomu kodu wyciągnąć wartości poszczególnych atrybutów z pliku Manifestu biblioteki lub aplikacji oraz jak można w całkiem przyjemny i uniwersalny sposób opakować numer wersji w obiekt przygotowanej do tego celu klasy.
P.S.: Zwolenników wykorzystywania numeru build’a zamiast numeru rewizji może zainteresować ta strona.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<property name="stage" value="" />
<property name="version" value="1.0.0" />
<if>
<equals arg1="${stage}" arg2="" />
<then>
<property name="version.with.stage" value="${version}" />
<property name="version.full" value="${version} (rev. ${revision})" />
</then>
<else>
<property name="version.with.stage" value="${version}-${stage}" />
<property name="version.full" value="${version} (rev. ${revision}) ${stage}" />
</else>
</if>