Numeração de compilação e versão para projetos Java (ant, cvs, hudson)

134

Quais são as melhores práticas atuais para numeração de compilação sistemática e gerenciamento de número de versão em projetos Java? Especificamente:

  • Como gerenciar números de compilação sistematicamente em um ambiente de desenvolvimento distribuído

  • Como manter os números de versão na origem / disponíveis para o aplicativo de tempo de execução

  • Como integrar corretamente com o repositório de origem

  • Como gerenciar automaticamente números de versão x tags de repositório

  • Como integrar-se à infraestrutura de criação contínua

Existem várias ferramentas disponíveis, e o ant (o sistema de compilação que estamos usando) tem uma tarefa que manterá um número de compilação, mas não está claro como gerenciar isso com vários desenvolvedores simultâneos usando CVS, svn ou similar .

[EDITAR]

Várias respostas parciais ou específicas, boas e úteis, apareceram abaixo; portanto, resumirei algumas delas. Parece-me que não há realmente uma "boa prática" forte sobre isso, mas uma coleção de idéias sobrepostas. Abaixo, encontre meus resumos e algumas perguntas resultantes que as pessoas podem tentar responder como acompanhamento. [Novo no stackoverflow ... forneça comentários se estiver fazendo isso errado.]

  • Se você estiver usando SVN, a versão de um checkout específico será fornecida durante o passeio. A numeração de compilação pode explorar isso para criar um número de compilação exclusivo que identifica o checkout / revisão específico. [O CVS, que estamos usando por motivos herdados, não fornece esse nível de insight ... a intervenção manual com tags leva você a meio caminho.]

  • Se você estiver usando o maven como seu sistema de construção, há suporte para a produção de um número de versão do SCM, bem como um módulo de release para a produção automática de releases. [Não podemos usar maven, por várias razões, mas isso ajuda aqueles que podem. [Obrigado a marcelo-morales ]]

  • Se você estiver usando ant como seu sistema de construção, a descrição da tarefa a seguir pode ajudar a produzir um arquivo Java .properties que captura informações de construção, que podem ser dobradas na construção de várias maneiras. [Expandimos essa idéia para incluir informações derivadas de hudson, graças a marty-lambe ].

  • Ant e maven (e hudson e cruise control) fornecem meios fáceis para obter números de compilação em um arquivo .properties ou em um arquivo .txt / .html. Isso é "seguro" o suficiente para impedir que ela seja violada intencionalmente ou acidentalmente? É melhor compilá-lo em uma classe "versioning" no momento da criação?

  • Afirmação: A numeração de compilação deve ser definida / promulgada em um sistema de integração contínua como hudson . [Graças a marcelo-morales ] Aceitamos essa sugestão, mas ela abre a questão da engenharia de lançamento: como acontece um lançamento? Existem vários buildnumbers em uma liberação? Existe uma relação significativa entre os números de compilação e as diferentes versões?

  • Pergunta: Qual é o objetivo por trás de um número de compilação? É usado para controle de qualidade? Quão? É usado principalmente pelos desenvolvedores para desambiguar entre várias compilações durante o desenvolvimento ou mais para o controle de qualidade determinar qual a compilação que um usuário final obteve? Se o objetivo é a reprodutibilidade, em teoria é isso que o número da versão do release deve fornecer - por que não? (responda a isso como parte de suas respostas abaixo, isso ajudará a iluminar as escolhas que você fez / sugeriu ...)

  • Pergunta: Existe um local para números de compilação nas compilações manuais? Isso é tão problemático que TODOS devem usar uma solução de IC?

  • Pergunta: Os números de compilação devem ser registrados no SCM? Se o objetivo é identificar de maneira confiável e inequívoca uma compilação específica, como lidar com uma variedade de sistemas de compilação contínua ou manual que podem travar / reiniciar / etc ...

  • Pergunta: Se um número de compilação for curto e agradável (ou seja, um número inteiro crescente monotonicamente), para facilitar a inserção de nomes de arquivos para arquivamento, consulta fácil na comunicação, etc ... ou deve ser longo e cheio de nomes de usuários, carimbos de data, nomes de máquinas, etc?

  • Pergunta: forneça detalhes sobre como a atribuição dos números de compilação se encaixa no seu processo de liberação automatizada. Sim, amantes espertos, sabemos que isso é feito e feito, mas nem todos nós já bebemos o kool-aid ainda ...

Eu realmente gostaria de explicar isso em uma resposta completa, pelo menos para o exemplo concreto de nossa configuração cvs / ant / hudson, para que alguém pudesse construir uma estratégia completa com base nessa pergunta. Marcarei como "A resposta" qualquer pessoa que possa fornecer uma descrição detalhada para esse caso em particular (incluindo esquema de marcação cvs, itens de configuração de IC relevantes e procedimento de liberação que dobra o número da compilação na liberação, de forma programática) acessível.) Se você quiser pedir / responder por outra configuração específica (por exemplo, svn / maven / cruise control), irei linkar a pergunta a partir daqui. --JA

