Hola, en este vídeo vamos a ver cómo compilar y linkear una aplicación usando Make. Para poder hacerlo vamos a necesitar crear un archivo llamado Make, donde le expliquemos a Make cómo se relacionan nuestros archivos, cuáles son los comandos que tiene que ejecutar para compilar y linkear y generar el binario. Antes de ver eso, vamos a ver un poco cómo es nuestra aplicación.
Bueno, esta aplicación básicamente tiene tres archivos.c, main.c, juego.c y personaje.c. Y dos headers asociados a juego.c, o sea, juego.h y a personaje.c, personaje.h. Bien, no es importante saber los detalles de la aplicación. Básicamente lo que nos interesa saber son las dependencias entre estos archivos.
Personaje.h define un tipo de personaje y declara dos métodos, atacar y level up, que van a estar definidos en personaje.c. Acá los tenemos. Y en juego.h vamos a tener, o sea, juego.h va a depender.
Va a usar el tipo personaje, o sea que vamos a necesitar incluir personaje.h, va a usar el personaje y los métodos van a estar definidos en juego.c, donde básicamente se crean dos personajes. Se crea un guerrero al cual le podemos pasar un nombre y se crea una hechicera a la cual también le podemos pasar un nombre. Y en main.c simplemente se... Se llaman estos métodos, o sea, se crean dos personajes, en particular se crea un guerrero y una hechicera con dos nombres, y después se llaman algunos métodos que están definidos en personaje.h. Bien, teniendo una idea de cómo son las dependencias entre nuestros archivos, vamos a tener que plasmar eso en nuestro archivo makefile.
¿Qué es el Make? Bueno, el Make es un archivo, que lo voy a crear ahora, que nos va a permitir definir cómo son las relaciones para poder compilar estos archivos a través de Make. Bueno, empecemos con la descripción de cómo funciona Make.
Bueno, supongamos que queremos compilar, por ejemplo, personaje.c, ¿sí? O sea, que queremos generar... a partir de personaje.c su correspondiente archivo objeto personaje.o.
Bueno, ¿cómo le explicamos a Make la manera de hacer eso? Bueno, a través del archivo Make. Entonces, ¿cómo hacemos?
Bueno, tenemos que empezar escribiendo lo que queremos hacer. En este caso queremos crear personaje.o. Ponemos dos puntos.
Y esto en la terminología de make se llama target. Nosotros queremos crear este target, o sea, a personaje.hoy le tenemos que decir a partir de qué prerequisitos crea ese target. En particular, personaje.hoy va a tener como prerequisitos a personaje.c y a personaje.h. Es decir, que para crear a personaje.hoy yo necesito... tener esos dos archivos fuentes, el.c y el.h.
Lo que no le dije todavía es cómo crearlo. O sea, le dije qué necesita, pero no le dije cómo crear el archivo target, en este caso personaje.o. Bueno, en la siguiente línea se explica cómo crearlo, o sea, se le da lo que se llama la regla. La regla empieza en contado, ¿sí?
Recuerden esto es muy importante. El primer comando siempre es un tab. Y bueno, acá le explicamos cómo crearlo. Esto ya lo sabemos hacer. Simplemente le decimos que compile personaje.c y que genere el archivo personaje.o.
Algo importante es que yo, bueno, personaje.h no interviene en la regla, pero es importante que esté acá porque... Lo que va a pasar es que cada vez los prerequisitos sirven para que cada vez que cambie alguno de los dos, tanto el personaje.c o el personaje.h, se dispare nuevamente la ejecución de la regla. Para generar nuevamente el target.
O sea que si el personaje.h cambia, bueno, entonces yo voy a ejecutar esto nuevamente. Bien, ahora. ¿Cómo generamos este target? Bueno, lo que tenemos que hacer es invocar a Make a través de la consola, ponemos Make y este archivo se llama de esta manera en particular, entonces Make lo va a tomar y lo va a ir a buscar directamente. Este nombre no es cualquier nombre sino que tiene que tener exactamente, se tiene que escribir exactamente de esta manera para que make lo vaya a buscar y no tengamos que especificárselo.
Entonces si ponemos make vemos que se genera el target que nosotros queremos. En este caso como hay un solo target, make toma ese y dice ok te voy a hacer ese target porque es el único que tenés especificado y lo genera. Si yo ejecuto make de vuelta, no realiza de vuelta la ejecución de la regla, sino que nos dice, no, mirá, personaje.o ya lo creé y es actual. O sea que no hace falta que lo vuelva a hacer. Bueno, ¿cómo se da cuenta de esto?
Bueno, se da cuenta comparando fechas de modificación. Es decir, compara la última fecha de modificación de personaje.o, que en este caso es el target, con la fecha de modificación de todos los prerequisitos. Si hay algún prerequisito que tiene una fecha de modificación que es más actual que la que tiene mi target, entonces se desencadena o se dispara de nuevo todo el proceso o toda la regla.
En este caso, como yo no modifiqué personaje.c o personaje.h, no era necesario de vuelta ejecutar la regla, o sea, generar nuevamente personaje.o. Si yo modifico, por ejemplo, este archivo, supongamos que le pongo un enter nada más, y ejecuto make, vemos que se vuelve a ejecutar la regla. Es decir, como que make detecta que hubo un cambio en la fecha de modificación y dice, ok, el archivo personaje.c es más actual que personaje.o.
Por lo tanto, tengo que volver a ejecutar la regla porque el personaje.o está desactualizado. Se ejecuta toda la regla y se genera nuevamente. Bueno, y así podríamos escribir todas las reglas, tanto de juego.c, main.c y la regla de linkeo para generar el ejecutable. Pero antes de hacer eso, vamos a escribir algunas convenciones que se suelen tomar en los archivos.
Make. Por ejemplo, en vez de poner el compilador directamente, se suelen definir variables. Las variables se definen simplemente, se pueden definir con dos puntos igual o con igual. Se define una variable, por ejemplo, que se llama cc, convencionalmente ccompiler, que va a ser el compilador que voy a usar, en este caso es gcc. También puedo definir los flags, que lo voy a pasar.
En este caso voy a decidir pasarle algunos flags de warnings. Estos flags de warnings es muy recomendado que se los pasen para que pueda tener mayor detección de errores comunes y que el compilador nos avise de eso. Incluso también le podemos pasar el...
el nivel de detección de warning más agresivo que es pedantic. Entonces, ¿cómo reemplazamos esto con variables acá abajo? Bueno, simplemente para escribir una variable, o sea, para tomar el valor de la variable, en make se pone el símbolo pesos y el nombre de la variable entre paréntesis.
Y los flags, en este caso, no se los estamos pasando, entonces se los tenemos que pasar. Vamos acá. Bien, entonces, una otra cosa que se puede hacer es que...
Personaje.c es un prerequisito y acá está repetido este nombre. Es decir que si nosotros cambiamos el prerequisito, cambiamos el nombre del pre-script, el prerequisito tendremos que cambiarlo en la regla. Bueno, para no tener que hacer eso, Make tiene algunas cosas que se llaman variables automáticas. Las variables automáticas son... variables que toman el nombre, por ejemplo, de los prerequisitos.
En particular, que queremos poner ahora es esta variable automática. Esta variable automática, que es pesos y el símbolo menor, está haciendo referencia solamente al primer prerequisito. Es decir, esto se va a reemplazar en forma automática, si la regla se ejecuta, por el primer prerequisito que tenemos listado en esta lista. Y en particular, el nombre personaje.o también está repetido.
y también tiene su variable automática, es decir, podemos poner pesos arroba. De esta manera queda un poco más críptico, pero cuando nos acostumbremos a usarlo, vamos a ver que es más fácil y es mejor. ¿Por qué? Porque de esta manera nosotros podemos después cambiar los nombres de los archivos. y no tener que estar editando todo el tiempo las reglas.
Además las reglas se terminan escribiendo siempre de la misma manera y lo único que tenemos que hacer es escribir la relación de dependencias. Bueno, nos queda escribir. Bueno, podemos ver cómo se ejecuta de vuelta esto nuevamente. O sea, así con estos cambios que hicimos. Si yo toco Make, Make dice que personaje.o está actual, así que lo que voy a hacer es...
Bueno, lo que puedo hacer es, una es hacer un comando touch. Touch lo que hace es cambiar la fecha de modificación de un archivo, por ejemplo, de personaje.c. Podría haberlo hecho con personaje.h también.
Entonces, si hago tocomake, se ejecuta toda la regla. Fíjense cómo se reemplazan. todos los flags que puse y se reemplaza también las variables automáticas personaje.c y personaje.o con esto.
Bien, entonces ya tenemos compilado, o sea, sabemos cómo indicarle cómo compilar personaje.o, nos falta juego.c y main.c. Bueno, van a ser bastante similares. De hecho esto lo voy a copiar. Por eso es útil tener también estas variables automáticas. Pero tenemos una particularidad que juego.c también depende de personaje.h.
Es decir que si personaje.h cambia, yo tendría que recompilar juego.c. Entonces eso se lo puedo indicar. acá, bien, y lo mismo con main, main depende de los dos, depende de personaje.h y de juego.h también lo puedo Bien, entonces ahí tenemos tres targets, nuestros tres archivos objeto nos faltaría generar el archivo, o sea, lo que sería el archivo ejecutable a partir de los tres puntos.
Eso lo podemos poner más arriba, lo voy a llamar ff100, a mi archivo ejecutable. Entonces, mi archivo ejecutable, ¿cuáles son los prerequisitos? Bueno, para generar mi ejecutable yo necesito tener mis.o generados.
Entonces lo que le voy a decir es, bueno, yo voy a necesitar que tenga mi main.o, mi juego.o y mi personaje. punto A. O sea que, para generar mi archivo ejecutable necesito tener cualquiera de estos tres, perdón, necesito tener estos tres y cualquiera de estos tres que cambie, yo tengo que regenerar nuevamente mi ejecutable. Bueno, ¿cómo es la regla para generar el ejecutable? Bueno, también ya lo habíamos visto.
le podemos pasar también los flags acá Bueno, y la diferencia es que ahora, como voy a hacer el proceso de linkeo, ahora tengo que pasarle todos los.o. Entonces tengo que usar otra variable automática, que en este caso es pesos sombrerito. Esto quiere decir todos, todos los prerequisitos van acá.
No el primero, como era el menor, sino que voy a tomar a todos. Y bueno, y el nombre del archivo que voy a generar. va a ser, bueno, en este caso, FF100. Entonces, si yo hago ahora, puedo hacer Make o Make FF100. Si hago Make solo, como el primer target es este, va a ser solamente este target.
También puedo hacer Make FF100. O Make, puedo tocar Tab también y puedo completar con lo que necesite. En este caso voy a tocar Make solamente. Bueno, fíjense cómo se generó el ejecutable. Make ejecutó todas las reglas.
¿Por qué? Porque el proceso se dispara. Primero, o sea, Make qué hace? Viene y se fija, bueno, dice, bueno, necesito crear FF100.
Ok, para crear FF100 necesito Main O. Bueno, y Main O cómo lo hago? y viene acá y dice ok, mainO necesito mainC, mainC lo tengo, sí, juego.h lo tengo, sí, personaje.h lo tengo, sí, bueno entonces ok, si los tengo a todos los prerequisitos entonces ejecuto la regla. Cuando termino de ejecutar la regla vuelve FFCen y dice ok, ahora necesito juego.o y va a la línea juego.o y se fija si lo puede generar a partir de los prerequisitos. Y lo mismo con personaje.o.
Una vez que tiene todos los prerequisitos, de este target, dice ok, ya tengo todos los requisitos, entonces ahora lo puedo ejecutar. Ejecuta toda esta línea. Entonces esto plantea una gran ventaja.
La ventaja es que cuando yo modifique, o sea, si yo ejecuto make de vuelta, me dice que FF100 está actualizado, que no tengo que hacer nada. Pero, por ejemplo, si yo modifico algún archivo, por ejemplo, vamos a hacer un touch de juego.c, y hacemos make, entonces solamente las reglas que se ejecutan van a ser en las cuales juego.c tiene algún papel. En particular, la regla que genera el juego.o, que es esta, Y como java.org se actualiza, también tengo que actualizar el ejecutable, el FF100.
Entonces vuelvo a ejecutar toda esta regla. Pero, fíjense como main.org y personaje.org no fueron regenerados. ¿Por qué? Porque sus prerequisitos no cambiaron.
Esa es la gran ventaja de Make. Que me permite hacer este tipo de compilaciones automáticas. Y solamente hace los pasos necesarios y no hace pasos de más. en la hora de compilar. Bueno, para terminar, algo útil es crear lo que se llaman algunos targets que se suelen, que se llaman phony.
Estos targets phony tienen unos nombres que suelen ser estándar, por ejemplo, clean. ¿Qué quiere decir un target phony? Bueno, que en realidad...
Este target no es un archivo. Es decir, cuando le estamos diciendo que es phony, le estamos diciendo, ok, mira, clean, hay un target llamado clean, pero no es un archivo. O sea, no lo trastes como un archivo, no intentes crear este target.
Cuando yo te diga que hagas este target, vos simplemente ejecutá las reglas. No crees el archivo, no te voy a pasar reglas acá para crear un archivo llamado clean. Entonces yo para decirle eso le digo, ok, mira que clean es un target phony.
Clean es útil porque me permite... puedo borrar por ejemplo todos los.o eso es muy útil y también puedo borrar el ejecutable bien entonces yo si ejecuto por ejemplo make clean Bueno, me borra, me ejecuta todas las líneas que yo le puse acá. Esta es una buena manera también de generar comandos en un makefile. O sea, yo puedo poner una serie de comandos asociados a un nombre. Entonces, cuando yo le hago make ese nombre, se ejecutan todos esos comandos.
Entonces si yo hago make de vuelta, bueno, se genera todo el proceso de compilación de vuelta. Entonces es muy útil hacer un make clean a veces para empezar de vuelta y ver si nuestro proceso de compilación está andando bien o tenemos algún problema o queremos regenerar todo de vuelta por alguna razón. Entonces es muy útil siempre tener un make clean acá abajo.
Y algo también bastante estándar es tener otro phony. llamado all y en general ese all va a contener el nombre del ejecutable que queremos generar o de todos los ejecutables que estemos generando porque bueno son nombres estándar sí es decir que alguien que agarra un make que no fue que no somos nosotros digamos que lo creó otra persona cuando ve un target all puede decir bueno que voy a hacer make all para generar todo lo que este makefile tiene por generar. Y además como se lista como primer target, ni siquiera hace falta poner makeall. Con que nosotros pongamos make, bueno, va a intentar hacer el target all.
Bueno, espero que con este repaso de compilación y también con la introducción de lo que es un makefile. puedan entender cómo funciona y quizás incluso codear sus propios makefiles en los procesos de compilación de la materia. Como comentario final les quiero decir que hay muchas maneras de hacer makefiles, o sea, hay muchas formas de listar estos archivos a través de variables y no repetir tanto los nombres, pero bueno, esta es la...
Esta sería la primera versión de un Makefile bastante entendible y con esto nos alcanza para los fines en los que vamos a trabajar en la materia.