Numeracja kompilacji i wersji dla projektów Java (ant, cvs, hudson)

134

Jakie są aktualne najlepsze praktyki dotyczące systematycznego numerowania kompilacji i zarządzania numerami wersji w projektach Java? Konkretnie:

  • Jak systematycznie zarządzać numerami kompilacji w rozproszonym środowisku programistycznym

  • Jak zachować numery wersji w źródle / dostępne dla aplikacji uruchomieniowej

  • Jak poprawnie zintegrować się z repozytorium źródeł

  • Jak bardziej automatycznie zarządzać numerami wersji a znacznikami repozytorium

  • Jak zintegrować się z infrastrukturą ciągłego budowania

Dostępnych jest wiele narzędzi, a ant (system kompilacji, którego używamy) ma zadanie, które będzie utrzymywać numer kompilacji, ale nie jest jasne, jak zarządzać tym z wieloma współbieżnymi programistami używającymi CVS, svn lub podobnych .

[EDYTOWAĆ]

Poniżej pojawiło się kilka dobrych i pomocnych odpowiedzi częściowych lub szczegółowych, więc podsumuję kilka z nich. Wydaje mi się, że nie ma w tej kwestii mocnej „najlepszej praktyki”, a raczej zbiór pokrywających się pomysłów. Poniżej znajdują się moje podsumowania i niektóre wynikające z nich pytania, na które ludzie mogą spróbować odpowiedzieć w ramach dalszych działań. [Nowość w stackoverflow… Jeśli robię to źle, podaj komentarze.]

  • Jeśli korzystasz z SVN, podczas jazdy pojawia się wersja konkretnego zamówienia. Numeracja kompilacji może to wykorzystać do stworzenia unikalnego numeru kompilacji, który identyfikuje konkretną kasę / wersję. [CVS, którego używamy ze starszych powodów, nie zapewnia takiego poziomu wglądu… ręczna interwencja przy użyciu tagów prowadzi do częściowego celu.]

  • Jeśli używasz maven jako systemu kompilacji, istnieje wsparcie dla tworzenia numeru wersji z SCM, a także moduł wydania do automatycznego tworzenia wydań. [Nie możemy używać mavena z różnych powodów, ale to pomaga tym, którzy mogą. [Podziękowania dla Marcelo-Morales ]]

  • Jeśli używasz ant jako systemu budowania, poniższy opis zadania może pomóc w utworzeniu pliku Java .properties przechwytującego informacje o kompilacji, które można następnie złożyć w kompilacji na wiele sposobów. [Rozszerzyliśmy ten pomysł, aby uwzględnić informacje pochodzące z hudsona , dzięki marty-lamb ].

  • Ant i maven (oraz hudson i cruise control) zapewniają łatwe sposoby pobierania numerów kompilacji do pliku .properties lub do pliku .txt / .html. Czy jest to wystarczająco „bezpieczne”, aby zapobiec celowej lub przypadkowej manipulacji? Czy lepiej skompilować go do klasy „wersjonowania” w czasie kompilacji?

  • Asercja: numeracja kompilacji powinna być zdefiniowana / uchwalona w systemie ciągłej integracji, takim jak Hudson . [Dzięki marcelo-morales ] Podjęliśmy się tej sugestii, ale otwiera ona pytanie dotyczące inżynierii wydania: Jak następuje wydanie? Czy w wydaniu jest wiele numerów kompilacji? Czy istnieje znacząca zależność między numerami kompilacji z różnych wersji?

  • Pytanie: Jaki jest cel numeru kompilacji? Czy jest używany do kontroli jakości? W jaki sposób? Czy jest używany głównie przez programistów do rozróżniania wielu kompilacji podczas programowania, czy też bardziej do kontroli jakości w celu ustalenia, jaką kompilację otrzymał użytkownik końcowy? Jeśli celem jest powtarzalność, teoretycznie to właśnie powinien zapewniać numer wersji wydania - dlaczego nie? (proszę odpowiedzieć na to jako część swoich odpowiedzi poniżej, pomoże to oświetlić wybory, których dokonałeś / zasugerowałeś ...)

  • Pytanie: Czy jest miejsce na numery kompilacji w kompilacjach ręcznych? Czy jest to tak problematyczne, że WSZYSCY powinni używać rozwiązania CI?

  • Pytanie: Czy numery kompilacji należy wpisywać do SCM? Jeśli celem jest niezawodne i jednoznaczne zidentyfikowanie konkretnej kompilacji, jak radzić sobie z różnymi systemami kompilacji ciągłej lub ręcznej, które mogą ulec awarii / ponownym uruchomieniu / itp.

  • Pytanie: Czy numer kompilacji powinien być krótki i przyjemny (tj. Monotonicznie rosnąca liczba całkowita), aby można go było łatwo umieścić w nazwach plików w celu archiwizacji, łatwo się do nich odwoływać w komunikacji itp. Lub powinien być długi i pełen nazw użytkowników, datowniki, nazwy maszyn itp.?

  • Pytanie: podaj szczegółowe informacje o tym, jak przypisanie numerów kompilacji pasuje do Twojego większego automatycznego procesu wydawania. Tak, kochankowie maven, wiemy, że to jest zrobione i zrobione, ale nie wszyscy z nas jeszcze wypili kool-aid ...

