Pembuatan dan Penomoran Versi untuk Proyek Java (semut, cvs, hudson)

134

Apa praktik terbaik saat ini untuk penomoran bangun sistematis dan manajemen nomor versi dalam proyek Java? Secara khusus:

  • Bagaimana mengelola angka membangun secara sistematis dalam lingkungan pengembangan terdistribusi

  • Cara mempertahankan nomor versi dalam sumber / tersedia untuk aplikasi runtime

  • Cara mengintegrasikan dengan benar dengan repositori sumber

  • Cara mengelola nomor versi secara lebih otomatis vs. tag repositori

  • Bagaimana mengintegrasikan dengan infrastruktur pembangunan berkelanjutan

Ada cukup banyak alat yang tersedia, dan semut (sistem build yang kami gunakan) memiliki tugas yang akan mempertahankan nomor build, tetapi tidak jelas bagaimana mengelola ini dengan beberapa, pengembang bersamaan menggunakan CVS, svn, atau serupa .

[EDIT]

Beberapa jawaban parsial atau spesifik yang baik dan bermanfaat telah muncul di bawah, jadi saya akan meringkas beberapa di antaranya. Bagi saya, sepertinya tidak ada "praktik terbaik" yang kuat dalam hal ini, melainkan kumpulan ide yang tumpang tindih. Di bawah, temukan rangkuman saya dan beberapa pertanyaan yang mungkin orang coba jawab sebagai tindak lanjut. [Baru di stackoverflow ... berikan komentar jika saya melakukan ini salah.]

  • Jika Anda menggunakan SVN, versi checkout tertentu datang untuk perjalanan. Penomoran build dapat mengeksploitasi ini untuk membuat nomor build unik yang mengidentifikasi checkout / revisi tertentu. [CVS, yang kami gunakan untuk alasan warisan, tidak cukup memberikan tingkat wawasan ini ... intervensi manual dengan tag membuat Anda berpisah di sana.]

  • Jika Anda menggunakan maven sebagai sistem build Anda, ada dukungan untuk menghasilkan nomor versi dari SCM, serta modul rilis untuk secara otomatis menghasilkan rilis. [Kita tidak bisa menggunakan pakar, karena berbagai alasan, tetapi ini membantu mereka yang bisa. [Terima kasih kepada marcelo-morales ]]

  • Jika Anda menggunakan semut sebagai sistem build Anda, deskripsi tugas berikut ini dapat membantu menghasilkan file Java .properties yang mengambil informasi build, yang kemudian dapat dilipat ke build Anda dalam sejumlah cara. [Kami memperluas gagasan ini untuk memasukkan informasi yang diturunkan dari hudson, terima kasih marty-lamb ].

  • Semut dan pakar (dan hudson serta kontrol jelajah) menyediakan cara mudah untuk mendapatkan nomor build ke file .properties, atau ke file .txt / .html. Apakah ini "aman" cukup agar tidak dirusak dengan sengaja atau tidak sengaja? Apakah lebih baik mengkompilasinya menjadi kelas "versi" pada waktu pembuatan?

  • Pernyataan: Nomor penomoran harus didefinisikan / diberlakukan dalam sistem integrasi berkelanjutan seperti hudson . [Terima kasih kepada marcelo-morales ] Kami telah mengambil saran ini, tetapi itu membuka pertanyaan rekayasa rilis: Bagaimana rilis bisa terjadi? Apakah ada beberapa buildnumber dalam rilis? Apakah ada hubungan yang bermakna antara buildnumber dengan rilis yang berbeda?

  • Pertanyaan: Apa tujuan di balik angka membangun? Apakah ini digunakan untuk QA? Bagaimana? Apakah ini digunakan terutama oleh pengembang untuk disambiguasi antara beberapa build selama pengembangan, atau lebih untuk QA untuk menentukan build apa yang didapatkan pengguna akhir? Jika tujuannya adalah reproduktifitas, secara teori inilah yang harus disediakan oleh nomor versi rilis - mengapa tidak? (tolong jawab ini sebagai bagian dari jawaban Anda di bawah, ini akan membantu menerangi pilihan yang telah Anda buat / sarankan ...)

  • Pertanyaan: Apakah ada tempat untuk membuat angka dalam pembuatan manual? Apakah ini sangat bermasalah sehingga SEMUA ORANG harus menggunakan solusi CI?

  • Pertanyaan: Haruskah nomor gedung diperiksa di SCM? Jika tujuannya secara andal dan tidak ambigu mengidentifikasi bangunan tertentu, bagaimana cara mengatasi berbagai sistem pembangunan berkelanjutan atau manual yang mungkin macet / restart / dll ...

  • Pertanyaan: Haruskah nomor build pendek dan manis (mis., Bilangan bulat meningkat secara monoton) sehingga mudah untuk memasukkan nama file untuk arsip, mudah untuk merujuk dalam komunikasi, dll ... atau harus panjang dan penuh dengan nama pengguna, stempel data, nama mesin, dll?

  • Pertanyaan: Harap berikan perincian tentang bagaimana penugasan nomor build sesuai dengan proses rilis otomatis Anda yang lebih besar. Ya, pecinta maven, kita tahu ini dilakukan dan dilakukan, tetapi tidak semua dari kita sudah minum kool-bantuan ...

