Java 프로젝트 (ant, cvs, hudson)의 빌드 및 버전 번호 매기기


134

Java 프로젝트에서 체계적인 빌드 번호 매기기 및 버전 번호 관리에 대한 현재 모범 사례는 무엇입니까? 구체적으로 특별히:

  • 분산 개발 환경에서 빌드 번호를 체계적으로 관리하는 방법

  • 소스에서 버전 번호를 유지 보수하는 방법 / 런타임 애플리케이션에서 사용 가능한 방법

  • 소스 리포지토리와 올바르게 통합하는 방법

  • 버전 번호와 저장소 태그를보다 자동으로 관리하는 방법

  • 지속적인 빌드 인프라와 통합하는 방법

사용 가능한 많은 도구가 있으며 개미 (우리가 사용하는 빌드 시스템)에는 빌드 번호를 유지 관리하는 작업이 있지만 CVS, svn 또는 이와 유사한 것을 사용하여 여러 명의 동시 개발자와 함께 관리하는 방법은 명확하지 않습니다 .

[편집하다]

몇 가지 좋고 유용한 부분 또는 구체적인 답변이 아래에 나와 있으므로 그 중 일부를 요약하겠습니다. 이것에 대한 강력한 "모범 사례"가 아니라 중복되는 아이디어 모음이있는 것처럼 들립니다. 아래에서 내 요약과 사람들이 후속 조치로 답변을 시도 할 수있는 몇 가지 결과적인 질문을 찾으십시오. [스택 오버플로가 처음입니다 ...이 작업을 잘못하면 의견을 보내주십시오.]

  • SVN을 사용하는 경우 특정 체크 아웃의 버전 관리가 제공됩니다. 빌드 번호 매기기는이를 이용하여 특정 체크 아웃 / 수정을 식별하는 고유 한 빌드 번호를 만들 수 있습니다. [우리가 레거시 이유로 사용하고있는 CVS는 이러한 수준의 통찰력을 제공하지 않습니다. 태그를 사용하여 수동으로 개입하면 도움이됩니다.]

  • maven을 빌드 시스템으로 사용하는 경우 SCM에서 버전 번호를 생성하고 릴리스를 자동으로 생성하기위한 릴리스 모듈이 지원됩니다. [우리는 여러 가지 이유로 maven을 사용할 수 없지만 이것은 가능한 사람들에게 도움이됩니다. [ 마르첼로 사기 감사합니다 ]]

  • 당신이 사용하는 경우 개미를 빌드 시스템으로, 다음과 같은 작업 설명 캔 도움은 여러 가지 방법으로 빌드로 접을 수있는 Java .properties의 파일 캡처 빌드 정보를 생산하고 있습니다. [우리는 marty-lamb 덕분에 hudson 파생 정보를 포함하도록이 아이디어를 확장했습니다 ].

  • Ant 및 Maven (및 hudson 및 cruise 컨트롤)은 빌드 번호를 .properties 파일 또는 .txt / .html 파일로 가져 오는 쉬운 방법을 제공합니다. 이 "안전한"부분이 의도적으로 또는 실수로 변조되지 않도록하기에 충분합니까? 빌드시 "versioning"클래스로 컴파일하는 것이 더 낫습니까?

  • 주장 : 빌드 넘버링은 hudson 과 같은 지속적인 통합 시스템에서 정의 / 제정되어야합니다 . [ marcelo-morales 에게 감사드립니다. ] 우리는이 제안을 받았지만, 릴리즈 엔지니어링 질문을 공개합니다 : 릴리즈는 어떻게 이루어 집니까? 릴리스에 여러 빌드 번호가 있습니까? 다른 릴리스의 빌드 번호 간에는 의미있는 관계가 있습니까?

  • 질문 : 빌드 번호의 목표는 무엇입니까? 품질 관리에 사용됩니까? 어떻게? 개발자가 주로 개발 중에 여러 빌드를 명확하게하거나 QA가 최종 사용자가 얻은 빌드를 결정하기 위해 더 많이 사용합니까? 재현성이 목표라면 이론적으로 이것은 릴리스 버전 번호가 제공해야하는 것입니다. 왜 그렇지 않습니까? (아래 답변의 일부로이 답변에 답하십시오. 선택 / 제안한 사항을 밝히는 데 도움이됩니다 ...)

  • 질문 : 수동 빌드에 빌드 번호를위한 장소가 있습니까? 모두가 CI 솔루션을 사용해야 할 정도로 문제가 있습니까?

  • 질문 : 빌드 번호를 SCM에 체크인해야합니까? 목표가 확실하고 명확하게 특정 빌드를 식별하는 경우 충돌 / 재시작 등의 다양한 연속 또는 수동 빌드 시스템에 대처하는 방법 ...

  • 질문 : 빌드 번호가 짧고 달콤합니다 (즉, 단조 증가하는 정수)하여 보관을 위해 파일 이름을 사용하기 쉽고 통신에서 참조하기 쉽습니다. 날짜, 기계 이름 등?

  • 질문 : 빌드 번호 할당이 대규모 자동 릴리스 프로세스에 어떻게 적용되는지 자세히 설명하십시오. 예, 메이븐 애호가들, 우리는 이것이 완료되었다는 것을 알고 있지만, 우리 모두가 아직 쿨 에이드를 마신 것은 아닙니다 ...

