[C++ Avancé] Bases du Makefile (1)

Makefile est en fait juste un fichier de commandes qui indique au programme make (ci-après appelé make ou parfois appelé commande make) comment fonctionner pour nous. Lorsque nous disons que Makefile parle en fait de make, nous devons avoir une compréhension claire de cela. Pour notre projet, Makefile fait référence à l'environnement de compilation du projet logiciel. Le contenu de travail le plus courant dans la phase de codage du développement de produits logiciels est à peu près :

  • Code des développeurs basé sur la conception des grandes lignes
  • Le développeur compile le code source conçu pour produire un exécutable
  • Les développeurs testent les produits logiciels pour vérifier l'exactitude de leurs fonctionnalités

Les trois étapes ci-dessus sont un processus itératif. Si l'exactitude de la conception est finalement vérifiée pour répondre aux exigences, alors le développement de la phase de codage est terminé. Sinon, ces trois étapes doivent être répétées jusqu'à ce que les exigences de conception soient satisfaites.

Parmi les étapes ci-dessus, la deuxième étape est la plus liée au Makefile, alors quelle influence la qualité du Makefile a-t-elle sur le développement du projet ? Avec un Makefile bien conçu, lorsque nous recompilons, nous n'avons besoin de compiler que les fichiers qui ont été modifiés depuis la dernière compilation réussie, c'est-à-dire qu'un delta est compilé au lieu du projet entier. A l'inverse, s'il y a un mauvais environnement Makefile, il peut être nécessaire de nettoyer à chaque compilation, puis de recompiler tout le projet. La différence entre les deux cas est évidente, ce dernier consommera beaucoup de temps pour les développeurs à compiler, ce qui signifie une faible efficacité. Pour les petits projets, l'inefficacité n'est peut-être pas évidente, mais pour les projets relativement importants, elle est très évidente. Les développeurs peuvent faire dix compilations par jour (ou même moins) et n'ont pas le temps de coder et de tester (débogage). C'est pourquoi, généralement, un grand projet aura une petite équipe dédiée à la maintenance du Makefile
pour soutenir le développement du produit.

Le plus important est de maîtriser deux concepts, l'un est la cible et l'autre la dépendance. L'objectif fait référence à ce qu'il faut faire, ou à ce qui est généré après l'exécution de make, et la dépendance indique à make comment le faire pour atteindre l'objectif. Dans un Makefile, les cibles et les dépendances sont exprimées par des règles. Ce que nous connaissons le mieux est d'utiliser make pour compiler le code des produits logiciels, mais il peut être utilisé pour faire beaucoup de choses, et nous donnerons quelques exemples de non-utilisation de make pour compiler le code plus tard. Pour contrôler Makefile, le plus important est d'apprendre à utiliser les objectifs et les dépendances pour réfléchir aux problèmes à résoudre.


insérez la description de l'image ici

  • Cibles : les cibles du Makefile font référence aux fichiers qui doivent être générés ou aux opérations qui doivent être effectuées. Une cible peut être un fichier, une commande ou une séquence d'opérations.
  • Dépendances : les dépendances dans le Makefile font référence aux fichiers ou aux commandes dont dépend la cible. Si des fichiers dépendants sont modifiés, la cible doit également être régénérée.
  • Commandes : les commandes du Makefile font référence à la séquence d'opérations qui doivent être effectuées pour générer la cible. Ces opérations peuvent être la compilation, la liaison, la copie, le conditionnement, etc. Les commandes doivent commencer par une tabulation ou plusieurs espaces, sinon elles seront traitées comme des commentaires.

insérez la description de l'image ici
Makefile est un fichier texte qui contient des règles et des instructions décrivant comment compiler et lier un ou plusieurs fichiers de code source pour générer des programmes exécutables ou des fichiers de bibliothèque.