Saya benar-benar ingin menyempurnakan ini menjadi jawaban yang lengkap, setidaknya untuk contoh nyata dari pengaturan cvs / semut / hudson kami, sehingga seseorang dapat membangun strategi lengkap berdasarkan pertanyaan ini. Saya akan menandai sebagai "The Answer" siapa pun yang dapat memberikan deskripsi sup-ke-kacang untuk kasus khusus ini (termasuk skema penandaan cvs, item konfigurasi CI yang relevan, dan prosedur pelepasan yang melipat nomor build ke dalam rilis sedemikian rupa sehingga secara terprogram dapat diakses.) Jika Anda ingin bertanya / menjawab untuk konfigurasi tertentu lainnya (misalnya, svn / maven / cruise control) saya akan menautkan ke pertanyaan dari sini. --JA

[EDIT 23 Okt 09] Saya menerima jawaban terpilih karena saya pikir itu solusi yang masuk akal, sementara beberapa jawaban lain juga termasuk ide bagus. Jika seseorang ingin mengambil celah dalam mensintesis beberapa dari ini dengan marty-lamb 's, saya akan mempertimbangkan untuk menerima yang berbeda. Satu-satunya masalah yang saya miliki dengan marty-lamb's adalah tidak menghasilkan nomor build bersambung yang andal - tergantung pada jam lokal di sistem builder untuk memberikan angka build yang jelas, yang tidak bagus.

[Edit 10 Jul]

Kami sekarang menyertakan kelas seperti di bawah ini. Ini memungkinkan nomor versi untuk dikompilasi ke dalam executable akhir. Berbagai bentuk info versi dipancarkan dalam data logging, produk-produk keluaran jangka panjang yang diarsipkan, dan digunakan untuk melacak analisis produk-produk keluaran kami (terkadang bertahun-tahun kemudian) ke bangunan tertentu.

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

Tinggalkan komentar jika ini layak menjadi diskusi wiki.

andersoj
sumber
2
Untuk pembaca di masa mendatang, harap dicatat bahwa nomor revisi dalam kode yang Anda sarankan berasal dari file, bukan revisi global dari repositori. Untuk info lebih lanjut, lihat: subversion.apache.org/faq.html#version-value-in-source
maayank
2
Saya bertanya-tanya apakah ada yang punya pendekatan sederhana yang serupa ketika menggunakan gradledan / atau git?
bnjmn

Jawaban:

63

Untuk beberapa proyek saya, saya menangkap nomor revisi subversi, waktu, pengguna yang menjalankan build, dan beberapa informasi sistem, memasukkannya ke file .properties yang dimasukkan ke dalam toples aplikasi, dan membaca toples itu pada saat runtime.

Kode semutnya terlihat seperti ini:

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

Sangat mudah untuk memperluas ini untuk memasukkan informasi apa pun yang mungkin ingin Anda tambahkan.