나는 적어도 cvs / ant / hudson 설정의 구체적인 예를 위해 이것을 완전한 대답으로 육체 화하고 싶습니다. 그래서 누군가이 질문에 기초하여 완전한 전략을 세울 수 있습니다. 이 특정 사례 (cvs 태그 지정 체계, 관련 CI 구성 항목 및 빌드 번호를 릴리스로 접는 릴리스 절차를 포함하여 프로그래밍 방식으로 설명)를 제공 할 수있는 사람이라면 누구나 "답변"으로 표시하겠습니다. 다른 특정 구성 (예 : svn / maven / cruise control)에 대해 질문 / 답변을하려면 여기에서 질문에 연결하겠습니다. --JA

[편집 10 10 월 9 일] 나는 그것이 합리적인 해결책이라고 생각하기 때문에 가장 인기있는 답변을 수락했지만 다른 답변들도 좋은 아이디어를 포함하고 있습니다. 누군가가 이들 중 일부를 marty-lamb 과 합성하는 데 어려움겪고 싶다면 다른 것을 받아들이는 것이 좋습니다. 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();
  }
}

위키 토론이 될만한 의견이 있으면 남겨주세요.


2
향후 독자를 위해 제안한 코드의 개정 번호는 리포지토리의 전체 개정이 아니라 파일의 개정 번호입니다. 자세한 내용은 다음을 참조하십시오 : subversion.apache.org/faq.html#version-value-in-source
maayank

2
gradle및 / 또는 git?를 사용할 때 비슷한 접근 방식을 가진 사람이 있는지 궁금합니다 .
bnjmn

답변:


63

내 프로젝트 중 일부에서는 Subversion 개정 번호, 시간, 빌드를 실행 한 사용자 및 일부 시스템 정보를 캡처하여 응용 프로그램 jar에 포함되는 .properties 파일에 넣고 런타임에 해당 jar을 읽습니다.

개미 코드는 다음과 같습니다.

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

추가하려는 정보를 포함하도록 이것을 확장하는 것은 간단합니다.


12
크로스 플랫폼 버전의 경우 위의 whoami 속성 대신 다음을 사용하십시오. <entry key = "builder"value = "$ {user.name}"/>
Ed Brannin

플랫폼 종속 솔루션이있는 경우 -1입니다. 사용 가능한 모든 개미를 파일로 만드는 좋은 방법은 다음과 같습니다.`<property name = "antprops.file"location = "$ {build.temp.project.dir} /used_ant.properties"/> <echoproperties destfile = "$ {antprops.file} "/> <!-ant 1.7.0 이상에서만 파일을 정렬하십시오 !!!-> <concat> <union> <sort> <tokens> <file file ="$ {antprops .file} "/> <linetokenizer includedelims ="true "/> </ tokens> </ sort> </ union> </ concat>`
raudi