Le Makefile fonctionne comme suit :

  1. Les makefiles définissent les fichiers cibles, les dépendances et les commandes. Les fichiers objets sont généralement des programmes exécutables ou des fichiers de bibliothèque, les fichiers dépendants sont des fichiers de code source, des fichiers d'en-tête ou d'autres dépendances, et les commandes sont les opérations de compilation, de liaison et de génération de fichiers objets.

  2. Lorsque la commande make est exécutée, les règles du Makefile seront analysées et un graphique de dépendance sera généré en fonction des dépendances pour déterminer quels fichiers doivent être recompilés.

  3. Le programme Make effectue de manière récursive les opérations de compilation, de liaison et de génération de fichiers objets conformément au graphique et aux règles de dépendance, garantissant que toutes les dépendances sont compilées et liées pour générer le fichier objet final.

  4. Si certaines dépendances n'ont pas changé, il n'est pas nécessaire de recompiler et de lier, ce qui améliore l'efficacité de la compilation.

  5. Makefile prend également en charge des fonctionnalités avancées telles que les variables, les instructions conditionnelles et les instructions de boucle, et peut être compilé et lié selon différentes conditions pour générer différents fichiers cibles.

1. Environnement

Les exigences d'environnement pour l'utilisation du makefile sont les suivantes :

  1. Système d'exploitation : les makefiles peuvent être utilisés sur la plupart des systèmes d'exploitation, y compris Linux, Unix, Mac OS X, Windows, etc.
  2. Compilateur : makefile nécessite un compilateur prenant en charge la syntaxe GNU make, telle que GNU make, BSD make, etc.
  3. Fichiers objet : les makefiles nécessitent des fichiers de code source compilables, tels que C, C++, Java, etc.
  4. Variables d'environnement : Makefile a besoin de certaines variables d'environnement pour spécifier le compilateur, les options de compilation, etc., telles que CC, CFLAGS, LDFLAGS, etc.
  5. Éditeur : Makefile a besoin d'un éditeur pour écrire et éditer des makefiles, tels que Vim, Emacs, etc.
  6. outil make : makefile nécessite un outil make pour exécuter le makefile, comme GNU make.

Étapes d'utilisation :

  1. Installez l'outil GNU Make : Make est un outil en ligne de commande utilisé pour automatiser le processus de création de logiciels. Vous pouvez télécharger et installer l'outil Make depuis le site officiel de GNU.
  2. Créer un Makefile : Un Makefile est un fichier texte qui contient une série de règles et d'instructions décrivant comment créer un logiciel. Un fichier nommé Makefile peut être créé dans le répertoire racine du projet.
  3. Écrire des règles Makefile : les règles Makefile se composent de cibles, de dépendances et de commandes. La cible fait référence au fichier à générer ou à l'opération à effectuer ; la dépendance fait référence au prérequis pour générer la cible ; la commande fait référence à l'opération spécifique de génération de la cible.
  4. Exécutez la commande Make : entrez le répertoire racine du projet sur la ligne de commande, entrez la commande make pour exécuter les règles définies dans le Makefile, générer des fichiers cibles ou effectuer des opérations.

Entrez la ligne de commande make -v, si les informations de version similaires à la figure ci-dessous apparaissent, cela signifie que make est déjà disponible dans votre environnement :
insérez la description de l'image ici

Précautions:

  1. Lors de l'écriture de Makefile, les variables et les fonctions doivent être utilisées autant que possible pour améliorer la lisibilité et la maintenabilité du code.
  2. Les commandes d'un Makefile doivent commencer par la touche Tab, pas par une barre d'espace.
  3. Les dépendances dans un Makefile doivent être aussi explicites que possible afin de déterminer correctement quelles règles doivent être exécutées.
  4. Avant d'exécuter la commande Make, vous devez vous assurer que tous les fichiers dépendants existent déjà, sinon la construction échouera.

2. Règles

Nous utilisons Hello World pour commencer à apprendre les règles du Makefile, écrivons un Makefile comme suit, et le répertoire de stockage du fichier peut être arbitraire :

all:
	echo "Hello World"

insérez la description de l'image ici

Il convient de noter qu'il ne doit y avoir qu'une TAB devant echo , et qu'il doit y avoir au moins une TAB, et que les espaces ne peuvent pas être utilisés à la place.

Le premier concept très important dans Makefile est la cible. Tout dans le code ci-dessus est notre cible. La cible est placée devant : et son nom peut être composé de lettres et de traits de soulignement. echo “Hello World”C'est la commande pour générer la cible. Ces commandes peuvent être n'importe quelles commandes exécutées dans votre environnement et les fonctions définies par make, etc. Ici, echo est une commande dans BASH Shell, et sa fonction est d'imprimer des chaînes sur le terminal. Le but de tous ici est d'imprimer "Hello World" sur le terminal, et parfois le but sera un concept plus abstrait. La définition de la cible all définit en fait comment générer la cible all, qui est appelée une règle, c'est-à-dire que le Makefile ci-dessus définit une règle pour générer la cible all.

