Build- und Versionsnummerierung für Java-Projekte (ant, cvs, hudson)

134

Was sind die aktuellen Best Practices für die systematische Build-Nummerierung und das Versionsnummernmanagement in Java-Projekten? Speziell:

  • So verwalten Sie Build-Nummern systematisch in einer verteilten Entwicklungsumgebung

  • So pflegen Sie Versionsnummern in der Quelle / stehen der Laufzeitanwendung zur Verfügung

  • So integrieren Sie sich ordnungsgemäß in das Quell-Repository

  • So verwalten Sie Versionsnummern im Vergleich zu Repository-Tags automatischer

  • Integration in die kontinuierliche Build-Infrastruktur

Es gibt eine ganze Reihe von Tools, und ant (das von uns verwendete Build-System) hat eine Aufgabe, die eine Build-Nummer verwaltet. Es ist jedoch nicht klar, wie dies mit mehreren gleichzeitigen Entwicklern mithilfe von CVS, svn oder ähnlichem verwaltet werden kann .

[BEARBEITEN]

Im Folgenden sind einige gute und hilfreiche teilweise oder spezifische Antworten aufgeführt, daher werde ich einige davon zusammenfassen. Es klingt für mich so, als ob es nicht wirklich eine starke "Best Practice" dafür gibt, sondern eine Sammlung überlappender Ideen. Unten finden Sie meine Zusammenfassungen und einige daraus resultierende Fragen, die die Leute möglicherweise als Follow-up beantworten möchten. [Neu bei Stackoverflow ... Bitte geben Sie Kommentare ab, wenn ich das falsch mache.]

  • Wenn Sie SVN verwenden, wird die Versionierung einer bestimmten Kasse für die Fahrt mitgeliefert. Die Build-Nummerierung kann dies ausnutzen, um eine eindeutige Build-Nummer zu erstellen, die die spezifische Prüfung / Revision identifiziert. [CVS, das wir aus früheren Gründen verwenden, bietet nicht ganz diese Einsicht ... manuelle Eingriffe mit Tags bringen Sie auf halbem Weg dorthin.]

  • Wenn Sie maven als Build-System verwenden, gibt es Unterstützung für die Erstellung einer Versionsnummer aus dem SCM sowie ein Release-Modul für die automatische Erstellung von Releases. [Wir können Maven aus verschiedenen Gründen nicht verwenden, aber das hilft denen, die es können. [Danke an marcelo-morales ]]

  • Wenn Sie ant als Build-System verwenden, kann die folgende Aufgabenbeschreibung dazu beitragen, eine Java-Eigenschaftendatei zu erstellen, in der Build-Informationen erfasst werden, die dann auf verschiedene Arten in Ihren Build gefaltet werden können. [Wir haben diese Idee erweitert, um von Hudson abgeleitete Informationen aufzunehmen, danke Marty-Lamb ].

  • Ant und Maven (sowie Hudson und Cruise Control) bieten eine einfache Möglichkeit, Build-Nummern in eine .properties-Datei oder in eine .txt / .html-Datei zu übertragen. Ist dies "sicher" genug, um zu verhindern, dass es absichtlich oder versehentlich manipuliert wird? Ist es besser, es zur Erstellungszeit in eine "Versionierungs" -Klasse zu kompilieren?

  • Behauptung: Die Build-Nummerierung sollte in einem kontinuierlichen Integrationssystem wie Hudson definiert / festgelegt werden . [Dank an marcelo-morales ] Wir haben diesen Vorschlag angenommen, aber er wirft die Frage der Release-Technik auf: Wie kommt es zu einer Veröffentlichung? Gibt es mehrere Build-Nummern in einer Version? Gibt es eine sinnvolle Beziehung zwischen Buildnummern aus verschiedenen Releases?

  • Frage: Was ist das Ziel einer Build-Nummer? Wird es für die Qualitätssicherung verwendet? Wie? Wird es hauptsächlich von Entwicklern verwendet, um während der Entwicklung zwischen mehreren Builds zu unterscheiden, oder mehr, damit die Qualitätssicherung feststellt, welchen Build ein Endbenutzer erhalten hat? Wenn das Ziel die Reproduzierbarkeit ist, sollte dies theoretisch eine Versionsnummer der Veröffentlichung bieten - warum nicht? (Bitte beantworten Sie dies als Teil Ihrer Antworten unten, um die von Ihnen getroffenen / vorgeschlagenen Entscheidungen zu beleuchten ...)

  • Frage: Gibt es in manuellen Builds einen Platz für Build-Nummern? Ist das so problematisch, dass JEDER eine CI-Lösung verwenden sollte?

  • Frage: Sollten Build-Nummern im SCM eingecheckt werden? Wenn das Ziel darin besteht, einen bestimmten Build zuverlässig und eindeutig zu identifizieren, wie mit einer Vielzahl von kontinuierlichen oder manuellen Build-Systemen umzugehen ist, die abstürzen / neu starten / etc ...

  • Frage: Sollte eine Build-Nummer kurz und bündig sein (dh eine monoton ansteigende Ganzzahl), so dass es einfach ist, sich für die Archivierung in Dateinamen zu stecken, in der Kommunikation leicht darauf zu verweisen usw. ... oder sollte sie lang und voller Benutzernamen sein? Datenstempel, Maschinennamen usw.?

  • Frage: Bitte geben Sie an, wie die Zuweisung von Build-Nummern in Ihren größeren automatisierten Freigabeprozess passt. Ja, Maven-Liebhaber, wir wissen, dass dies getan und getan ist, aber noch nicht alle von uns haben die Kool-Hilfe getrunken ...