이 작업을 한 후에 다시 커밋합니까? 아니면 실제 개정을 커밋하기 전에 수행됩니까?
Charles Wood

6
자식 저장소에 대해 이것을 수행하는 방법은 무엇입니까?
TechCrunch

46

귀하 의 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>
...

자바 코드

String ver = MyClass.class.getPackage().getImplementationVersion();

6
Java가 이미 지원하는 속성을 이와 같은 방법으로 사용하는 경우 +1
Ed Brannin

2
별도의 속성 파일이 아닌 build.xml에 빌드 번호가있는 경우 -1
raudi

6
  • 빌드 번호는 hudson 과 같은 지속적인 통합 서버와 연결되어야합니다 . 지점 / 팀 / 배포마다 다른 작업을 사용하십시오.
  • 최종 빌드에서 버전 번호를 유지하려면 빌드 시스템에 maven 을 사용하는 것이 좋습니다 . 최종 .jar / .war / .whatever-ar에 아카이브 된 .properties 파일을 작성합니다 META-INF/maven/<project group>/<project id>/pom.properties. .properties 파일에는 버전 속성이 포함됩니다.
  • maven을 권장하기 때문에 소스 저장소에서 릴리스를 준비하고 버전을 동기화 상태로 유지하기 위해 릴리스 플러그인 을 확인하는 것이 좋습니다 .

6

소프트웨어:

  • SVN
  • 개미
  • 지속적인 통합을위한 Hudson
  • SVN 개정을 찾기위한 Ant 태스크 인 svntask : http://code.google.com/p/svntask/

Hudson에는 Continuous, Nightly 및 Release의 세 가지 빌드 / 작업이 있습니다.

Continuous / Nightly 빌드의 경우 : 빌드 번호는 svntask를 사용하여 찾은 SVN 개정입니다.

릴리스 빌드 / 작업 : 빌드 번호는 특성 파일에서 Ant가 읽은 릴리스 번호입니다. 런타임에 빌드 번호를 표시하기 위해 릴리스와 함께 특성 파일을 분배 할 수도 있습니다.

Ant 빌드 스크립트는 빌드 중에 작성된 jar / war 파일의 Manifest 파일에 빌드 번호를 넣습니다. 모든 빌드에 적용됩니다.

릴리스 빌드의 빌드 후 조치 : 빌드 번호가있는 Hudson 플러그인 : 태그 SVN을 사용하여 쉽게 수행 할 수 있습니다.

혜택:

  • jar / war의 개발 버전의 경우 개발자는 jar / war에서 SVN 개정을 찾아 SVN에서 해당 코드를 찾을 수 있습니다.
  • 릴리스의 경우 SVN 개정은 릴리스 번호가 포함 된 SVN 태그에 해당하는 개정입니다.

도움이 되었기를 바랍니다.


릴리스 빌드 / 작업에서 빌드 / 릴리스 번호 특성 파일을 다시 SVN으로 점검합니까?
matt b

연속 빌드의 경우 빌드 번호는 SVN 개정 번호입니다. 따라서 체크인 할 것이 없습니다. 릴리스 빌드의 경우 빌드 번호를 얻는 데 사용되는 체크인 파일입니다. 일반적으로 릴리스 엔지니어 나 다른 사람은 릴리스 전에이 파일을 잘 업데이트하려고합니다.
Raleigh

4

훨씬 간단한 시나리오이지만 허드슨도 사용하고 있습니다.

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

Hudson은 작업이 실행될 때마다 이러한 환경 변수를 설정합니다.

필자의 경우이 프로젝트는 webapp이며 webapp build-number.txt의 루트 폴더 에이 파일을 포함시키고 있습니다.

