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

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

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



답변

내 프로젝트 중 일부에서는 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>

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


답변

귀하 의 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();


답변

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

답변

소프트웨어:

  • 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 태그에 해당하는 개정입니다.

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


답변

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

내 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 작업이 이미 설정되어 있으므로 소스 컨트롤에 태그를 지정하지 않습니다.

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


답변

http://code.google.com/p/codebistro/wiki/BuildNumber에 있는 하나의 jar에서 BuildNumber Maven 플러그인 및 Ant 태스크를 살펴볼 수도 있습니다 . 나는 그것을 간단하고 간단하게 만들려고 노력했다. 명령 행 Subversion 만 설치되는 매우 작은 jar 파일입니다.


답변

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

  • 소스는 빌드 디렉토리에 복사됩니다
  • 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>