Ich möchte dies wirklich in eine vollständige Antwort umwandeln, zumindest für das konkrete Beispiel unseres cvs / ant / hudson-Setups, damit jemand eine vollständige Strategie basierend auf dieser Frage entwickeln kann. Ich werde jeden als "Die Antwort" markieren, der eine Suppe-zu-Nuss-Beschreibung für diesen speziellen Fall geben kann (einschließlich Lebenslauf-Tagging-Schema, relevanten CI-Konfigurationselementen und Freigabeverfahren, bei denen die Build-Nummer programmgesteuert in die Freigabe gefaltet wird zugänglich.) Wenn Sie nach einer anderen bestimmten Konfiguration fragen / antworten möchten (z. B. SVN / Maven / Tempomat), werde ich von hier aus auf die Frage verlinken. - JA

[EDIT 23 Oct 09] Ich habe die Antwort mit der höchsten Abstimmung akzeptiert, weil ich denke, dass dies eine vernünftige Lösung ist, während einige der anderen Antworten auch gute Ideen enthalten. Wenn jemand eine Pause einlegen möchte, um einige davon mit Marty-Lamm zu synthetisieren , werde ich in Betracht ziehen, eine andere zu akzeptieren. Das einzige Problem, das ich mit Marty-Lambs habe, ist, dass es keine zuverlässig serialisierte Build-Nummer erzeugt - es hängt von einer lokalen Uhr im Builder-System ab, um eindeutige Build-Nummern bereitzustellen, was nicht großartig ist.

[Bearbeiten 10. Juli]

Wir schließen jetzt eine Klasse wie die folgende ein. Dadurch können die Versionsnummern in die endgültige ausführbare Datei kompiliert werden. Verschiedene Formen der Versionsinformationen werden in Protokolldaten und langfristig archivierten Ausgabeprodukten ausgegeben und verwendet, um unsere (manchmal Jahre später) Analyse von Ausgabeprodukten auf einen bestimmten Build zurückzuführen.

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();
  }
}

Hinterlasse Kommentare, wenn dies eine Wiki-Diskussion verdient.

andersoj
quelle
2
Beachten Sie für zukünftige Leser, dass die Revisionsnummer in dem von Ihnen vorgeschlagenen Code aus der Datei stammt und nicht aus der globalen Revision des Repositorys. Weitere Informationen finden Sie unter: subversion.apache.org/faq.html#version-value-in-source
maayank
2
Ich frage mich, ob jemand ähnliche einfache Ansätze bei der Verwendung hat gradleund / oder git?
Bnjmn

Antworten:

63

Für einige meiner Projekte erfasse ich die Versionsnummer der Subversion, die Zeit, den Benutzer, der den Build ausgeführt hat, und einige Systeminformationen, füge sie in eine .properties-Datei ein, die in die Anwendungs-JAR aufgenommen wird, und lese diese JAR zur Laufzeit.

Der Ameisencode sieht folgendermaßen aus:

<!-- 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>

Es ist einfach, dies um alle Informationen zu erweitern, die Sie möglicherweise hinzufügen möchten.

