En esta página, se explica qué son los sistemas de compilación, qué hacen, por qué debes usar un sistema de compilación y por qué los compiladores y las secuencias de comandos de compilación no son la mejor opción a medida que tu organización comienza a escalar. Está destinado a desarrolladores que no tienen mucha experiencia con un sistema de compilación.
¿Qué es un sistema de compilación?
En esencia, todos los sistemas de compilación tienen un propósito sencillo: transforman el código fuente escrito por los ingenieros en objetos binarios ejecutables que las máquinas pueden leer. Los sistemas de compilación no solo son para el código creado por humanos, sino que también permiten que las máquinas creen compilaciones automáticamente, ya sea para pruebas o para lanzamientos en producción. En una organización con miles de ingenieros, es común que la mayoría de las compilaciones se activen automáticamente en lugar de que los ingenieros lo hagan directamente.
¿No puedo usar un compilador?
Es posible que la necesidad de un sistema de compilación no sea evidente de inmediato. La mayoría de los ingenieros no usan un sistema de compilación mientras aprenden a codificar: la mayoría comienza invocando herramientas como gcc
o javac
directamente desde la línea de comandos, o el equivalente en un entorno de desarrollo integrado (IDE). Siempre que todo el código fuente esté en el mismo directorio, un comando como este funciona bien:
javac *.java
Esto le indica al compilador Java que tome todos los archivos fuente de Java del directorio actual y los convierta en un archivo de clase binario. En el caso más simple, esto es todo lo que necesitas.
Sin embargo, en cuanto se expande el código, comienzan las complicaciones. javac
es lo suficientemente inteligente como para buscar en los subdirectorios del directorio actual y encontrar código para importar. Sin embargo, no tiene forma de encontrar código almacenado en otras partes del sistema de archivos (tal vez una biblioteca compartida por varios proyectos). También solo sabe compilar código Java. Los sistemas grandes suelen incluir diferentes partes escritas en una variedad de lenguajes de programación con redes de dependencias entre esas partes, lo que significa que ningún compilador para un solo lenguaje puede compilar todo el sistema.
Una vez que trabajes con código de varios lenguajes o varias unidades de compilación, la compilación de código ya no será un proceso de un solo paso. Ahora debes evaluar de qué depende tu código y compilar esas piezas en el orden correcto, posiblemente con un conjunto diferente de herramientas para cada una. Si cambia alguna dependencia, debes repetir este proceso para evitar depender de objetos binarios inactivos. Para una base de código de tamaño moderado, este proceso se vuelve rápidamente tedioso y propenso a errores.
El compilador tampoco sabe cómo controlar dependencias externas, como archivos JAR
de terceros en Java. Sin un sistema de compilación, podrías descargar la dependencia de Internet, colocarla en una carpeta lib
en el disco duro y configurar el compilador para que lea las bibliotecas de ese directorio. Con el tiempo, es difícil mantener las actualizaciones, las versiones y la fuente de estas dependencias externas.
¿Qué sucede con las secuencias de comandos de shell?
Supongamos que tu proyecto de afición comienza lo suficientemente simple como para que puedas compilarlo solo con un compilador, pero comienzas a encontrarte con algunos de los problemas descritos anteriormente. Tal vez aún no creas que necesitas un sistema de compilación y que puedes automatizar las partes tediosas con algunas secuencias de comandos de shell simples que se encargan de compilar todo en el orden correcto. Esto te ayuda durante un tiempo, pero pronto comienzas a encontrarte con más problemas:
Se vuelve tedioso. A medida que tu sistema se vuelve más complejo, comienzas a pasar casi tanto tiempo trabajando en tus secuencias de comandos de compilación como en el código real. La depuración de secuencias de comandos de shell es un proceso doloroso, ya que cada vez hay más hacks superpuestos.
Es lento. Para asegurarte de no depender accidentalmente de bibliotecas inactivas, la secuencia de comandos de compilación compila cada dependencia en orden cada vez que la ejecutas. Piensas en agregar algo de lógica para detectar qué partes deben reconstruirse, pero eso suena muy complejo y propenso a errores para una secuencia de comandos. O bien puedes pensar en especificar qué partes deben volver a compilarse cada vez, pero vuelves al punto de partida.
¡Buenas noticias! Es hora de lanzar la versión. Mejor averigua todos los argumentos que debes pasar al comando jar para hacer la compilación final. Y recuerda cómo subirlo y enviarlo al repositorio central. Además, compila y envía las actualizaciones de la documentación, y envía una notificación a los usuarios. Hmm, quizás esto requiera otra secuencia de comandos…
¡Desastre! Tu disco duro falla y ahora debes volver a crear todo el sistema. Fuiste lo suficientemente inteligente como para mantener todos tus archivos fuente en el control de versiones, pero ¿qué sucede con las bibliotecas que descargaste? ¿Puedes volver a encontrarlos y asegurarte de que tengan la misma versión que cuando los descargaste por primera vez? Es probable que tus secuencias de comandos dependan de herramientas específicas que se instalaron en lugares específicos. ¿Puedes restablecer ese mismo entorno para que las secuencias de comandos vuelvan a funcionar? ¿Qué sucede con todas esas variables de entorno que configuraste hace mucho tiempo para que el compilador funcionara correctamente y luego olvidaste?
A pesar de los problemas, tu proyecto es lo suficientemente exitoso como para que puedas comenzar a contratar a más ingenieros. Ahora te das cuenta de que no hace falta un desastre para que surjan los problemas anteriores. Debes pasar por el mismo proceso doloroso de inicialización cada vez que un desarrollador nuevo se une a tu equipo. Y, a pesar de tus mejores esfuerzos, aún hay pequeñas diferencias en el sistema de cada persona. A menudo, lo que funciona en la máquina de una persona no funciona en la de otra, y cada vez se necesitan algunas horas de rutas de herramientas de depuración o versiones de bibliotecas para descubrir dónde está la diferencia.
Decides que necesitas automatizar tu sistema de compilación. En teoría, esto es tan simple como obtener una computadora nueva y configurarla para ejecutar la secuencia de comandos de compilación todas las noches con cron. Aún debes pasar por el doloroso proceso de configuración, pero ahora no tienes el beneficio de un cerebro humano que pueda detectar y resolver problemas menores. Ahora, cada mañana cuando llegas, ves que la compilación de la noche anterior falló porque ayer un desarrollador realizó un cambio que funcionó en su sistema, pero no en el sistema de compilación automatizado. Cada vez es una solución simple, pero ocurre con tanta frecuencia que terminas por pasar mucho tiempo todos los días descubriendo y aplicando estas soluciones simples.
Las compilaciones se vuelven más lentas a medida que el proyecto crece. Un día, mientras esperas que se complete una compilación, miras con tristeza el escritorio inactivo de tu compañero de trabajo, que está de vacaciones, y deseas que hubiera una manera de aprovechar toda esa potencia de procesamiento desperdiciada.
Te encontraste con un problema clásico de escala. Para un solo desarrollador que trabaja en un máximo de doscientas líneas de código durante una o dos semanas como máximo (que podría ser toda la experiencia hasta el momento de un desarrollador junior que acaba de graduarse de la universidad), un compilador es todo lo que necesitas. Las secuencias de comandos pueden llevarte un poco más lejos. Sin embargo, en cuanto necesites coordinar entre varios desarrolladores y sus máquinas, incluso una secuencia de comandos de compilación perfecta no será suficiente, ya que se vuelve muy difícil tener en cuenta las diferencias menores en esas máquinas. En este punto, este enfoque simple falla y es hora de invertir en un sistema de compilación real.