Marty Lamb
sumber
12
Untuk versi lintas-platform, gunakan ini alih-alih properti whoami di atas: <entry key = "builder" value = "$ {user.name}" />
Ed Brannin
-1 untuk memiliki solusi yang bergantung pada platform. cara yang bagus untuk memasukkan semua properti yang tersedia ke dalam file adalah: `<properti name =" antprops.file "location =" $ {build.temp.project.dir} /used_ant.properties "/> <echoproperties destfile =" $ {antprops.file} "/> <! - urutkan file, HANYA untuk semut 1.7.0 dan yang lebih baru !!! -> <concat> <union> <sort> <tokens> <file file =" $ {antprops .file} "/> <linetokenizer includedelims =" true "/> </tokens> </sort> </union> </concat>`
raudi
Apakah Anda berkomitmen lagi setelah melakukan ini? Atau apakah ini dilakukan sebelum melakukan revisi Anda yang sebenarnya?
Charles Wood
6
Bagaimana melakukan ini untuk repositori git?
TechCrunch
46

Build.xml Anda

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

Kode java Anda

String ver = MyClass.class.getPackage().getImplementationVersion();
pengguna146146
sumber
6
+1 untuk menggunakan properti yang sudah didukung Java dengan metode seperti itu.
Ed Brannin
2
-1 karena memiliki nomor build di build.xml dan tidak dalam file properti terpisah
raudi
6
  • Nomor build harus dikaitkan dengan server integrasi berkelanjutan seperti hudson . Gunakan pekerjaan yang berbeda untuk cabang / tim / distribusi yang berbeda.
  • Untuk menjaga nomor versi dalam versi final, saya akan merekomendasikan hanya menggunakan maven untuk sistem build. Ini akan membuat file properti. Diarsipkan ke final .jar / .war / .whthing-ar on META-INF/maven/<project group>/<project id>/pom.properties. File .properties akan berisi properti versi.
  • Karena saya merekomendasikan pakar, saya akan mendesak Anda untuk memeriksa plugin rilis untuk mempersiapkan rilis pada repositori sumber dan menjaga versi di sinkronisasi.
H Marcelo Morales
sumber
6

Perangkat lunak:

  • SVN
  • Semut
  • Hudson, untuk integrasi berkelanjutan
  • svntask, tugas Semut untuk menemukan revisi SVN: http://code.google.com/p/svntask/

Hudson memiliki tiga bangunan / pekerjaan: Continuous, Nightly and Release.

Untuk build Continuous / Nightly: Build number adalah revisi SVN, ditemukan menggunakan svntask.

Untuk pekerjaan / rilis Release: Build number adalah nomor Release, dibaca oleh Ant, dari file Properties. File properti juga dapat didistribusikan dengan rilis untuk menampilkan nomor build saat runtime.

Skrip build Ant menempatkan nomor build dalam file manifes file jar / perang yang dibuat selama build. Berlaku untuk semua build.

Tindakan pasca-pembangunan untuk rilis Build, dilakukan dengan mudah menggunakan Hudson plug-in: tag SVN dengan nomor build.

Manfaat:

  • Untuk versi dev jar / perang, pengembang dapat menemukan revisi SVN dari jar / perang dan mencari kode yang sesuai di SVN
  • Untuk rilis, revisi SVN adalah yang sesuai dengan tag SVN yang memiliki nomor rilis di dalamnya.

Semoga ini membantu.

Raleigh
sumber
Apakah Anda memiliki Release build / job memeriksa file properti build / release number kembali ke SVN?
matt b
Untuk build berkelanjutan, nomor build adalah nomor revisi SVN. Jadi, tidak perlu check-in. Untuk rilis build, ini file check-in yang digunakan untuk mendapatkan nomor build. Biasanya, insinyur rilis atau siapa pun ingin memperbarui file ini sebelum rilis.
Raleigh
4

Saya juga menggunakan Hudson, meskipun skenario yang jauh lebih sederhana:

Skrip Ant saya memiliki target di dalamnya yang terlihat seperti:

<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 menetapkan variabel lingkungan ini untuk saya setiap kali pekerjaan saya berjalan.

Dalam kasus saya, proyek ini adalah aplikasi web dan saya memasukkan build-number.txtfile ini ke folder root aplikasi web - saya tidak terlalu peduli siapa yang melihatnya.