Marty Lamb
quelle
12
Verwenden Sie für eine plattformübergreifende Version diese anstelle der obigen whoami-Eigenschaft: <entry key = "builder" value = "$ {user.name}" />
Ed Brannin
-1 für eine plattformabhängige Lösung. Eine gute Möglichkeit, alle verfügbaren Eigenschaften von Ameisen in einer Datei zu haben, ist: `<property name =" antprops.file "location =" $ {build.temp.project.dir} /used_ant.properties "/> <echoproperties destfile =" $ {antprops.file} "/> <! - sortiere die Datei, NUR für ant 1.7.0 und neuer !!! -> <concat> <union> <sort> <tokens> <file file =" $ {antprops .file} "/> <linetokenizer includeelims =" true "/> </ tokens> </ sort> </ union> </ concat>`
raudi
Verpflichten Sie sich danach erneut? Oder erfolgt dies, bevor Sie Ihre eigentlichen Überarbeitungen vornehmen?
Charles Wood
6
Wie mache ich das für das Git-Repository?
TechCrunch
46

Ihre 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>
...

Dein Java-Code

String ver = MyClass.class.getPackage().getImplementationVersion();
user146146
quelle
6
+1 für die Verwendung einer Eigenschaft, die Java bereits mit einer solchen Methode unterstützt.
Ed Brannin
2
-1 für die Build-Nummer in der build.xml und nicht in einer separaten Eigenschaftendatei
raudi
6
  • Build-Nummern sollten einem Server für kontinuierliche Integration wie Hudson zugeordnet werden . Verwenden Sie unterschiedliche Jobs für unterschiedliche Niederlassungen / Teams / Distributionen.
  • Um die Versionsnummer im endgültigen Build beizubehalten , würde ich empfehlen, nur maven für das Build-System zu verwenden. Es wird eine .properties-Datei erstellt, die im endgültigen .jar / .war / .whatever-ar on archiviert wird META-INF/maven/<project group>/<project id>/pom.properties. Die .properties-Datei enthält die version-Eigenschaft.
  • Da ich maven empfehle, möchte ich Sie dringend bitten, das Release-Plugin zu lesen, um das Release im Quell-Repository vorzubereiten und die Versionen synchron zu halten.
H Marcelo Morales
quelle
6

Software:

  • SVN
  • Ameise
  • Hudson für die kontinuierliche Integration
  • svntask, eine Ant-Aufgabe zum Auffinden der SVN-Revision: http://code.google.com/p/svntask/

Hudson hat drei Builds / Jobs: Continuous, Nightly und Release.

Für einen kontinuierlichen / nächtlichen Build: Die Build-Nummer ist die SVN-Revision, die mit svntask gefunden wurde.

Für einen Release-Build / Job: Build-Nummer ist die von Ant aus einer Eigenschaftendatei gelesene Release-Nummer. Die Eigenschaftendatei kann auch mit der Version zur Anzeige der Build-Nummer zur Laufzeit verteilt werden.

Das Ant-Build-Skript fügt die Build-Nummer in die Manifest-Datei der JAR / War-Dateien ein, die während des Builds erstellt werden. Gilt für alle Builds.

Post-Build-Aktion für Release-Builds, einfach mit einem Hudson-Plug-In: Tag-SVN mit der Build-Nummer.

Leistungen:

  • Für eine Entwicklungsversion eines JAR / War kann der Entwickler die SVN-Revision aus dem JAR / War finden und den entsprechenden Code in SVN nachschlagen
  • Bei einer Version entspricht die SVN-Revision dem SVN-Tag mit der Versionsnummer.

Hoffe das hilft.

Raleigh
quelle
Lassen Sie den Release-Build / Job die Eigenschaftendatei für Build- / Release-Nummer wieder in SVN überprüfen?
Matt B
Bei fortlaufenden Builds ist die Build-Nummer die SVN-Versionsnummer. Es muss also nicht eingecheckt werden. Bei Release-Builds wird die eingecheckte Datei zum Abrufen der Build-Nummer verwendet. Normalerweise möchte der Release-Ingenieur oder eine andere Person diese Datei rechtzeitig vor der Veröffentlichung aktualisieren.
Raleigh
4

Ich benutze auch Hudson, obwohl ein viel einfacheres Szenario:

Mein Ant-Skript enthält ein Ziel, das wie folgt aussieht:

<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 legt diese Umgebungsvariablen für mich fest, wenn mein Job ausgeführt wird.

In meinem Fall ist dieses Projekt eine Webanwendung und ich füge diese build-number.txtDatei in den Stammordner der Webanwendung ein - es ist mir egal, wer sie sieht.