[EDIT 23 Out 09] Aceitei a resposta mais votada porque acho que é uma solução razoável, enquanto várias das outras respostas também incluem boas idéias. Se alguém quiser tentar sintetizar alguns deles com o martir-cordeiro , considerarei aceitar outro. A única preocupação que tenho com o marty-lambs é que ele não produz um número de compilação serializado confiável - depende de um relógio local no sistema do construtor para fornecer números de compilação inequívocos, o que não é ótimo.

10 de julho

Agora incluímos uma classe como a abaixo. Isso permite que os números de versão sejam compilados no executável final. Diferentes formas das informações da versão são emitidas nos dados de log, nos produtos de saída arquivados a longo prazo e usados ​​para rastrear nossa análise (às vezes anos depois) dos produtos de saída para uma construção específica.

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

Deixe comentários se isso merece se tornar uma discussão na wiki.

andersoj
fonte
2
Para futuros leitores, observe que o número da revisão no código que você sugeriu é do arquivo, não a revisão global do repositório. Para obter mais informações, consulte: subversion.apache.org/faq.html#version-value-in-source
maayank
2
Gostaria de saber se alguém tem abordagens simples semelhantes ao usar gradlee / ou git?
bnjmn

Respostas:

63

Para vários de meus projetos, capto o número da revisão do subversion, a hora, o usuário que executou a compilação e algumas informações do sistema, coloco-as em um arquivo .properties que é incluído no jar do aplicativo e leio esse jar no tempo de execução.

O código formiga fica assim:

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

É simples estender isso para incluir qualquer informação que você queira adicionar.

Marty Lamb
fonte
12
Para uma versão de plataforma cruzada, use isso em vez da propriedade whoami acima: <entry key = "builder" value = "$ {user.name}" />
Ed Brannin
-1 por ter uma solução dependente da plataforma. Uma boa maneira de ter todas as propriedades ant disponíveis em um arquivo é: `<nome da propriedade =" antprops.file "location =" $ {build.temp.project.dir} /used_ant.properties "/> <echoproperties destfile =" $ {antprops.file} "/> <! - ordena o arquivo, APENAS para ant 1.7.0 e mais recente !!! -> <concat> <union> <sort> <tokens> <file file =" $ {antprops .file} "/> <linetokenizer includedelims =" true "/> </tokens> </sort> </union> </concat>`
raudi
Você se compromete novamente depois de fazer isso? Ou isso é feito antes de confirmar suas revisões reais?
Charles Wood
6
Como fazer isso para o repositório git?
TechCrunch #
46

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

Seu código Java

String ver = MyClass.class.getPackage().getImplementationVersion();
user146146
fonte
6
+1 por usar uma propriedade que o Java já suporta com um método como esse.
Ed Brannin
2
-1 por ter o número de compilação no build.xml e não num ficheiro separado propriedades
raudi
6
  • Os números de compilação devem ser associados a um servidor de integração contínua como hudson . Use trabalhos diferentes para diferentes filiais / equipes / distribuições.
  • Para manter o número da versão na versão final, eu recomendaria apenas o uso do maven for build system. Ele criará um arquivo .properties arquivado no arquivo .jar / .war / .whatever-ar final META-INF/maven/<project group>/<project id>/pom.properties. O arquivo .properties conterá a propriedade version.
  • Como estou recomendando o maven, recomendamos que você verifique o plug - in de lançamento para preparar o lançamento no repositório de origem e manter as versões sincronizadas.
H Marcelo Morales
fonte
6

Programas:

  • SVN
  • Formiga
  • Hudson, para integração contínua
  • svntask, uma tarefa Ant para encontrar a revisão SVN: http://code.google.com/p/svntask/

Hudson tem três builds / jobs: Contínuo, Noturno e Release.

Para uma compilação contínua / noturna: o número da compilação é a revisão do SVN, encontrada usando o svntask.

Para um build / trabalho da versão: Número da versão é o número da versão, lido por Ant, a partir de um arquivo Propriedades. O arquivo de propriedades também pode ser distribuído com o release para exibir o número da compilação no tempo de execução.

O script de construção do Ant coloca o número da construção no arquivo de manifesto dos arquivos jar / war que são criados durante a construção. Aplica-se a todas as compilações.

Ação pós-compilação para compilações do Release, feita facilmente usando um plug-in Hudson: identifique o SVN com o número da compilação.

