Javaプロジェクトでの体系的なビルド番号付けとバージョン番号管理の現在のベストプラクティスは何ですか?具体的には:
分散開発環境でビルド番号を体系的に管理する方法
ソースでバージョン番号を維持する方法/ランタイムアプリケーションで使用可能
ソースリポジトリと適切に統合する方法
バージョン番号とリポジトリタグをより自動的に管理する方法
継続的なビルドインフラストラクチャと統合する方法
非常に多くのツールが利用可能であり、ant(使用しているビルドシステム)にはビルド番号を維持するタスクがありますが、CVS、svnなどを使用する複数の同時開発者でこれを管理する方法は明確ではありません。
[編集]
いくつかの適切で役立つ部分的または具体的な回答が下に表示されているので、それらのいくつかを要約します。私には、これには実際には強力な「ベストプラクティス」ではなく、重複するアイデアのコレクションがあるように聞こえます。以下に、私の要約と、フォローアップとして回答しようとするかもしれないいくつかの質問を見つけます。 [stackoverflowの新機能...これを間違っている場合は、コメントを入力してください。]
SVNを使用している場合、特定のチェックアウトのバージョン管理がライドに付随します。ビルド番号はこれを利用して、特定のチェックアウト/リビジョンを識別する一意のビルド番号を作成できます。 [レガシーの理由で使用しているCVSは、このレベルの洞察をまったく提供していません。タグを手動で操作すると、そこまで到達できません。]
ビルドシステムとしてmavenを使用している場合、SCMからバージョン番号を生成するためのサポートと、自動的にリリースを生成するためのリリースモジュールがあります。 [さまざまな理由でmavenを使用することはできませんが、これはできる人に役立ちます。 [ marcelo-morales ]に感謝します]
ビルドシステムとして ant を使用している場合、次のタスクの説明は、ビルド情報をキャプチャしたJava .propertiesファイルを生成するのに役立ちます。 [このアイデアを拡張して、ハドソンから派生した情報を追加しました。ありがとう marty-lamb ]。
AntとMaven(およびハドソンとクルーズコントロール)は、ビルド番号を.propertiesファイルまたは.txt/.htmlファイルに取得する簡単な手段を提供します。これは、意図的または誤って改ざんされるのを防ぐのに十分な「安全」ですか?ビルド時に「バージョン管理」クラスにコンパイルする方が良いですか?
アサーション:ビルド番号は hudson のような継続的インテグレーションシステムで定義/実施する必要があります。 [おかげで marcelo-morales ]この提案を受け入れましたが、それはリリースエンジニアリングの疑問を解き放ちます:リリースはどうやって起こるのでしょうか?リリースには複数のビルド番号がありますか?異なるリリースのビルド番号間に意味のある関係はありますか?
質問:ビルド番号の背後にある目的は何ですか? QAに使用されていますか?どうやって?主に開発者が開発中に複数のビルドを明確にするために使用するのですか、それともQAがエンドユーザーが取得したビルドを決定するために使用するのですか?目標が再現性である場合、理論上、これはリリースバージョン番号が提供すべきものです。なぜそうではないのですか? (以下の回答の一部としてこれに回答してください。これは、あなたが行った/提案した選択を明らかにするのに役立ちます...)
質問:手動ビルドにビルド番号の場所はありますか?これは非常に問題が多いため、誰もがCIソリューションを使用する必要がありますか?
質問:ビルド番号をSCMにチェックインする必要がありますか?目標が特定のビルドを確実かつ明確に識別することである場合、クラッシュ/再起動/などのさまざまな継続的または手動のビルドシステムに対処する方法...
質問:ビルド番号が短くて甘い(つまり、単調に増加する整数)ようにして、アーカイブ用のファイル名に簡単に貼り付けたり、通信で参照したりしやすいようにするか、または長くて完全なユーザー名にする必要がありますか?日付スタンプ、マシン名など?
質問:ビルド番号の割り当てがより大きな自動リリースプロセスにどのように適合するかについての詳細を提供してください。はい、メイヴン愛好家、私たちはこれが行われていることを知っていますが、私たち全員がまだクールエイドを飲んだわけではありません...
少なくともcvs/ant/hudsonのセットアップの具体例については、これを完全な回答に具体化したいので、誰かがこの質問に基づいて完全な戦略を構築できるようにします。この特定のケースにナッツの説明を提供できる人は誰でも「The Answer」としてマークします(cvsのタグ付けスキーム、関連するCI構成アイテム、ビルド番号をプログラムに組み込むようにリリースに組み込むリリース手順を含む)別の特定の構成(たとえば、svn/maven/cruiseコントロール)を尋ねる/答えたい場合は、ここから質問にリンクします。 --JA
[2009年10月23日編集]私はそれが合理的な解決策だと思うので、トップ投票の回答を受け入れましたが、他の回答のいくつかには良いアイデアも含まれています。誰かが marty-lamb 'sでこれらのいくつかを合成することでクラックを取りたいなら、私は別のものを受け入れることを検討するでしょう。私がmarty-lambに関して懸念しているのは、確実にシリアル化されたビルド番号を生成しないことです-明確なビルド番号を提供するために、ビルダーシステムのローカルクロックに依存しますが、これは素晴らしいことではありません。
[7月10日編集]
以下のようなクラスが含まれるようになりました。これにより、バージョン番号を最終的な実行可能ファイルにコンパイルできます。さまざまな形式のバージョン情報がロギングデータ、長期アーカイブ出力製品に出力され、出力製品の(場合によっては数年後の)分析を特定のビルドにトレースするために使用されます。
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();
}
}
これがwikiディスカッションに値する場合はコメントを残してください。
私のプロジェクトのいくつかでは、Subversionのリビジョン番号、時間、ビルドを実行したユーザー、およびいくつかのシステム情報をキャプチャし、アプリケーションjarに含まれる.propertiesファイルにそれらを詰め込み、実行時にそのjarを読み取ります。
Antコードは次のようになります。
<!-- 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>
これを拡張して、追加したい情報を含めるのは簡単です。
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>
...
あなたのJavaコード
String ver = MyClass.class.getPackage().getImplementationVersion();
META-INF/maven/<project group>/<project id>/pom.properties
の最終的な.jar/.war/.whatever-arにアーカイブされた.propertiesファイルを作成します。 .propertiesファイルにはバージョンプロパティが含まれます。ソフトウェア:
ハドソンには、連続、夜間、リリースの3つのビルド/ジョブがあります。
連続/夜間ビルドの場合:ビルド番号はsvntaskを使用して検出されたSVNリビジョンです。
リリースビルド/ジョブの場合:ビルド番号は、Antがプロパティファイルから読み取るリリース番号です。実行時にビルド番号を表示するために、リリースとともにプロパティファイルを配布することもできます。
Antビルドスクリプトは、ビルド中に作成されるjar/warファイルのマニフェストファイルにビルド番号を入れます。すべてのビルドに適用されます。
Hudsonプラグインを使用して簡単に実行されるリリースビルドのビルド後のアクション:ビルド番号でSVNをタグ付けします。
利点:
お役に立てれば。
ハドソンも使用していますが、はるかに単純なシナリオです。
私のAntスクリプトには、次のようなターゲットがあります。
<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>
ハドソンは、ジョブを実行するたびにこれらの環境変数を設定します。
私の場合、このプロジェクトはwebappであり、このbuild-number.txt
ファイルはwebappのルートフォルダにあります-誰が見るかはあまり気にしません。
ビルドが成功したときにビルド番号/タイムスタンプでタグ付けするようにHudsonジョブを既にセットアップしているため、これが行われたときにソース管理にタグを付けません。
私のソリューションは、開発用の増分ビルド番号のみを対象としています。リリース番号を対象とするプロジェクトでは、まだ十分な成果を得ていません。
http://code.google.com/p/codebistro/wiki/BuildNumber にある1つのjarでBuildNumber MavenプラグインとAntタスクを確認することもできます。私はそれをシンプルかつ簡単にしようとしました。これは、インストールされているコマンドラインSubversionのみに依存する非常に小さなjarファイルです。
これは私がこれを解決した方法です:
Javaバージョン情報を保存するファイル:
public class Settings {
public static final String VERSION = "$VERSION$";
public static final String DATE = "$DATE$";
}
そして、これが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>
これが私の2セントです。
ビルドスクリプトは、アプリをビルドするたびにビルド番号(タイムスタンプ付き!)を作成します。これにより、作成される数値が多すぎますが、少なすぎることはありません。コードに変更がある場合、ビルド番号は少なくとも1回変更されます。
すべてのリリースでビルド番号をバージョン管理します(間ではありません)。プロジェクトを更新して新しいビルド番号を取得すると(誰かがリリースしたため)、ローカルバージョンを上書きして最初からやり直します。これにより、ビルド番号が低くなる可能性があるため、タイムスタンプを含めました。
リリースが発生すると、ビルド番号は、「build 1547」というメッセージとともに、単一コミットの最後のアイテムとしてコミットされます。その後、公式リリースになると、ツリー全体にタグが付けられます。このように、ビルドファイルには常にすべてのタグがあり、タグとビルド番号の間には単純な1:1マッピングがあります。
[編集]プロジェクトでversion.htmlをデプロイし、スクレーパーを使用して、どこにインストールされているかを正確なマップを簡単に収集できます。 Tomcatなどを使用している場合は、ビルド番号とタイムスタンプを web.xml のdescription
要素に入れます。覚えておいてください:コンピュータにあなたに代わってそれをさせることができるとき、何も覚えてはいけません。
CruiseControlを使用してビルドを実行し(ここにお気に入りのビルドマネージャーを挿入します)、メインのビルドとテストを実行します。
次に、Antと BuildNumber を使用してバージョン番号をインクリメントし、この情報にビルドの日付およびその他のメタデータを加えたプロパティファイルを作成します。
これを読んで、GUI /ログなどに提供することに専念するクラスがあります。
次に、これらすべてをパッケージ化し、ビルド番号と対応するビルドを結び付けたデプロイ可能なものをビルドします。すべてのサーバーは、起動時にこのメタ情報をダンプします。 CruiseControlログを調べて、ビルド番号を日付とチェックインに関連付けることができます。