Kami tidak memberi tag kontrol sumber saat ini dilakukan karena kami sudah menyiapkan pekerjaan Hudson kami untuk menandainya dengan nomor build / cap waktu ketika build berhasil.

Solusi saya hanya mencakup angka build inkremental untuk pengembangan, kami belum cukup jauh dalam proyek di mana kami sedang meliput angka rilis.

matt b
sumber
selain itu (setidaknya dalam versi jenkins saat ini) Anda dapat memeriksa properti: `env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 dll.
raudi
3

Anda mungkin juga ingin melihat plugin BuildNumber Maven dan tugas Ant dalam satu toples yang ditemukan di http://code.google.com/p/codebistro/wiki/BuildNumber . Saya mencoba membuatnya sederhana dan mudah. Ini adalah file jar yang sangat kecil yang hanya bergantung pada baris perintah Subversion yang diinstal.

Sasha O
sumber
3

Beginilah cara saya menyelesaikan ini:

  • sumber disalin ke direktori build
  • maka anttask "versioninfo" diterapkan
  • kompilasi sumber yang dimodifikasi

Berikut adalah file java yang menyimpan info versi:

public class Settings {

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

}

Dan inilah 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
sumber
2

Ini 2 sen saya:

  • Skrip build saya membuat nomor build (dengan stempel waktu!) Setiap kali saya membuat aplikasi. Ini menciptakan terlalu banyak angka tetapi tidak pernah terlalu sedikit. Jika saya memiliki perubahan dalam kode, nomor build akan berubah setidaknya sekali.

  • Saya versi nomor build dengan setiap rilis (meskipun tidak peralihan). Ketika saya memperbarui proyek dan saya mendapatkan nomor build baru (karena orang lain melakukan rilis), saya menimpa versi lokal saya dan memulai dari awal. Ini dapat mengarah ke nomor build yang lebih rendah itulah sebabnya saya menyertakan cap waktu.

  • Ketika rilis terjadi, nomor build dikomit sebagai item terakhir dalam komit tunggal dengan pesan "build 1547". Setelah itu, ketika ini merupakan rilis resmi, seluruh pohon ditandai. Dengan cara ini, file build selalu memiliki semua tag dan ada pemetaan 1: 1 sederhana antara tag dan nomor build.

[EDIT] Saya menggunakan version.html dengan proyek saya dan kemudian, saya bisa menggunakan scraper untuk hanya mengumpulkan peta yang akurat apa yang diinstal di mana. Jika Anda menggunakan Tomcat atau yang serupa, masukkan nomor build dan cap waktu di descriptionelemen web.xml . Ingat: Jangan pernah menghafal apa pun saat komputer dapat melakukannya untuk Anda.

Aaron Digulla
sumber
Ya ... itulah yang kita miliki juga. Dan itu menyakitkan untuk digunakan, ketika Anda harus ingat yang sangat panjang membangun telah digunakan di sini atau di sana. Begitu banyak angka juga masalah ...
Varkhan
@ Vikhan: Kenapa? Cukup letakkan versi di suatu tempat di mana pengikis HTML dapat mengumpulkannya untuk Anda ...
Aaron Digulla
1

Kami menjalankan build kami melalui CruiseControl (masukkan build manager favorit Anda di sini), dan melakukan build utama dan tes.

Kami kemudian menambah nomor versi menggunakan Semut dan BuildNumber dan membuat file properti dengan info ini ditambah tanggal pembuatan dan metadata lainnya.

Kami memiliki kelas yang didedikasikan untuk membaca ini dan menyediakannya untuk GUI / log dll.

Kami kemudian mengemas semua ini dan membangun ikatan yang dapat dipasang bersama-sama nomor build dan build yang sesuai. Semua server kami membuang informasi meta ini saat memulai. Kita dapat kembali melalui log CruiseControl dan mengikat nomor build ke tanggal dan checkin.

Brian Agnew
sumber
Kiat: Jika Anda menggunakan CC dan Semut BuildNumber, Anda dapat menggunakan PropertyFileLabelIncrementer untuk menjaga label CC tetap sinkron dengan nomor build.
Jeffrey Fredrick