L'exemple suivant montre trois manières différentes d'exécuter et les résultats de chaque manière :

  • La première façon : tant que vous exécutez makela commande dans le répertoire où se trouve le Makefile, deux lignes seront affichées sur le terminal, la première ligne est en fait la commande que nous avons écrite dans le Makefile, et la deuxième ligne est le résultat de exécuter la commande
  • La deuxième façon: exécutez make allla commande, qui indique à l'outil make que je veux générer la cible tout, et le résultat est le même que la première façon
  • La troisième façon : run make test, demande à make de générer la cible de test pour nous. Comme nous n'avons pas du tout défini la cible de test, le résultat de l'exécution est prévisible, make signale que la cible de test est introuvable

insérez la description de l'image ici
Apportez maintenant une petite modification au Makefile ci-dessus, comme indiqué ci-dessous, en ajoutant la règle de test pour construire la cible de test, de manière à afficher "Just for test!" sur le terminal :

all:
	echo "Hello World"
test:
	echo "Just for test!"

insérez la description de l'image ici
À partir de la sortie ci-dessus, nous pouvons trouver :

  • Plusieurs cibles peuvent être définies dans un Makefile
  • Lors de l'appel maked'une commande, nous devons lui dire quelle est notre cible, c'est-à-dire ce que nous voulons qu'elle fasse. Lorsqu'aucune cible spécifique n'est spécifiée, make utilise la première cible définie dans le Makefile comme cible pour cette exécution. Ce premier objectif est également appelé objectif par défaut (il n'a rien à voir avec le fait que ce soit tout)
  • Lorsque make obtient la cible, il trouve d'abord les règles qui définissent la cible, puis exécute les commandes dans les règles pour atteindre l'objectif de construction de la cible. Dans le Makefile affiché maintenant, il n'y a qu'une seule commande dans chaque règle, mais dans le Makefile réel, chaque règle peut contenir plusieurs commandes

Comme dans l'exemple précédent, lors de l'exécution make, les commandes du Makefile sont également imprimées sur le terminal. Parfois, ce n'est pas souhaitable, car cela peut rendre la sortie un peu confuse. Pour que makela commande ne soit pas imprimée, faites juste une petite modification, le Makefile modifié est le suivant, c'est-à-dire ajoutez un avant la commande @. Ce symbole indique make, ne pas afficher cette ligne de commande à l'exécution :

all:
	@echo "Hello World"
test:
	@echo "Just for test!

insérez la description de l'image ici
Apportez une petite modification au code ci-dessus, :ajoutez la cible de test après la cible tout, comme indiqué ci-dessous

all: test
        @echo "Hello World"
test:
        @echo "Just for test!"

insérez la description de l'image ici


Expliquons les dépendances dans le Makefile

Dans le code ci-dessus, le test après la cible all indique à make que la cible all dépend de la cible de test, et cette cible dépendante est également appelée prérequis dans le Makefile. Lorsqu'une telle dépendance de cible se produit, l'outil make construit d'abord chaque cible dont dépend la règle dans l'ordre de gauche à droite. Si vous voulez construire la cible all, alors make devra construire la cible de test avant de la construire, c'est pourquoi on l'appelle un prérequis. Le diagramme de classes suivant exprime les dépendances de toutes les cibles :

insérez la description de l'image ici

Jusqu'à présent, nous comprenons les règles dans le Makefile, ce qui suit est le texte et l'UML des règles. Une règle est composée de cibles, de prérequis et de commandes. Il convient de préciser que l'expression entre la cible et le prérequis est la dépendance (dépendance), qui indique que le prérequis doit être satisfait (ou construit) avant que la cible ne soit construite ; le prérequis peut être d'autres cibles, lorsqu'un prérequis est une cible, elle doit d'abord être construite.

targets : prerequisites
	command

insérez la description de l'image ici
Il peut y avoir plusieurs cibles dans une règle. Lorsqu'il y a plusieurs cibles et que cette règle est la première règle du Makefile, si nous exécutons la commande make sans aucune cible, la première cible de la règle sera considérée comme étant la cible par défaut, comme suit:

all test:
	@echo "Hello World"

insérez la description de l'image ici
Le diagramme d'activité de faire le traitement d'une règle est illustré dans la figure ci-dessous. L'activité de création de cible(s) dépendante(s) (notez qu'il s'agit d'une activité, pas d'une action) consiste à répéter la même activité que celle illustrée dans la figure ci-dessous. Vous Peut être vu comme un appel récursif au diagramme d'activité ci-dessous. La commande run to build target (exécuter la commande pour construire la cible) est une action, qui est une action composée de commandes. La différence entre une activité et une action est qu'une action ne fait qu'une seule chose (mais peut avoir plusieurs commandes), tandis qu'une activité peut inclure plusieurs actions.
insérez la description de l'image ici

3. Principe

Ensuite, nous essayons d'appliquer les règles à la compilation du programme. Ci-dessous, nous supposons qu'il existe deux fichiers de programme source utilisés pour créer un fichier exécutable simple. Nous devons écrire un Makefile pour créer un programme exécutable simple. Comment ce Makefile doit-il être compilé ? Écrire?
foo.c

#include <stdio.h>
void foo ()
{
    
    
	printf ("This is foo()\n");
}

principal c

extern void foo();
int main ()
{
    
    
	foo();
	
	return 0;
}

La première étape de l'écriture d'un Makefile n'est pas de se lancer et d'essayer d'écrire une règle, mais d'utiliser une méthode orientée dépendances pour déterminer le type de dépendances que le Makefile à écrire doit exprimer, ce qui est très important. Grâce à une pratique continue, nous pouvons enfin parvenir à une utilisation naturelle des dépendances pour réfléchir aux problèmes. À ce moment-là, lorsque vous écrivez à nouveau Makefile, votre esprit sera très clair sur ce que vous écrivez et sur ce que vous écrivez plus tard. Maintenant, mettez de côté le Makefile, regardons quelles sont les dépendances du programme simple.

Le premier graphe de dépendance qui vient à l'esprit, où l'exécutable simple est évidemment produit en compilant et en liant main.c et foo.c à la fin. Grâce à ce graphe de dépendances, un Makefile peut réellement être écrit. Le Makefile écrit par de telles dépendances n'est pas très réalisable en réalité, c'est-à-dire qu'il faut mettre tous les programmes sources sur une seule ligne et laisser GCC les compiler pour nous : la figure suivante est une expression plus précise des dépendances du programme simple
insérez la description de l'image ici
Qui a ajouté le fichier objet. Pour un programme exécutable simple, la figure ci-dessous montre son "arbre de dépendances". La prochaine chose à faire est d'exprimer chacune des dépendances, c'est-à-dire chacune des lignes pointillées avec des flèches, avec les règles du Makefile :
insérez la description de l'image ici

all: main.o foo.o
        gcc main.o foo.o -o simple
main.o: main.c
        gcc main.c -c
foo.o: foo.c
        gcc foo.c -c

.PHONY:clean
clean:
        rm -f main.o foo.o simple

Dans ce Makefile, j'ai également ajouté une pseudo-cible pour supprimer les fichiers générés, y compris les fichiers objets et les programmes exécutables simples, qui sont très courants dans les projets réels.
insérez la description de l'image ici
Que se passe-t-il si on recompile sans changer le code ? La figure ci-dessous montre les résultats. Notez que la deuxième compilation n'a pas l'action de construire le fichier cible, mais pourquoi y a-t-il une action de construire le programme exécutable simple ?
insérez la description de l'image ici
Makefile jugera si le fichier doit être reconstruit en fonction de l'horodatage du fichier (c'est-à-dire l'heure de la dernière modification). Si un fichier a un horodatage plus ancien que les fichiers qui en dépendent, le fichier doit être reconstruit. Par conséquent, si vous exécutez la commande make plusieurs fois, même si les fichiers source et d'en-tête n'ont pas changé, l'horodatage de l'exécutable sera mis à jour, provoquant une reconstruction. Si vous voulez éviter cela, vous pouvez utiliser la fonctionnalité de construction incrémentielle de make, qui ne reconstruit que les fichiers nécessaires.

Vérifions que si nous modifions foo.c, il se reconstruira. Pour l'outil make, la modification d'un fichier n'est pas basée sur la taille du fichier, mais sur son horodatage. Sous Linux, il vous suffit d'utiliser la commande touch pour modifier l'horodatage du fichier, ce qui équivaut à simuler une modification du fichier sans réellement le modifier. Comme le montre la figure, make trouve le changement de foo.c, et recompilé :
insérez la description de l'image ici

4. Faux objectifs

Dans Makefile, une pseudo-cible est une cible spéciale qui ne représente pas un fichier réel, mais est utilisée pour effectuer une tâche spécifique ou organiser la séquence d'exécution d'autres cibles.

Supposons que nous ayons un projet en langage C, incluant les fichiers suivants : main.c, foo.c, bar.c, foo.h, bar.h. Nous devons compiler ce projet pour produire un exécutable my_program. Un simple Makefile pourrait ressembler à ceci :

my_program: main.o foo.o bar.o
	gcc -Wall -g -o my_program main.o foo.o bar.o

main.o: main.c foo.h bar.h
	gcc -Wall -g -c main.c

foo.o: foo.c foo.h
	gcc -Wall -g -c foo.c

bar.o: bar.c bar.h
	gcc -Wall -g -c bar.c

clean:
	rm -f *.o my_program

Dans ce Makefile, nous avons une cleancible nommée . Il ne dépend pas d'autres cibles et ne représente pas non plus un fichier réel. Son rôle est de supprimer tous les fichiers intermédiaires ( .ofiles ) et les fichiers exécutables générés ( my_program), ce qui est une pseudo-cible typique.

Principales caractéristiques et utilisations des fausses cibles :

  • Ne représente pas un fichier réel : La pseudo-cible ne correspond à aucun fichier réel, elle n'existe que pour effectuer une tâche précise
  • Évitez les collisions de noms : étant donné que les pseudo-cibles ne représentent pas les fichiers réels, nous pouvons éviter les erreurs causées par le fait que les noms de fichier et de cible sont identiques
  • Mieux organiser les Makefiles : avec les pseudo-cibles, nous pouvons séparer différentes tâches et opérations, ce qui rend les Makefiles plus clairs et plus faciles à lire
  • Application : à l'aide de pseudo-cibles, nous pouvons appliquer une certaine tâche, que le fichier existe ou ait été mis à jour

Dans le Makefile, nous pouvons utiliser `.PHONY`` pour déclarer une pseudo-cible pour indiquer explicitement à make que la cible n'est pas un fichier réel. Par exemple, nous pourrions ajouter la déclaration suivante à l'exemple ci-dessus :

.PHONY: clean

L'avantage est que même s'il existe un fichier nommé make dans le répertoire courant clean, make saura qu'il cleans'agit d'une pseudo-cible et non d'un fichier réel.

Bien sûr, en plus des pseudo-cibles propres mentionnées ci-dessus, il existe d'autres pseudo-cibles courantes. Voici quelques pseudo-cibles souvent utilisées dans les Makefiles :

  1. all: Cette pseudo-cible est généralement utilisée pour compiler l'ensemble du projet. Lorsque l'utilisateur exécute make ou make all, il compilera et générera automatiquement toutes les cibles requises.
    .PHONY: all
    all: my_program
    
  2. install: Cette pseudo cible est utilisée pour installer le programme compilé dans le répertoire spécifié par le système. En règle générale, cela nécessite des privilèges d'administrateur, car cela implique la création ou la modification de fichiers dans les répertoires système.
    .PHONY: install
    install: my_program
    	cp my_program /usr/local/bin
    
  3. uninstall: Cette pseudo cible est utilisée pour supprimer les programmes installés du système. Comme l'installation, il nécessite généralement des privilèges d'administrateur.
    .PHONY: uninstall
    uninstall:
    	rm -f /usr/local/bin/my_program
    
  4. test: Cette pseudo-cible est utilisée pour exécuter les cas de test du projet, en s'assurant que les différentes parties du projet fonctionnent correctement.
    .PHONY: test
    test: my_program
    	./test_script.sh
    
  5. help: Cette pseudo-cible est utilisée pour afficher les instructions du Makefile afin d'aider les utilisateurs à comprendre comment utiliser le Makefile.
    .PHONY: help
    help:
    	@echo "Usage:"
    	@echo "  make all      - Compile the project"
    	@echo "  make clean    - Remove compiled files and binaries"
    	@echo "  make install  - Install the program"
    	@echo "  make test     - Run tests"
    

Je suppose que tu aimes

Origine blog.csdn.net/weixin_52665939/article/details/130131272
conseillé
Classement