Naprawdę chciałbym przekształcić to w kompletną odpowiedź, przynajmniej dla konkretnego przykładu naszej konfiguracji cvs / ant / hudson, aby ktoś mógł zbudować kompletną strategię w oparciu o to pytanie. Oznaczę jako „The Answer” każdego, kto może podać opis zupy do orzechów dla tego konkretnego przypadku (w tym schemat znakowania CVS, odpowiednie elementy konfiguracji CI i procedurę wydania, która składa numer kompilacji w wydaniu tak, że jest to programowo dostępne.) Jeśli chcesz zapytać / odpowiedzieć na inną konkretną konfigurację (powiedzmy, svn / maven / cruise control), podam link do pytania tutaj. - JA

[EDYCJA 23 października 09] Przyjąłem odpowiedź, która została najwyżej oceniona, ponieważ uważam, że jest to rozsądne rozwiązanie, podczas gdy kilka innych odpowiedzi zawiera również dobre pomysły. Jeśli ktoś chce spróbować zsyntetyzować niektóre z nich z owieczkami martyrologicznymi , rozważę wybór innego. Jedynym problemem, jaki mam w przypadku marty-lamb's, jest to, że nie generuje on niezawodnie zserializowanego numeru kompilacji - zależy to od lokalnego zegara w systemie konstruktora, który zapewnia jednoznaczne numery kompilacji, co nie jest świetne.

[Edytuj 10 lipca]

Teraz dołączamy klasę taką jak poniżej. Pozwala to skompilować numery wersji do ostatecznego pliku wykonywalnego. Różne formy informacji o wersji są emitowane w danych logowania, długoterminowych zarchiwizowanych produktach wyjściowych i wykorzystywane do śledzenia naszej (czasami po latach) analizy produktów wyjściowych do określonej wersji.

public final class AppVersion
{
   // SVN should fill this out with the latest tag when it's checked out.

   private static final String APP_SVNURL_RAW = 
     "$HeadURL: svn+ssh://user@host/svnroot/app/trunk/src/AppVersion.java $";
   private static final String APP_SVN_REVISION_RAW = "$Revision: 325 $";  

   private static final Pattern SVNBRANCH_PAT = 
     Pattern.compile("(branches|trunk|releases)\\/([\\w\\.\\-]+)\\/.*");
   private static final String APP_SVNTAIL = 
     APP_SVNURL_RAW.replaceFirst(".*\\/svnroot\\/app\\/", "");

  private static final String APP_BRANCHTAG;
  private static final String APP_BRANCHTAG_NAME;
  private static final String APP_SVNREVISION = 
    APP_SVN_REVISION_RAW.replaceAll("\\$Revision:\\s*","").replaceAll("\\s*\\$", "");


  static {
    Matcher m = SVNBRANCH_PAT.matcher(APP_SVNTAIL);
    if (!m.matches()) {
      APP_BRANCHTAG = "[Broken SVN Info]";
      APP_BRANCHTAG_NAME = "[Broken SVN Info]";
    } else {
      APP_BRANCHTAG = m.group(1);
      if (APP_BRANCHTAG.equals("trunk")) {
        // this isn't necessary in this SO example, but it 
        // is since we don't call it trunk in the real case
        APP_BRANCHTAG_NAME = "trunk";
      } else {
        APP_BRANCHTAG_NAME = m.group(2);
      }
    }
  }

  public static String tagOrBranchName()
  { return APP_BRANCHTAG_NAME; }

  /** Answers a formatter String descriptor for the app version.
   * @return version string */
  public static String longStringVersion()
  { return "app "+tagOrBranchName()+" ("+
    tagOrBranchName()+", svn revision="+svnRevision()+")"; }

