Compilación y numeración de versiones para proyectos Java (ant, cvs, hudson)

134

¿Cuáles son las mejores prácticas actuales para la numeración de compilación sistemática y la gestión del número de versión en proyectos Java? Específicamente:

  • Cómo administrar números de compilación sistemáticamente en un entorno de desarrollo distribuido

  • Cómo mantener los números de versión en origen / disponibles para la aplicación en tiempo de ejecución

  • Cómo integrarse correctamente con el repositorio de origen

  • Cómo administrar más automáticamente los números de versión frente a las etiquetas de repositorio

  • Cómo integrarse con una infraestructura de construcción continua

Hay bastantes herramientas disponibles, y ant (el sistema de compilación que estamos usando) tiene una tarea que mantendrá un número de compilación, pero no está claro cómo administrar esto con múltiples desarrolladores concurrentes que usan CVS, svn o similar .

[EDITAR]

Varias respuestas parciales o específicas buenas y útiles han aparecido a continuación, así que resumiré algunas de ellas. Me parece que en realidad no hay una "mejor práctica" sólida sobre esto, sino una colección de ideas superpuestas. A continuación, encuentre mis resúmenes y algunas preguntas resultantes que la gente podría intentar responder como seguimientos. [Nuevo en stackoverflow ... proporcione comentarios si estoy haciendo esto mal]

  • Si está utilizando SVN, la versión de un pago específico aparece para el viaje. La numeración de compilación puede explotar esto para crear un número de compilación único que identifique la comprobación / revisión específica. [CVS, que estamos utilizando por razones heredadas, no proporciona este nivel de conocimiento ... la intervención manual con etiquetas lo lleva a la mitad del camino].

  • Si está utilizando Maven como su sistema de compilación, hay soporte para producir un número de versión desde SCM, así como un módulo de lanzamiento para producir lanzamientos automáticamente. [No podemos usar Maven, por una variedad de razones, pero esto ayuda a aquellos que sí pueden. [Gracias a marcelo-morales ]]

  • Si está utilizando hormiga como su sistema de compilación, la siguiente descripción de tarea puede ayudarlo a producir un archivo .properties de Java que capture la información de compilación, que luego se puede plegar en su compilación de varias maneras. [Ampliamos esta idea para incluir información derivada de Hudson , gracias marty-lamb ].

  • Ant y Maven (y Hudson y control de crucero) proporcionan medios fáciles para obtener números de compilación en un archivo .properties, o en un archivo .txt / .html. ¿Es esto lo suficientemente "seguro" como para evitar que sea manipulado intencionalmente o accidentalmente? ¿Es mejor compilarlo en una clase de "versiones" en tiempo de compilación?

  • Afirmación: La numeración de compilación debe definirse / promulgarse en un sistema de integración continua como Hudson . [Gracias a marcelo-morales ] Hemos tomado esta sugerencia, pero abre la pregunta de ingeniería de lanzamiento: ¿Cómo ocurre un lanzamiento? ¿Hay varios números de compilación en una versión? ¿Existe una relación significativa entre los números de compilación de diferentes versiones?

  • Pregunta: ¿Cuál es el objetivo detrás de un número de compilación? ¿Se usa para el control de calidad? ¿Cómo? ¿Lo utilizan principalmente los desarrolladores para desambiguar entre varias compilaciones durante el desarrollo, o más para el control de calidad para determinar qué compilación obtuvo un usuario final? Si el objetivo es la reproducibilidad, en teoría esto es lo que debe proporcionar un número de versión de lanzamiento, ¿por qué no? (responda esto como parte de sus respuestas a continuación, ayudará a iluminar las elecciones que ha hecho / sugerido ...)

  • Pregunta: ¿Hay un lugar para los números de compilación en las compilaciones manuales? ¿Es esto tan problemático que TODOS deberían usar una solución de CI?

  • Pregunta: ¿Deberían registrarse los números de compilación en el SCM? Si el objetivo es identificar de manera confiable e inequívoca una compilación en particular, cómo hacer frente a una variedad de sistemas de compilación continua o manual que pueden bloquearse / reiniciarse, etc.

  • Pregunta: ¿Debe un número de compilación ser corto y dulce (es decir, un número entero que aumenta monotónicamente) para que sea fácil pegarse en los nombres de archivo para el archivo, fácil de consultar en la comunicación, etc., o debe ser largo y lleno de nombres de usuario, sellos de fecha, nombres de máquinas, etc.

  • Pregunta: Proporcione detalles sobre cómo la asignación de números de compilación se ajusta a su proceso de lanzamiento automatizado más grande. Sí, amantes de Maven, sabemos que esto ya está hecho, pero no todos hemos bebido el kool-aid todavía ...

