Fichiers d’en-tête dans plusieurs répertoires: meilleures pratiques

Je suis un C Newb

J’écris beaucoup de code dans des langages dynamics (javascript, python, haskell, etc.), mais j’apprends maintenant le C pour mes études supérieures et je n’ai aucune idée de ce que je fais.

Le problème

A l’origine, je construisais tous mes sources dans un répertoire en utilisant un fichier makefile, ce qui a plutôt bien fonctionné. Cependant, mon projet prend de l’ampleur et j’aimerais scinder la source en plusieurs répertoires (tests unitaires, utils, core, etc.). Par exemple, mon arborescence de répertoires pourrait ressembler à ceci:

. |-- src | |-- foo.c | |-- foo.h | `-- main.c `-- test `-- test_foo.c 

test/test_foo.c utilise à la fois src/foo.c et src/foo.h À l’aide de makefiles, quel est le meilleur moyen / standard de construire cela? De préférence, il y aura une règle pour la construction du projet et une autre pour la construction des tests.

Remarque

Je sais qu’il existe d’autres moyens de procéder, notamment autoconf et d’autres solutions automatiques. Cependant, j’aimerais comprendre ce qui se passe et être capable d’écrire les fichiers Makefiles à partir de zéro, malgré son possible impraticabilité.

Toute orientation ou des conseils seraient appréciés. Merci!

[Modifier]

Les trois solutions proposées jusqu’ici sont donc les suivantes:

  • Placez les fichiers d’en-tête utilisés globalement dans un répertoire d’ include parallèle
  • utilisez le chemin dans le satement #include comme dans #include "../src/foo.h"
  • utilisez le commutateur -I pour informer le compilateur des emplacements d’inclusion

Jusqu’ici, j’aime bien la solution -I switch car elle n’implique pas de changement de code source lorsque la structure de répertoires change.

Pour test_foo.c, vous devez simplement indiquer au compilateur où se trouvent les fichiers d’en-tête. Par exemple

 gcc -I../src -c test_foo.c 

Ensuite, le compilateur examinera également dans ce répertoire les fichiers d’en-tête. Dans test_foo.c, vous écrivez alors:

 #include "foo.h" 

EDIT : Pour lier contre foo.c, en fait contre foo.o, vous devez le mentionner dans la liste des fichiers objects. Je suppose que vous avez déjà les fichiers object, puis faites ensuite:

 gcc test_foo.o ../src/foo.o -o test 

J’utilise aussi rarement les outils automatiques GNU. Au lieu de cela, je vais mettre un fichier Make artisanal dans le répertoire racine.

Pour obtenir tous les en-têtes dans le répertoire source, utilisez quelque chose comme ceci:

 get_headers = $(wildcard $(1)/*.h) headers := $(call get_headers,src) 

Ensuite, vous pouvez utiliser ce qui suit pour que les fichiers-objects du répertoire de test dépendent de ces en-têtes:

 test/%.o : test/%.c $(headers) gcc -std=c99 -pedantic -Wall -Wextra -Werror $(flags) -Isrc -g -c -o $@ $< 

Comme vous pouvez le constater, je ne suis pas fan des directives intégrées. Notez également le commutateur -I .

Obtenir une liste de fichiers objects pour un répertoire est un peu plus compliqué:

 get_objects = $(patsubst %.c,%.o,$(wildcard $(1)/*.c)) test_objects = $(call get_objects,test) 

La règle suivante créerait les objects pour vos tests:

 test : $(test_objects) 

La règle de test ne doit pas uniquement créer les fichiers object, mais les exécutables. L'écriture de la règle dépend de la structure de vos tests: vous pouvez par exemple créer un exécutable pour chaque fichier .c ou un seul qui teste tous les fichiers.

Une méthode courante consiste à nommer les fichiers d’en-tête utilisés par un seul fichier C de la même manière que ce fichier C et dans le même répertoire, et à utiliser les fichiers d’en-tête utilisés par de nombreux fichiers C (en particulier ceux utilisés par le projet entier). être dans un répertoire include parallèle au répertoire source C.

Votre fichier de test doit simplement inclure les fichiers d’en-tête directement en utilisant des chemins relatifs, comme ceci:

 #include "../src/foo.h"