  public static String shortStringVersion()
  { return tagOrBranchName(); }

  public static String svnVersion()
  { return APP_SVNURL_RAW; }

  public static String svnRevision()
  { return APP_SVNREVISION; }

  public static String svnBranchId()
  { return APP_BRANCHTAG + "/" + APP_BRANCHTAG_NAME; } 

  public static final String banner()
  {
    StringBuilder sb = new StringBuilder();
    sb.append("\n----------------------------------------------------------------");
    sb.append("\nApplication -- ");
    sb.append(longStringVersion());
    sb.append("\n----------------------------------------------------------------\n");
    return sb.toString();
  }
}

Zostaw komentarze, jeśli zasługuje na to, aby stać się dyskusją na wiki.

andersoj
źródło
2
Dla przyszłych czytelników proszę zauważyć, że numer wersji w zasugerowanym kodzie dotyczy pliku, a nie globalnej wersji repozytorium. Więcej informacji można znaleźć na stronie: subversion.apache.org/faq.html#version-value-in-source
maayank
2
Zastanawiam się, czy ktoś ma podobne proste podejście podczas używania gradlei / lub git?
bnjmn

Odpowiedzi:

63

W przypadku kilku moich projektów przechwytuję numer wersji subversion, czas, użytkownika, który uruchomił kompilację i niektóre informacje o systemie, umieszczam je w pliku .properties, który jest dołączany do pliku jar aplikacji i czytam ten plik w czasie wykonywania.

Kod mrówki wygląda następująco:

<!-- software revision number -->
<property name="version" value="1.23"/>

<target name="buildinfo">
    <tstamp>
        <format property="builtat" pattern="MM/dd/yyyy hh:mm aa" timezone="America/New_York"/>
    </tstamp>        
    <exec executable="svnversion" outputproperty="svnversion"/>
    <exec executable="whoami" outputproperty="whoami"/>
    <exec executable="uname" outputproperty="buildsystem"><arg value="-a"/></exec>

    <propertyfile file="path/to/project.properties"
        comment="This file is automatically generated - DO NOT EDIT">        
        <entry key="buildtime" value="${builtat}"/>
        <entry key="build" value="${svnversion}"/>
        <entry key="builder" value="${whoami}"/>
        <entry key="version" value="${version}"/>
        <entry key="system" value="${buildsystem}"/>
    </propertyfile>
</target>

Można to łatwo rozszerzyć, aby uwzględnić wszelkie informacje, które chcesz dodać.

Marty Lamb
źródło
12
W przypadku wersji wieloplatformowej użyj tego zamiast powyższej właściwości whoami: <entry key = "builder" value = "$ {user.name}" />
Ed Brannin
-1 za rozwiązanie zależne od platformy. dobrym sposobem na umieszczenie wszystkich dostępnych właściwości w pliku jest: `<property name =" antprops.file "location =" $ {build.temp.project.dir} /used_ant.properties "/> <echoproperties destfile =" $ {antprops.file} "/> <! - sortuj plik, TYLKO dla Ant 1.7.0 i nowszych !!! -> <concat> <union> <sort> <tokens> <file file =" $ {antprops .file} "/> <linetokenizer includedelims =" true "/> </tokens> </sort> </union> </concat>`
raudi
Czy po wykonaniu tej czynności ponownie się angażujesz? Czy jest to zrobione przed wprowadzeniem rzeczywistych poprawek?
Charles Wood
6
Jak to zrobić dla repozytorium git?
TechCrunch
46

Twój plik build.xml

...
<property name="version" value="1.0"/>
...
<target name="jar" depends="compile">
    <buildnumber file="build.num"/>
    <manifest file="MANIFEST.MF">
        ...
        <attribute name="Main-Class" value="MyClass"/>
        <attribute name="Implementation-Version" value="${version}.${build.number}"/>
        ...
    </manifest>
</target>
...

Twój kod java

String ver = MyClass.class.getPackage().getImplementationVersion();
user146146
źródło
6
+1 za użycie właściwości, którą Java już obsługuje za pomocą takiej metody.
Ed Brannin
2
-1 za posiadanie numeru kompilacji w pliku build.xml, a nie w oddzielnym pliku właściwości
raudi
6
  • Numery kompilacji powinny być powiązane z serwerem ciągłej integracji, takim jak hudson . Używaj różnych stanowisk dla różnych gałęzi / zespołów / dystrybucji.
  • Aby zachować numer wersji w ostatecznej kompilacji, poleciłbym po prostu użyć mavena do kompilacji systemu. Spowoduje to utworzenie pliku .properties zarchiwizowanego w ostatecznej wersji .jar / .war /. Cokolwiek-ar META-INF/maven/<project group>/<project id>/pom.properties. Plik .properties będzie zawierał właściwość wersji.
  • Ponieważ polecam mavena, zachęcam do zapoznania się z wtyczką wydania, aby przygotować wydanie w repozytorium źródłowym i zachować synchronizację wersji.
