miércoles, 30 de enero de 2019

Condiciones en Makefiles

De vez en cuando me toca lidiar con Makefiles. La mayoría de las veces, tengo que tomar tips de varias fuentes para poder completar un solo comando. En este post, me gustaría compartirles una de esas ocasiones. Mi objetivo era ejecutar condicionalmente comandos basado en la versión instalada de Java. Les muestro primero el resultado final.

prueba_java:
ifneq (,$(findstring build 1.8, $(shell java -version 2>&1)))
 @echo "Java 8"
else
 @echo "No Java 8"
endif

El comando java -version retorna algo similar a esto:

openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-0ubuntu0.16.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

Noten el uso de “2>&1”. El comando anterior no imprime la versión a stdout sino a stderr. Por lo que tengo que redireccionar stderr a stdout.

El siguiente paso es buscar la cadena build 1.8 en el resultado de java -version usando la función findstring. Noten que el primer argumento no lleva comillas. Aquí aprendí que las comillas son necesarias sólo si el comando que se ejecutará en la terminal las necesita. Como es el caso del comando echo. Pero en esta situación, findstring es un comando que make ejecutar directamente, y los comandos nativos de make no necesitan comillas.

Por último, comparamos el resultado de findstring con la cadena vacía. El comando findstring retorna build 1.8 si encuentra la cadena en el segundo argumento o una cadena vacía en caso contrario. Noten nuevamente la forma en la que representamos la cadena vacía al no dejar espacio entre el primer paréntesis y la coma.

Les dejo un enlace a un sitio con muy buenos ejemplos. Una vez estaba tratando de entender algo similar a las siguientes lineas:

foo := a.o b.o c.o
bar := $(foo:%.o=%)

Y no sabía ni cómo buscar. Navegando por este tutorial encontré este ejemplo que era muy parecido al código que estaba analizando, y eso me dio la pista de que esto es equivalente al comando patsubst

domingo, 27 de enero de 2019

Java classpath y jar

Recientemente me topé con la forma en la cual las opciones classpath y jar interactúan. Usemos un ejemplo muy sencillo para ilustarlo.

Supongamos que queremos usar una clase que viene en un jar. Por ejemplo:

public class MiClase {
  public static String saludo() {
    return "Saludos de MiClase";
  }
}

Podemos construir un jar de la siguiente manera.

javac MiClase.java
jar cf MiClase.jar MiClase.class

Ahora usemos este jar desde otra clase.

public class Inicio {
  public static void main(String[] args) {
    System.out.println(MiClase.saludo());
  }
}

Si sólo queremos ejecutar esta clase.

javac -cp MiClase.jar Inicio.java
java -cp .:MiClase.jar Inicio

Qué pasa si también queremos crear un jar de esta clase.

jar cfe Inicio.jar Inicio Inicio.class

Noten que el parámetro Inicio indica el punto de entrada a nuestra aplicación. Este fue mi primer intento para ejecutar el programa.

java -cp MiClase.jar -jar Inicio.jar

El cual me generó este error:

Exception in thread "main" java.lang.NoClassDefFoundError: MiClase

Resulta que la opción jar funciona con jars que contienen todas sus dependencias, también les llaman jar ejecutables. La solución es poner los jars en el classpath e indicar el punto de entrada.

java -cp MiClase.jar:Inicio.jar Inicio