Makefiles & GNU GCJ

Como ya conté en anteriores posts, en mi proyecto de fin de carrera tenía que compilar una librería de clases Java bastante extensa con GNU GCJ. Es bastante sencillo compilar una clase simple, incluso compilar varias de ellas a mano, pero en el caso de una colección importante, el compilar y enlazar es una tarea muy larga y aburrida.

En su momento estuve buscando cómo podía automatizar esta tarea de compilación por medio de herramientas diseñadas para esta labor. Lo primero que se me ocurrió fue crear un paquete de instalación por medio de las GNU Autotools pero al mirar en la documentación pude comprobar que no había soporte para GCJ por lo que elegí una de las herramientas que se incorporan en este paquete: GNU Make.

Además de la utilización de las Autotools y Make también pensé en utilizar Apache Ant que tiene bastantes características positivas como por ejemplo que no depende de las órdenes del Shell en el que se ejecute el Antfile por lo que la portabilidad está garantizada. A pesar de ésto, y de que Make sí depende del Shell, me decanté por esta última opción al estar más extendido y generalizado en el mundo de GNU/Linux su uso.

La primera gran pregunta que me aborda es ¿cómo puedo escribir el Makefile para compilar toda la librería sin alterar la estructura de paquetes que originalmente tiene en el modelo tradicional de Java?. Podríamos escribir línea por línea la compilación de cada una de las clases con su ruta y al final enlazarlo todo en una línea enorme. Así se puede hacer pero lo mejor sería encontrar la manera de automatizar la lectura de todas las clases para no tener que escribir la ruta a cada una de ellas.

Lo que vamos a hacer es mantener dentro de Makefile varias cadenas que contengan la ruta a todos los ficheros de código fuente que queremos compilar y la ruta a todos los objetos que se originarán después de la compilación de las fuentes, que además serán enlazados. Para este fin utilizaremos la opción wildcard mediante la cual se puede utilizar un comodín tal como lo haríamos con un ls en Bash de manera que se nos devolverá el nombre de todos los ficheros que concuerden con la expresión regular. En primer lugar se obtienen los ficheros de código fuente y luego a partir de ellos cambiaremos .java por .o para tener los objetos.

Después de esto escribiremos las acciones del Makefile del modo lo más genérico posible. Definiremos una acción a partir del nombre de los objetos para compilarlos uno a uno mediante una expresión regular. Una vez tengamos todos los objetos simplemente habrá que realizar el enlace señalando cuál es las clase principal del programa. A continuación os dejo un pequeño ejemplo orientativo donde, en el directorio en el que se encuentra el Makefile ,se han incluido las fuentes en un subdirectorio llamado src y los objetos serán compilados a otro subdirectorio llamado bin:

##############################################
# Makefile para compilación con GCJ
# Creador por ELMOesDIOS para
# https://just4cool.wordpress.com
##############################################

## Destino de los objetos y del ejecutable
DEST_DIR = bin

## Nombre del Ejecutable y la librería dinámica
BINARY_NAME := MiPrograma

## Cargamos las fuentes de todas las clases a compilar
JAVA_SOURCES := $(wildcard src/paqueteA/*.java)
JAVA_SOURCES += $(wildcard src/paqueteA/paqueteAA/*.java)
JAVA_SOURCES += $(wildcard src/paqueteA/paqueteAB/*.java)
JAVA_SOURCES += $(wildcard src/paqueteA/paqueteAB/paqueteABC/*.java)

## A partir de la cadena de fuentes obtenemos la de objetos
JAVA_OBJECTS := $(JAVA_SOURCES:src/%.java=$(DEST_DIR)/%.o)

## Macros para la compilación
GCJ := $(GCC_PATH)/usr/bin/$(GCC_TARGET)gcj
GCJ_FLAGS := -O2 -g –classpath=src

## Instrucción general para obtener el ejecutable
all: $(DEST_DIR)/$(BINARY_NAME)

## Compilar las fuentes de nuestro programa
$(JAVA_OBJECTS): $(DEST_DIR)/%.o: src/%.java
$(GCJ) $(GCJ_FLAGS) -c $< -o $@ ## El ejecutable final se obtiene en el enlace $(DEST_DIR)/$(BINARY_NAME): $(JAVA_OBJECTS) $(GCJ) --main=paqueteA.paqueteAB.clasePrincipal $^ -o $(DEST_DIR)/$(BINARY_NAME) ## Habrá que crear los directorios donde se almacenarán los objetos una vez compilados prepare: mkdir bin/paqueteA; mkdir bin/paqueteA/paqueteAA; mkdir bin/paqueteA/paqueteAB; mkdir bin/paqueteA/paqueteAB/paqueteAC; [/sourcecode]

En el ejemplo, antes escribir make, habría que generar la estructura de directorios para los objetos tal y como se describe en el ejemplo. Además podemos agregar nuevas opciones de instalación y limpieza para que la instalación de nuestro programa Java compilado con GCJ sea estándar del modo:

 $ make prepare
 $ make
 $ sudo make install

Para orientaros en lo que respecta a los símbolos utilizados para construir las expresiones regulares, podéis echar un vistazo al manual de GNU Make. Esto es para un caso pequeño en el que no se utilizan librerías externas. En caso de que se utilicen bastaría con añadir una nueva macro para almacenar sus fuentes; recordemos que en la Introducción a GNU GCJ ya se explicó que las colecciones de clases .class en .jar se pueden compilar. Luego se obtendría una macro para los objetos y se compilarían igual que en el ejemplo. Además habría que modificar el enlace.

Me gustaría indicar también que de esta manera obtendremos únicamente un ejecutable que contiene todo el programa. Es más adecuado compilar utilizando librerías dinámicas. El proceso se puede deducir a partir de este mismo ejemplo, aunque si tenéis algún tipo de problema o alguna duda, dejad un comentario. Espero que si os ha sido útil también lo hagáis. Proximamente dejaré más posts acerca de GNU GCJ, su rendimiento y otras características.

Anuncios

Un pensamiento en “Makefiles & GNU GCJ

  1. Vaya, es una pena que no haya soporte de las autotools para gcj. Llevo un tiempo viendo como funcionan las autotools, y una vez que te manejas medianamente bien con ellas la gestión y compilación de los proyectos software es super sencilla.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s