H Marcelo Morales
źródło
6

Oprogramowanie:

  • SVN
  • Mrówka
  • Hudson za ciągłą integrację
  • svntask, zadanie Ant, aby znaleźć wersję SVN: http://code.google.com/p/svntask/

Hudson ma trzy kompilacje / zadania: ciągłe, nocne i wydanie.

W przypadku kompilacji ciągłej / nocnej: numer kompilacji to wersja SVN, znaleziona za pomocą svntask.

W przypadku kompilacji / zadania wydania: Numer kompilacji to numer wersji odczytany przez Anta z pliku Właściwości. Plik właściwości można również rozpowszechniać wraz z wydaniem w celu wyświetlania numeru kompilacji w czasie wykonywania.

Skrypt budujący Ant umieszcza numer kompilacji w pliku manifestu plików jar / war, które są tworzone podczas kompilacji. Dotyczy wszystkich kompilacji.

Czynność po kompilacji dla kompilacji wydania, którą można łatwo wykonać za pomocą wtyczki Hudson: oznacz SVN numerem kompilacji.

Korzyści:

  • W przypadku wersji deweloperskiej jar / war, deweloper może znaleźć wersję SVN z jar / war i wyszukać odpowiedni kod w SVN
  • W przypadku wydania wersją SVN jest wersja odpowiadająca znacznikowi SVN, który zawiera numer wydania.

Mam nadzieję że to pomoże.

Raleigh
źródło
Czy kompilacja / zadanie wydania sprawdzają plik właściwości numeru kompilacji / wydania z powrotem do SVN?
mat b
W przypadku kompilacji ciągłych numer kompilacji to numer wersji SVN. Nie ma więc nic do zaewidencjonowania. W przypadku kompilacji wydań jest to plik wpisany, który jest używany do pobierania numeru kompilacji. Zazwyczaj inżynier ds. Wydania lub ktokolwiek inny chciałby zaktualizować ten plik na długo przed wydaniem.
Raleigh,
4

Używam również Hudsona, chociaż scenariusz jest znacznie prostszy:

Mój skrypt Ant zawiera cel, który wygląda następująco:

<target name="build-number">
    <property environment="env" />
    <echo append="false" file="${build.dir}/build-number.txt">Build: ${env.BUILD_TAG}, Id: ${env.BUILD_ID}, URL: ${env.HUDSON_URL}</echo>
</target>

Hudson ustawia dla mnie te zmienne środowiskowe za każdym razem, gdy moja praca jest wykonywana.

W moim przypadku ten projekt jest aplikacją internetową i umieszczam ten build-number.txtplik w folderze głównym aplikacji internetowej - nie obchodzi mnie, kto go widzi.

Nie oznaczamy kontroli źródła, gdy jest to zrobione, ponieważ mamy już skonfigurowane zadanie Hudson, aby oznaczyć je numerem kompilacji / sygnaturą czasową, gdy kompilacja się powiedzie.

Moje rozwiązanie obejmuje tylko przyrostowe numery kompilacji na potrzeby programowania, nie zaszliśmy wystarczająco daleko w projekcie, w którym zajmujemy się jeszcze numerami wersji.

