¿Por qué crear un sistema de compilación?

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á dirigido a desarrolladores que no tienen mucha experiencia con un sistema de compilación.

¿Qué es un sistema de compilación?

Básicamente, 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 son solo para código creado por humanos; también permiten que las máquinas creen compilaciones de forma automática, ya sea para probar o lanzar versiones 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 hacerlo directamente por ingenieros.

¿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 programar. En la mayoría de los casos, invocan herramientas como gcc o javac directamente desde la línea de comandos o el equivalente en un entorno de desarrollo integrado (IDE). Siempre que el código fuente esté en el mismo directorio, un comando como este funciona correctamente:

javac *.java

De esta manera, se le indica al compilador de Java que tome todos los archivos fuente de Java del directorio actual y los convierta en un archivo de clase binaria. En el caso más simple, esto es todo lo que necesitas.

Sin embargo, en cuanto el código se expande, comienzan las complicaciones. javac es lo suficientemente inteligente a fin de buscar en los subdirectorios del directorio actual para encontrar el código que se debe importar. Sin embargo, no tiene forma de encontrar código almacenado en otras partes del sistema de archivos (quizás una biblioteca compartida por varios proyectos). También sabe cómo compilar código Java. A menudo, los sistemas grandes incluyen diferentes piezas escritas en una variedad de lenguajes de programación con webs de dependencias entre ellas, lo que significa que ningún compilador para un solo lenguaje puede compilar todo el sistema.

Una vez que te ocupes de código de varios lenguajes o varias unidades de compilación, el código de compilación ya no será un proceso de un paso. Ahora, debes evaluar de qué depende tu código y compilar esas partes en el orden adecuado, puedes usar un conjunto de herramientas diferente para cada elemento. Si alguna dependencia cambia, debes repetir este proceso para evitar depender de objetos binarios inactivos. Para una base de código de tamaño uniforme, este proceso se vuelve rápidamente tedioso y propenso a errores.

El compilador tampoco sabe nada sobre cómo controlar dependencias externas, como archivos JAR de terceros en Java. Sin un sistema de compilación, puedes administrar esto si descargas la dependencia de Internet, la colocas en una carpeta lib del disco duro y configuras el compilador para que lea las bibliotecas de ese directorio. Con el tiempo, es difícil mantener las actualizaciones, las versiones y el origen de estas dependencias externas.

¿Qué ocurre con las secuencias de comandos de shell?

Supongamos que tu proyecto de pasatiempo comienza lo suficientemente simple como para poder compilarlo solo con un compilador, pero comienzas a encontrar algunos de los problemas descritos anteriormente. Quizás aún no creas que necesitas un sistema de compilación y puedes automatizar las partes tediosas con algunas secuencias de comandos de shell simples que se encargan de compilar las cosas en el orden correcto. Esto te ayudará por un tiempo, pero próximamente comenzarás a tener aún más problemas:

  • Se vuelve tedioso. A medida que el sistema se vuelva más complejo, comenzarás a pasar casi el mismo tiempo trabajando en las secuencias de comandos de compilación que en el código real. Depurar las secuencias de comandos de shell es complicado, ya que cada vez más trucos se superponen.

  • Es lenta. Para asegurarte de no depender accidentalmente de bibliotecas inactivas, la secuencia de comandos de compilación compilará todas las dependencias en orden cada vez que las ejecutes. Piensas en agregar lógica para detectar qué partes se deben volver a compilar, pero eso suena muy complejo y propenso a errores para una secuencia de comandos. También puedes especificar qué partes se deben volver a compilar cada vez, pero luego vuelves al cuadrado.

  • Buenas noticias: llegó la hora de lanzar la app. Es mejor determinar todos los argumentos que necesitas pasar al comando jar para realizar la compilación final. 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. Mmm, tal vez esto requiera otra secuencia de comandos...

  • Desastre. Tu disco duro falla y, ahora, debes volver a crear todo el sistema. Eres lo suficientemente inteligente para mantener todos tus archivos de origen en control de versión, 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 la instalación de herramientas particulares en lugares particulares. ¿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 funcione correctamente y se olvide de él?

  • A pesar de los problemas, tu proyecto será lo suficientemente exitoso como para que puedas comenzar a contratar a más ingenieros. Ahora te das cuenta de que no se necesita un desastre para que surjan los problemas anteriores, por lo que debes atravesar el mismo proceso problemático cada vez que se une un nuevo desarrollador a tu equipo. A pesar de tus mejores esfuerzos, todavía existen pequeñas diferencias en el sistema de cada persona. Con frecuencia, lo que funciona en la máquina de una persona no funciona en otra, y cada vez se necesita unas horas para depurar las rutas de acceso de las herramientas o las versiones de la biblioteca a fin de determinar 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 cada noche mediante cron. Todavía debes realizar el difícil proceso de configuración, pero ahora no tienes el beneficio de que el cerebro humano pueda detectar y resolver problemas menores. Ahora, todas las mañanas cuando entras, ves que la compilación de anoche 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 a menudo sucede que dedicas mucho tiempo a cada día a descubrir y aplicar estas soluciones simples.

  • Las compilaciones se vuelven más lentas y más lentas a medida que el proyecto crece. Un día, mientras esperas que se complete una compilación, observa con tristeza el escritorio inactivo de tu compañero de trabajo, que está de vacaciones, y desearías poder aprovechar todo ese poder de desperdicio de procesamiento.

Te encontraste con un problema clásico de escala. Para un desarrollador que trabaja, como máximo, un par de centenares de líneas de código durante una semana o dos (que podría haber sido toda la experiencia hasta el momento de un desarrollador júnior que acaba de graduarse de la universidad), solo necesitas un compilador. Quizás las secuencias te lleven un poco más lejos. Sin embargo, apenas necesites coordinar con varios desarrolladores y sus máquinas, incluso una secuencia de comandos de compilación perfecta no es suficiente porque se torna muy difícil explicar las diferencias menores en esas máquinas. En este punto, este enfoque simple se desglosa y es hora de invertir en un sistema de compilación real.