Realmente me gustaría desarrollar esto en una respuesta completa, al menos para el ejemplo concreto de nuestra configuración cvs / ant / hudson, para que alguien pueda construir una estrategia completa basada en esta pregunta. Marcaré como "La respuesta" a cualquiera que pueda dar una descripción de este caso en particular (incluido el esquema de etiquetado cvs, los elementos de configuración de CI relevantes y el procedimiento de lanzamiento que dobla el número de compilación en el lanzamiento de manera programática). accesible.) Si desea preguntar / responder para otra configuración particular (por ejemplo, svn / maven / control de crucero), enlazaré con la pregunta desde aquí. --JA

[EDITAR 23 oct 09] Acepté la respuesta mejor votada porque creo que es una solución razonable, mientras que varias de las otras respuestas también incluyen buenas ideas. Si alguien quiere probar sintetizar algunos de estos con marty-lamb 's, consideraré aceptar uno diferente. La única preocupación que tengo con marty-lamb's es que no produce un número de compilación serializado confiablemente; depende de un reloj local en el sistema del constructor para proporcionar números de compilación inequívocos, lo cual no es genial.

[Editar 10 de julio]

Ahora incluimos una clase como la siguiente. Esto permite que los números de versión se compilen en el ejecutable final. Se emiten diferentes formas de la información de versión en los datos de registro, productos de salida archivados a largo plazo, y se utilizan para rastrear nuestro análisis (a veces años después) de productos de salida a una compilación 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();
  }
}

Deje comentarios si esto merece convertirse en una discusión wiki.

andersoj
fuente
2
Para futuros lectores, tenga en cuenta que el número de revisión en el código que sugirió es del archivo, no la revisión global del repositorio. Para obtener más información, consulte: subversion.apache.org/faq.html#version-value-in-source
maayank
2
Me pregunto si alguien tiene enfoques simples similares al usar gradley / o git.
bnjmn

Respuestas:

63

Para varios de mis proyectos, capturo el número de revisión de subversión, el tiempo, el usuario que ejecutó la compilación y cierta información del sistema, los guardo en un archivo .properties que se incluye en el archivo jar de la aplicación y lo leo en tiempo de ejecución.

El código de la hormiga se ve así:

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

Es simple extender esto para incluir cualquier información que desee agregar.

Marty Lamb
fuente
12
Para una versión multiplataforma, use esto en lugar de la propiedad whoami anterior: <entry key = "builder" value = "$ {user.name}" />
Ed Brannin
-1 por tener una solución dependiente de la plataforma. Una buena manera de tener todas las propiedades disponibles en un archivo es: `<property name =" antprops.file "location =" $ {build.temp.project.dir} /used_ant.properties "/> <echoproperties destfile =" $ {antprops.file} "/> <! - ordena el archivo, SOLO para ant 1.7.0 y más reciente !!! -> <concat> <union> <sort> <tokens> <file file =" $ {antprops .file} "/> <linetokenizer includedelims =" true "/> </tokens> </sort> </union> </concat>`
raudi
¿Te comprometes de nuevo después de hacer esto? ¿O se hace esto antes de comprometer sus revisiones reales?
Charles Wood
66
¿Cómo hacer esto para el repositorio git?
TechCrunch
46

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

Tu código java

String ver = MyClass.class.getPackage().getImplementationVersion();
usuario146146
fuente
66
+1 por usar una propiedad que Java ya admite con un método como ese.
Ed Brannin
2
-1 por tener el número de compilación en build.xml y no en un archivo de propiedades separado
raudi
6
  • Los números de compilación deben estar asociados con un servidor de integración continua como Hudson . Use diferentes trabajos para diferentes sucursales / equipos / distribuciones.
  • Para mantener el número de versión en la compilación final, recomendaría simplemente usar maven para el sistema de compilación. Creará un archivo .properties archivado en el archivo .jar / .war / .whatever-ar final META-INF/maven/<project group>/<project id>/pom.properties. El archivo .properties contendrá la propiedad de versión.
  • Como estoy recomendando a Maven, le insto a que consulte el complemento de lanzamiento para preparar el lanzamiento en el repositorio de origen y mantener las versiones sincronizadas.
H Marcelo Morales
fuente
6

Software:

  • SVN
  • Hormiga
  • Hudson, para una integración continua
  • svntask, una tarea Ant para encontrar la revisión SVN: http://code.google.com/p/svntask/

Hudson tiene tres compilaciones / trabajos: continuo, nocturno y de lanzamiento.

Para una compilación continua / nocturna: el número de compilación es la revisión SVN, que se encuentra usando svntask.

Para una compilación / trabajo de lanzamiento: el número de compilación es el número de lanzamiento, leído por Ant, de un archivo de propiedades. El archivo de propiedades también se puede distribuir con la versión para mostrar el número de compilación en tiempo de ejecución.

El script de compilación Ant coloca el número de compilación en el archivo de manifiesto de los archivos jar / war que se crean durante la compilación. Se aplica a todas las compilaciones.

Acción posterior a la compilación para las versiones de lanzamiento, realizada fácilmente usando un complemento de Hudson: etiqueta SVN con el número de compilación.