In diesem Fall wird die Quellcodeverwaltung nicht mit Tags versehen, da unser Hudson-Job bereits so eingerichtet ist, dass er bei erfolgreichem Build mit der Build-Nummer / dem Zeitstempel versehen wird.

Meine Lösung deckt nur die inkrementellen Build-Nummern für die Entwicklung ab. Wir sind in dem Projekt, in dem wir die Release-Nummern behandeln, noch nicht weit genug gekommen.

matt b
quelle
Zusätzlich (zumindest in aktuellen Jenkins-Versionen) können Sie die Eigenschaften überprüfen: `env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 usw.
raudi
3

Vielleicht möchten Sie auch einen Blick auf das BuildNumber Maven-Plugin und die Ant-Aufgabe in einem Glas werfen, das Sie unter http://code.google.com/p/codebistro/wiki/BuildNumber finden . Ich habe versucht, es einfach und unkompliziert zu machen. Es ist eine sehr kleine JAR-Datei, die nur von der installierten Befehlszeilen-Subversion abhängt.

Sasha O.
quelle
3

So habe ich das gelöst:

  • Die Quellen werden in das Build-Verzeichnis kopiert
  • dann wird die anttask "versioninfo" angewendet
  • Kompilieren Sie die geänderten Quellen

Hier ist die Java-Datei, in der die Versionsinformationen gespeichert sind:

public class Settings {

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

}

Und hier ist die Antitask "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
quelle
2

Hier sind meine 2 Cent:

  • Mein Build-Skript erstellt bei jedem Erstellen der App eine Build-Nummer (mit Zeitstempel!). Dies erzeugt zu viele Zahlen, aber niemals zu wenige. Wenn ich eine Änderung im Code habe, ändert sich die Build-Nummer mindestens einmal.

  • Ich versioniere die Build-Nummer mit jeder Veröffentlichung (wenn auch nicht dazwischen). Wenn ich das Projekt aktualisiere und eine neue Build-Nummer erhalte (weil jemand anderes eine Version erstellt hat), überschreibe ich meine lokale Version und beginne von vorne. Dies kann zu einer niedrigeren Build-Nummer führen, weshalb ich den Zeitstempel eingefügt habe.

  • Wenn eine Veröffentlichung erfolgt, wird die Build-Nummer als letztes Element in einem einzelnen Commit mit der Meldung "Build 1547" festgeschrieben. Danach, wenn es sich um eine offizielle Veröffentlichung handelt, wird der gesamte Baum markiert. Auf diese Weise enthält die Build-Datei immer alle Tags und es gibt eine einfache 1: 1-Zuordnung zwischen Tags und Build-Nummern.

[BEARBEITEN] Ich stelle eine version.html mit meinen Projekten bereit und kann dann mit einem Scraper einfach eine genaue Karte erfassen, was wo installiert ist. Wenn Sie Tomcat oder ähnliches verwenden, geben Sie die Build-Nummer und den Zeitstempel in das descriptionElement web.xml ein . Denken Sie daran: Merken Sie sich niemals etwas, wenn Sie es von einem Computer für Sie erledigen lassen können.

Aaron Digulla
quelle
Ja ... das haben wir auch. Und es ist ein Schmerz, wenn man sich daran erinnern muss, welcher unglaublich lange Build hier oder da bereitgestellt wurde. So viele Zahlen sind auch Ärger ...
Varkhan
@ Varkhan: Warum?
Platzieren
1

Wir führen unseren Build über CruiseControl aus (fügen Sie hier Ihren bevorzugten Build-Manager ein) und führen den Haupt-Build und die Tests durch.

Anschließend erhöhen wir die Versionsnummer mit Ant und BuildNumber und erstellen eine Eigenschaftendatei mit diesen Informationen sowie dem Erstellungsdatum und anderen Metadaten.

Wir haben eine Klasse, die sich dem Lesen und Bereitstellen für GUIs / Protokolle usw. widmet.

Wir packen das alles zusammen und erstellen ein Deployable, das die Build-Nummer und den entsprechenden Build miteinander verbindet. Alle unsere Server geben diese Metainformationen beim Start aus. Wir können die CruiseControl-Protokolle erneut durchgehen und die Build-Nummer mit dem Datum und den Check-ins verknüpfen.

Brian Agnew
quelle
Tipp: Wenn Sie CC und Ants BuildNumber verwenden, können Sie den PropertyFileLabelIncrementer verwenden, um die CC-Labels mit der Build-Nummer synchron zu halten.
Jeffrey Fredrick