빌드가 완료되면 빌드 번호 / 타임 스탬프로 태그를 지정하기 위해 Hudson 작업이 이미 설정되어 있으므로 소스 컨트롤에 태그를 지정하지 않습니다.

내 솔루션은 개발을위한 증분 빌드 번호 만 다루지 만 아직 릴리스 번호를 다루는 프로젝트에서는 충분히 얻지 못했습니다.


추가 (현재 젠킨스 버전 적어도) 당신은 속성을 확인 수 있습니다`env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 등
raudi


3

이것이 내가 이것을 해결 한 방법입니다.

  • 소스는 빌드 디렉토리에 복사됩니다
  • anttask "versioninfo"가 적용됩니다
  • 수정 된 소스를 컴파일

다음은 버전 정보를 저장하는 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

여기 내 2 센트가 있습니다 :

  • 내 빌드 스크립트는 앱을 빌드 할 때마다 빌드 번호 (타임 스탬프 포함)를 만듭니다. 이것은 너무 많은 수를 만들지 만 너무 적은 수는 없습니다. 코드가 변경되면 빌드 번호가 한 번 이상 변경됩니다.

  • 매번 릴리스마다 빌드 번호를 버전 화합니다 (중간은 아니지만). 프로젝트를 업데이트하고 다른 사람이 릴리스했기 때문에 새 빌드 번호를 얻으면 로컬 버전을 덮어 쓰고 다시 시작합니다. 이로 인해 빌드 번호가 낮아질 수 있으므로 타임 스탬프를 포함 시켰습니다.

  • 릴리스가 발생하면 빌드 번호는 "build 1547"메시지와 함께 단일 커밋의 마지막 항목으로 커밋됩니다. 그 후 공식 릴리스 인 경우 전체 트리에 태그가 지정됩니다. 이런 식으로 빌드 파일에는 항상 모든 태그가 있으며 태그와 빌드 번호 사이에 간단한 1 : 1 매핑이 있습니다.

[편집] 프로젝트와 함께 version.html을 배포 한 다음 스크레이퍼를 사용하여 어디에 설치되어 있는지 정확하게지도를 수집 할 수 있습니다. Tomcat 또는 이와 유사한 것을 사용하는 경우 빌드 번호와 타임 스탬프 descriptionweb.xml 요소에 넣으십시오 . 기억하십시오 : 컴퓨터가 당신을 위해 그것을 할 수있을 때 아무것도 암기하지 마십시오.


예. 우리도 마찬가지입니다. 그리고 여기저기서 구축 된 엄청나게 긴 빌드를 기억해야 할 때 사용하기가 고통 스럽습니다. 너무 많은 숫자도 문제입니다 ...
Varkhan

@ Varkhan : 왜? HTML 스크레이퍼가 수집 할 수있는 곳에 버전을 넣으십시오.
Aaron Digulla

1

우리는 CruiseControl을 통해 빌드를 실행하고 (여기서 좋아하는 빌드 관리자를 삽입하십시오) 기본 빌드 및 테스트를 수행합니다.

그런 다음 Ant 및 BuildNumber를 사용하여 버전 번호를 늘리고이 정보와 함께 빌드 날짜 및 기타 메타 데이터가있는 특성 파일을 작성하십시오.

우리는 이것을 읽고 GUI / 로그 등에 제공하는 수업을 가지고 있습니다.

그런 다음이 모든 것을 패키징하고 빌드 번호와 해당 빌드를 묶어 배포 가능한 빌드합니다. 모든 서버는 시작할 때이 메타 정보를 덤프합니다. CruiseControl 로그로 돌아가서 빌드 번호를 날짜와 체크인에 연결할 수 있습니다.


팁 : CC 및 Ant의 BuildNumber를 사용하는 경우 PropertyFileLabelIncrementer를 사용하여 CC 레이블을 빌드 번호와 동기화 된 상태로 유지할 수 있습니다.
Jeffrey Fredrick
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.