matowe b
źródło
dodatkowo (przynajmniej w aktualnych wersjach Jenkinsa) możesz sprawdzić właściwości: `env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 itd.
raudi
3

Możesz również rzucić okiem na wtyczkę BuildNumber Maven i zadanie Ant w jednym słoiku znalezionym pod adresem http://code.google.com/p/codebistro/wiki/BuildNumber . Starałem się, aby było to proste i nieskomplikowane. Jest to bardzo mały plik jar, który zależy tylko od zainstalowanej linii poleceń Subversion.

Sasha O
źródło
3

Oto jak to rozwiązałem:

  • źródła są kopiowane do katalogu budowania
  • następnie stosowane jest anttask "informacje o wersji"
  • skompiluj zmodyfikowane źródła

Oto plik java przechowujący informacje o wersji:

public class Settings {

    public static final String VERSION = "$VERSION$";
    public static final String DATE = "$DATE$";

}

A oto anttask „versioninfo”:

    <!-- ================================= 
     target: versioninfo              
     ================================= -->
    <target name="versioninfo"
            depends="init"
            description="gets version info from svn"
    >

        <!-- 
        get svn info from the src folder 
        -->
        <typedef resource="org/tigris/subversion/svnant/svnantlib.xml"
                 classpathref="ant.classpath"
        />
        <svnSetting id="svn.setting"
                    javahl="false"
                    svnkit="true"
                    dateformatter="dd.MM.yyyy"
        />
        <svn refid="svn.setting">
            <info target="src" />
        </svn>

        <!-- 
        if repository is a taged version use "v <tagname>"
        else "rev <revisionnumber> (SVN)" as versionnumber
         -->
        <taskdef resource="net/sf/antcontrib/antcontrib.properties"
                 classpathref="ant.classpath"
        />
        <propertyregex property="version"
                       input="${svn.info.url}"
                       regexp=".*/tags/(.*)/${ant.project.name}/src"
                       select="v \1"
                       defaultvalue="rev ${svn.info.lastRev} (SVN)"
                       override="true"
        />


        <!-- 
        replace date and version in the versionfile ()
         -->
        <replace file="build/${versionfile}">
            <replacefilter token="$DATE$" value="${svn.info.lastDate}" />
            <replacefilter token="$VERSION$" value="${version}" />
        </replace>

    </target>
flederohr
źródło
2

Oto moje 2 centy:

  • Mój skrypt kompilacji tworzy numer kompilacji (z sygnaturą czasową!) Za każdym razem, gdy buduję aplikację. Tworzy to zbyt wiele liczb, ale nigdy za mało. Jeśli mam zmianę w kodzie, numer kompilacji zmieni się przynajmniej raz.

  • Wersję numer kompilacji wykonuję z każdym wydaniem (choć nie pomiędzy). Kiedy aktualizuję projekt i otrzymuję nowy numer kompilacji (ponieważ ktoś inny wydał wydanie), nadpisuję moją lokalną wersję i zaczynam od nowa. Może to prowadzić do niższego numeru kompilacji, dlatego dołączyłem sygnaturę czasową.

  • Po wydaniu numer kompilacji jest zatwierdzany jako ostatni element w pojedynczym zatwierdzeniu z komunikatem „kompilacja 1547”. Następnie, gdy jest to oficjalne wydanie, całe drzewo jest oznaczane. W ten sposób plik kompilacji zawsze zawiera wszystkie tagi i istnieje proste mapowanie 1: 1 między tagami i numerami kompilacji.

[EDYTUJ] Wdrażam plik version.html z moimi projektami, a następnie mogę użyć skrobaka, aby po prostu zebrać dokładną mapę, która jest zainstalowana i gdzie. Jeśli używasz Tomcata lub podobnego, umieść numer kompilacji i znacznik czasu w descriptionelemencie web.xml . Pamiętaj: nigdy nie zapamiętuj niczego, jeśli komputer może zrobić to za Ciebie.

Aaron Digulla
źródło
Tak ... to też mamy. Używanie go jest uciążliwe, gdy trzeba pamiętać, która niewiarygodnie długa wersja została rozmieszczona tu czy tam. Tak zbyt wiele liczb jest problem zbyt ...
Varkhan
@Varkhan: Dlaczego? Wystarczy umieścić wersji gdzieś, gdzie skrobak HTML mogą odebrać to dla ciebie ...
Aaron Digulla
1

Uruchamiamy naszą kompilację za pośrednictwem CruiseControl (wstaw tutaj swojego ulubionego menedżera kompilacji) i wykonujemy główną kompilację i testy.

Następnie zwiększamy numer wersji za pomocą Ant i BuildNumber i tworzymy plik właściwości z tymi informacjami oraz datą kompilacji i innymi metadanymi.

Mamy klasę poświęconą czytaniu tego i dostarczaniu go do GUI / logów itp.

Następnie pakujemy to wszystko i tworzymy możliwą do wdrożenia, łączącą razem numer kompilacji i odpowiednią kompilację. Wszystkie nasze serwery zrzucają te metainformacje podczas uruchamiania. Możemy wrócić do dzienników CruiseControl i powiązać numer kompilacji z datą i zameldowaniami.

Brian Agnew
źródło
Wskazówka: jeśli używasz CC i Ant's BuildNumber, możesz użyć PropertyFileLabelIncrementer, aby zachować synchronizację etykiet CC z numerem kompilacji.
Jeffrey Fredrick,