Sidelab Commons

SidelabCode tiene un nuevo proyecto, liberado bajo una licencia de código abierto. Se trata de Sidelab Commons. El objetivo de Sidelab Commons es exportar pequeñas librerías de utilidad para que puedan ser utilizadas desde otros proyectos.

Para explicar en más detalle el objetivo del proyecto, aunque muchos ya habrán realizado una asociación con el proyecto Apache Commons, nos centraremos en un caso particular. En varias ocasiones nos hemos encontrado con la necesidad de invocar comandos, a través de la API Runtime (más concretamente Runtime.exec), desde java. Además, esta funcionalidad la hemos requerido desde proyectos muy diferentes:

  • En Optsicom Remote Experiment System (Optsicom RES) se utiliza para ejecutar en una máquina remota un programa Java arrancando una nueva VM.
  • En Optsicom Framework se utiliza para ejecutar un experimento en una VM diferente.
  • En Pascaline, un plugin de Eclipse para Pascal, se utiliza para invocar el compilador de FreePascal.
  • SidelabCode Stack, el instalador de la forja de SidelabCode, se utiliza para invocar apt-gets y todo tipo de comandos que permiten configurar adecuadamente los servicios de la forja.
  • En proyectos que hemos desarrollado para empresas también hemos necesitado invocar comandos externos a menudo.

El uso de Runtime.exec no es trivial. Además de las diferentes versiones disponibles, hay que tener en cuenta la captura de la salida estándar y la salida de error del proceso que se está lanzando. Normalmente, para cerciorarnos de que el proceso ha terminado correctamente es necesario:

  • Al terminar el proceso comprobar el código de salida
  • Recuperar la salida estándar
  • Recuperar la salida de error

La recuperación de las salidas del proceso requiere la creación de un hilo para la salida estándar y otro para la salida de error que consumen los datos de los respectivos InputStreams hasta que el proceso los cierra. Básicamente, el código tiene este aspecto:

new Runnable() {
  public void run() {
    try {
      byte[] buffer = new byte[1024];
      int leidos = 0;
      while ((leidos = System.in.read(buffer)) != 0) {
        out.write(buffer, 0, leidos);
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

Como esto es tedioso de hacer cada vez, en su día lo factorizamos en una pequeña librería que llamamos commandline. Ahora, hemos abierto el proyecto Sidelab Commons para alojar este tipo de recursos. Actualmente, cualquier proyecto puede hacer uso de commandline, y obtener de forma sencilla la salida de un proceso:

CommandLine cl = new CommandLine(); // Opcionalmente podríamos especificar el dir de trabajo en el constructor.
CommandOutput co = cl.syncExec("tail /etc/apache2/sites-enabled/default");
System.out.println(co.getStandardOutput());
System.out.println(co.getErrorOutput());

Puedes descargar commandline o añadirlo como dependencia a tu pom. Echa un vistazo a nuestro archiva.

Por cierto, recuerda que cuando invocas comandos desde Java, no estás ejecutando el comando dentro de una shell, y por tanto no funcionan los wildcards (cosas como ls *.java). Si necesitas realmente ejecutar tu comando desde una shell para que te interprete adecuadamente este tipo de comodines usa bash -c ‘ls *.java’ o mira la documentación de la shell que soporte tu sistema. Nosotros, por ejemplo, hemos tenido problemas ejecutando un proceso en Windows y tuvimos que utilizar «cmd /c» para correrlo dentro de la shell de Windows en modo no interactivo. Puedes echar un vistazo a esto en el proyecto Pascaline.

Resolución de dependencias en plugins de Eclipse con Maven

En la lista de distribución de tycho, he visto una interesante discusión sobre cómo tener una dependencia a un jar en un plugin que se resuelva a través de Maven. Parece que no es problema hacer esto cuando se construye el artefacto, pero en desarrollo tiene que estar el jar forzosamente en el workspace o habrá problemas de compilación.

En Sidelab estamos comenzando a migrar algunos proyectos a Maven (puedes ver cómo lo hemos hecho en los proyectos Pascaline y Optsicom RES), y necesitamos tener un ojo puesto en este tipo de cuestiones. Concretamente utilizamos el plugin m2eclipse para Eclipse y tycho para construir artefactos PDE (plugins, features, repositorios p2).

En Pascaline, por ejemplo, tenemos un jar en uno de los plugins que me gustaría que se resolviese dinámicamente cuando haces un checkout del repositorio. Sé que puedo hacer esto cuando hago un build con Maven, porque hay formas de generar artefactos en previamente a la construcción para que sean utilizados en la compilación (como se comenta  en el mismo hilo), pero lo realmente interesante sería hacer un checkout del repositorio y que el plugin m2eclipse resolviera las dependencias materializando el jar desde el repositorio Nexus que tenemos instalado en SidelabCode. Actualmente, el jar, situado en una carpeta lib del plugin, está alojado también en el repositorio, por comodidad. Esta situación es desaconsejable y hace tedioso pasar de una versión a otra del mismo, porque hay que quitar un jar y sustituirlo por otro, pero sobre todo, no queda claro en ningún sitio qué versión del jar se está utilizando. Por supuesto el jar puede incluir en el nombre la versión, pero preferiría que el plugin declarara qué versión del jar necesita y esa versión fuera incorporada en el proyecto dinámicamente.

Creo que esto lo puede hacer Maven automáticamente, siempre que las dependencias se declaren en el POM. Pero en este caso la dependencia se especifica en el fichero build.properties del plugin, que es manejado por tycho. Este el problema principal del enfoque Manifest first frente al enfoque POM first. En el enfoque Manifest first la construcción está dirigida por tycho y es leída de los descriptores del plugin (Manifest, build.properties y plugin.xml). En el enfoque POM first, la configuración es leída del POM. De momento, por tanto, no existe una solución satisfactoria. Me pregunto si tycho podría proporcionar un goal que hiciera exactamente esto y que pudiera ejecutarse tras un checkout para materializar los jars/plugins/etc que conforman las dependencias del código descargado. ¿Alguna idea al respecto?

[UPDATE] En este ejemplo parecen hacer justamente eso: descargar un jar en una carpeta. Por lo visto esta acción hay que engancharla al lifecycle-mapping de m2e.