Benefícios:

  • Para uma versão dev de um jar / war, o desenvolvedor pode encontrar a revisão SVN do jar / war e procurar o código correspondente no SVN
  • Para uma liberação, a revisão SVN é a correspondente à tag SVN que contém o número da liberação.

Espero que isto ajude.

Raleigh
fonte
Você tem o build / job de liberação, verifique novamente o arquivo de propriedades do número de build / release no SVN?
mate b
Para construções contínuas, o número da construção é o número da revisão do SVN. Portanto, nada para fazer check-in. Para compilações de versão, é o arquivo de check-in usado para obter o número da compilação. Normalmente, o engenheiro de versão ou qualquer outra pessoa deseja atualizar esse arquivo com bastante antecedência.
Raleigh
4

Também estou usando o Hudson, embora um cenário muito mais simples:

Meu script Ant tem um destino que se parece com:

<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 define essas variáveis ​​de ambiente para mim sempre que meu trabalho é executado.

No meu caso, este projeto é um aplicativo da web e estou incluindo esse build-number.txtarquivo na pasta raiz do aplicativo - eu realmente não me importo com quem o vê.

Não identificamos o controle de origem quando isso é feito, porque já temos nosso trabalho no Hudson configurado para identificá-lo com o número / carimbo de data / hora da compilação quando a compilação é bem-sucedida.

Minha solução cobre apenas os números de compilação incrementais para desenvolvimento, ainda não chegamos longe o suficiente no projeto em que cobrimos os números de versão.

matt b
fonte
adicionalmente (pelo menos nas versões Jenkins atuais) que você pode verificar as propriedades: `env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 etc.
raudi
3

Você também pode dar uma olhada no plug-in BuildNumber Maven e na tarefa Ant em um jar encontrado em http://code.google.com/p/codebistro/wiki/BuildNumber . Eu tentei torná-lo simples e direto. É um arquivo jar muito pequeno que depende apenas da linha de comando Subversion instalada.

Sasha O
fonte
3

Isto é como eu resolvi isso:

  • as fontes são copiadas para o diretório de construção
  • então o anttask "versioninfo" é aplicado
  • compilar as fontes modificadas

Aqui está o arquivo java que armazena as informações da versão:

public class Settings {

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

}

E aqui está o 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
fonte
2

Aqui estão os meus 2 centavos:

  • Meu script de compilação cria um número de compilação (com registro de data e hora!) Toda vez que crio o aplicativo. Isso cria muitos números, mas nunca poucos. Se eu tiver uma alteração no código, o número da compilação será alterado pelo menos uma vez.

  • Eu versão o número da compilação a cada versão (embora não entre elas). Quando atualizo o projeto e recebo um novo número de compilação (porque outra pessoa fez um release), substituo minha versão local e recomeço. Isso pode levar a um número menor de compilação, motivo pelo qual incluí o carimbo de data / hora.

  • Quando uma liberação acontece, o número da compilação é confirmado como o último item em uma única confirmação com a mensagem "compilação 1547". Depois disso, quando é um lançamento oficial, toda a árvore é marcada. Dessa forma, o arquivo de compilação sempre possui todas as tags e existe um mapeamento 1: 1 simples entre tags e números de compilação.

[EDIT] Implantei um version.html com meus projetos e, em seguida, posso usar um raspador para simplesmente coletar um mapa preciso do que está instalado onde. Se você estiver usando o Tomcat ou similar, coloque o número da compilação e o carimbo de data e hora no descriptionelemento web.xml . Lembre-se: nunca memorize nada quando puder que um computador faça isso por você.

Aaron Digulla
fonte
Sim ... é o que temos também. E é difícil usar isso quando você precisa se lembrar de qual build incrivelmente longo foi implantado aqui ou ali. Assim, muitos números é problema também ...
Varkhan
@Varkhan: Por quê? Basta colocar a algum lugar versão onde um raspador de HTML pode cobrá-lo para você ...
Aaron Digulla
1

Executamos nossa compilação via CruiseControl (insira seu gerente de compilação favorito aqui) e executamos a compilação e os testes principais.

Em seguida, incrementamos o número da versão usando Ant e BuildNumber e criamos um arquivo de propriedades com essas informações, mais a data da compilação e outros metadados.

Temos uma classe dedicada a ler isso e fornecê-lo a GUIs / logs etc.

Em seguida, empacotamos tudo isso e construímos um conjunto implementável unindo o número da compilação e a compilação correspondente. Todos os nossos servidores despejam essas informações meta na inicialização. Podemos voltar pelos logs do CruiseControl e vincular o número da compilação à data e aos checkins.

Brian Agnew
fonte
Dica: Se você estiver usando o CC e o BuildNumber do Ant, poderá usar o PropertyFileLabelIncrementer para manter os rótulos do CC sincronizados com o número da compilação.
Jeffrey Fredrick