Beneficios:

  • Para una versión de desarrollo de un jar / war, el desarrollador puede encontrar la revisión SVN del jar / war y buscar el código correspondiente en SVN
  • Para una versión, la revisión SVN es la que corresponde a la etiqueta SVN que tiene el número de versión.

Espero que esto ayude.

Raleigh
fuente
¿Tiene la versión de compilación / trabajo comprueba el archivo de propiedades del número de compilación / versión nuevamente en SVN?
mate b
Para compilaciones continuas, el número de compilación es el número de revisión SVN. Por lo tanto, no hay nada que registrar. Para las versiones de lanzamiento, es el archivo registrado que se utiliza para obtener el número de compilación. Por lo general, el ingeniero de lanzamiento o cualquier otra persona desearía actualizar este archivo mucho antes del lanzamiento.
Raleigh
4

También estoy usando Hudson, aunque un escenario mucho más simple:

Mi script Ant tiene un objetivo que se ve así:

<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 establece estas variables de entorno para mí cada vez que se ejecuta mi trabajo.

En mi caso, este proyecto es una aplicación web y estoy incluyendo este build-number.txtarchivo en la carpeta raíz de la aplicación web. Realmente no me importa quién lo vea.

No etiquetamos el control de origen cuando esto se hace porque ya tenemos nuestro trabajo Hudson configurado para etiquetarlo con el número de compilación / marca de tiempo cuando la compilación es exitosa.

Mi solución solo cubre los números de compilación incrementales para el desarrollo, aún no hemos llegado lo suficientemente lejos en el proyecto donde estamos cubriendo los números de lanzamiento.

mate b
fuente
además (al menos en las versiones actuales de jenkins) puede consultar las propiedades: `env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 etc.
raudi
3

También puede echar un vistazo al complemento BuildNumber Maven y la tarea Ant en un jar que se encuentra en http://code.google.com/p/codebistro/wiki/BuildNumber . Traté de hacerlo simple y directo. Es un archivo jar muy pequeño que solo depende de la línea de comando instalada en Subversion.

Sasha O
fuente
3

Así es como resolví esto:

  • las fuentes se copian en el directorio de compilación
  • entonces se aplica la antitarea "versioninfo"
  • compilar las fuentes modificadas

Aquí está el archivo java que almacena la información de la versión:

public class Settings {

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

}

Y aquí está la antitarea "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
fuente
2

Aquí están mis 2 centavos:

  • Mi script de compilación crea un número de compilación (¡con marca de tiempo!) Cada vez que construyo la aplicación. Esto crea demasiados números pero nunca muy pocos. Si tengo un cambio en el código, el número de compilación cambiará al menos una vez.

  • Modifico el número de compilación con cada versión (aunque no en el medio). Cuando actualizo el proyecto y obtengo un nuevo número de compilación (porque alguien más realizó un lanzamiento), sobrescribo mi versión local y empiezo de nuevo. Esto puede conducir a un número de compilación más bajo, por eso he incluido la marca de tiempo.

  • Cuando ocurre una versión, el número de compilación se confirma como el último elemento en una única confirmación con el mensaje "compilación 1547". Después de eso, cuando se trata de un lanzamiento oficial, se etiqueta todo el árbol. De esta manera, el archivo de compilación siempre tiene todas las etiquetas y hay una asignación simple 1: 1 entre etiquetas y números de compilación.

[EDITAR] Despliegue una versión.html con mis proyectos y luego, puedo usar un raspador para simplemente recopilar un mapa preciso de lo que está instalado donde. Si está utilizando Tomcat o similar, coloque el número de compilación y la marca de tiempo en el descriptionelemento de web.xml . Recuerde: nunca memorice nada cuando pueda hacer que una computadora lo haga por usted.

Aaron Digulla
fuente
Sí ... eso es lo que tenemos nosotros también. Y es difícil de usar, cuando tienes que recordar qué construcción increíblemente larga se ha implementado aquí o allá. Demasiados números también son un problema ...
Varkhan
@Varkhan: ¿Por qué? Simplemente coloque la versión en algún lugar donde un raspador de HTML pueda recopilarlo para usted ...
Aaron Digulla
1

Ejecutamos nuestra compilación a través de CruiseControl (inserte aquí su administrador de compilación favorito) y realizamos la compilación principal y las pruebas.

Luego incrementamos el número de versión usando Ant y BuildNumber y creamos un archivo de propiedades con esta información más la fecha de compilación y otros metadatos.

Tenemos una clase dedicada a leer esto y proporcionarlo a GUI / logs, etc.

Luego empaquetamos todo esto y construimos un enlace desplegable junto con el número de compilación y la compilación correspondiente. Todos nuestros servidores vuelcan esta metainformación al inicio. Podemos volver a revisar los registros de CruiseControl y vincular el número de compilación a la fecha y los registros.

Brian Agnew
fuente
Consejo: Si está utilizando CC y AntNúmero de compilación, puede usar PropertyFileLabelIncrementer para mantener las etiquetas CC sincronizadas con el número de compilación.
Jeffrey Fredrick el