3
votes

Évolution d'un Makefile d'une structure de répertoire plat à une structure de sous-répertoire

VOIR LES MISES À JOUR CI-DESSOUS


Recherche terminée: Je trouve difficile d'apprendre à faire évoluer les Makefiles d'une situation à une autre. Il existe une tonne de questions et réponses, mais peu d'entre elles montrent réellement comment un Makefile peut évoluer à mesure que votre projet change. Ils semblent également tous utiliser différentes techniques et expressions idiomatiques de Makefiles, donc traduire entre une question et une autre peut être difficile lorsque vous apprenez Makefiles pour la première fois, comme je le suis.

Problème: Mon problème est que j'ai un projet qui a commencé comme une structure de répertoire plat mais qui migre ensuite vers une structure avec des sous-répertoires. Ce que je ne peux pas faire, c'est emmener mon Makefile le long du trajet.

Je vais d'abord montrer ce que j'ai créé qui fonctionne, puis je montrerai comment je veux qu'il évolue et comment cela ne fonctionne pas .

Structure de répertoire plat, Makefile fonctionnel

J'ai un répertoire de projet qui contient tous mes fichiers C et un fichier d'en-tête plus mon Makefile:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin

$(shell mkdir -p $(BIN_DIR))
$(shell mkdir -p $(OBJ_DIR))

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

EXECUTABLES := c8_dasm c8_asm c8_terp

all: $(EXECUTABLES)

c8_dasm: $(SRC_DIR)/c8_dasm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Disassembler Built"

c8_asm: $(SRC_DIR)/c8_asm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Assembler Built"

c8_terp: $(SRC_DIR)/c8_terp.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Interpreter Built"

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $(BIN_DIR)/$@

.PHONY: clean
clean:
    rm -rf $(BIN_DIR)
    rm -f $(OBJECTS)

Voici mon Makefile (qui fonctionne très bien):

make c8_dasm
make clean

Je reçois tous mes fichiers .o et mes exécutables sont créés dans le répertoire du projet.

Faire évoluer le projet

Mais ce que je voulais faire, c'est avoir mes fichiers sources (tous .c et .h) dans un src annuaire. Je voulais créer un répertoire obj et placer les exécutables dans un répertoire bin. Donc mon projet ressemblerait à ceci:

make
make clean

Structure du sous-répertoire, Makefile NE FONCTIONNE PAS

Pour tenir compte de ce qui précède, J'ai changé mon Makefile en conséquence:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

# Directories.

SRC_DIR = src
BIN_DIR = bin

$(shell mkdir -p $(BIN_DIR))

# Patterns for files.

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(SRC_DIR)/%.o)

EXECUTABLES := c8_dasm c8_asm c8_terp

# Targets

all: $(EXECUTABLES)

c8_dasm: $(SRC_DIR)/c8_dasm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Disassembler Built"

c8_asm: $(SRC_DIR)/c8_asm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Assembler Built"

c8_terp: $(SRC_DIR)/c8_terp.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Interpreter Built"

# Using implicit rules for updating an '.o' file from a correspondingly
# named '.c' file.

c8_dasm.o: $(SRC_DIR)/chip8.h
c8_asm.o: $(SRC_DIR)/chip8.h
c8_terp.o: $(SRC_DIR)/chip8.h

.PHONY: clean
clean:
    rm $(OBJECTS)
    rm -r $(BIN_DIR)

En exécutant ceci, j'obtiens ce qui suit:

mkdir -p obj/obj/
gcc src/c8_dasm.c -o obj/c8_dasm.o
gcc -lm obj/c8_dasm.o -o bin/c8_dasm
ld: can't link with a main executable file 'obj/c8_dasm.o' for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [bin/c8_dasm] Error 1

Je voulais m'arrêter ici et obtenir de l'aide parce que j'ai peur de créer ce Makefile plus compliqué que nécessaire et j'essaie d'éviter de prendre de mauvaises habitudes.

J'espère entendre des opinions sur ce que je ne suis pas conceptualiser correctement ici.

PREMIÈRE MISE À JOUR

J'ai réussi à le prendre petit à petit et à le faire fonctionner principalement. Voici ce avec quoi je me suis retrouvé:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

MKDIR_P ?= mkdir -p

# Targets

all: $(BIN_DIR)/c8_dasm $(BIN_DIR)/c8_asm $(BIN_DIR)/c8_terp

$(BIN_DIR)/c8_dasm: $(OBJ_DIR)/c8_dasm.o
    $(CC) $(LDLIBS) $(OBJ_DIR)/c8_dasm.o -o $@

$(BIN_DIR)/c8_asm: $(OBJ_DIR)/c8_asm.o
    $(CC) $(LDLIBS) $(OBJ_DIR)/c8_asm.o -o $@

$(BIN_DIR)/c8_terp: $(OBJ_DIR)/c8_terp.o
    $(MKDIR_P) $(dir $@)
    $(CC) $(LDLIBS) $(OBJ_DIR)/c8_terp.o -o $@

$(OBJECTS): $(OBJ_DIR)/%.o : $(SRC_DIR)/%.c
    $(MKDIR_P) $(dir $@)
    $(CC) $< -o $(OBJ_DIR)/$@

# Using implicit rules for updating an '.o' file from a correspondingly
# named '.c' file.

$(OBJ_DIR)/c8_dasm.o: $(SRC_DIR)/chip8.h
$(OBJ_DIR)/c8_asm.o: $(SRC_DIR)/chip8.h
$(OBJ_DIR)/c8_terp.o: $(SRC_DIR)/chip8.h

.PHONY: clean
clean:
    rm -r $(BUILD_DIR)
    rm $(OBJECTS)

Bien sûr, comme je le trouve avec Make, cela conduit à d'autres problèmes obscurs. Par exemple, faire ceci:

project
  src
    c8_asm.c
    c8_dasm.c
    c8_terp.c
    chip8.h
  Makefile

fonctionne très bien. Cela signifie que tous les fichiers sont générés et que les fichiers sont nettoyés, y compris le répertoire bin .

Cependant, si je fais cela:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

# Targets

all: c8_dasm c8_asm c8_terp

c8_dasm: c8_dasm.o
    $(CC) $(LDLIBS) c8_dasm.o -o $@

c8_asm: c8_asm.o
    $(CC) $(LDLIBS) c8_asm.o -o $@

c8_terp: c8_terp.o
    $(CC) $(LDLIBS) c8_terp.o -o $@

# Using implicit rules for updating an '.o' file from a correspondingly
# named '.c' file.

c8_dasm.o: chip8.h
c8_asm.o: chip8.h
c8_terp.o: chip8.h

.PHONY: clean
clean:
    rm c8_dasm c8_asm c8_terp c8_dasm.o c8_asm.o c8_terp.o

Cela se construit bien. Mais le nettoyage échoue à supprimer le répertoire bin (bien qu'il supprime les fichiers objets). Cela se produit quel que soit l'exécutable que j'essaie de créer.

Aucune recherche ne m'aide à en découvrir la raison.

DEUXIÈME MISE À JOUR p>

J'ai trouvé que le problème était également résolu. Il suffit d'utiliser le "-f" pour les instructions rm dans la cible propre.

TROISIÈME MISE À JOUR

Pour faire fonctionner la partie du répertoire du fichier objet, J'ai essayé (à partir de ceci: chemin include et répertoire src makefile ) à construire mon Makefile comme suit:

project
  Makefile
  c8_asm.c
  c8_dasm.c
  c8_terp.c
  chip8.h

J'ai pu condenser les trois lignes originales en utilisant chip8.h en une seule cible mais je n'ai aucun moyen de savoir si c'est correct. Il compile au moins. J'ai également changé la ligne OBJECTS pour refléter le nouveau OBJ_DIR que j'ai créé.

Cependant, cela ne met pas les fichiers objets au bon endroit. Il les place toujours dans le répertoire src plutôt que dans le répertoire obj .


0 commentaires

3 Réponses :


1
votes

C'est pourquoi il est logique de ne rien faire de compliqué avec Makefiles. Mettez simplement les noms de répertoires réels dans vos commandes. Ne vous fiez jamais aux caractères génériques.

Les gens qui utilisent C et C ++ et qui utilisent Makefiles passent trop de temps à essayer de les faire fonctionner plutôt que de simplement faire avancer les choses. C'est pourquoi vous voyez autant de questions que vous voyez et pourquoi les réponses varient tellement.

Dans votre cas particulier, vos cibles ne doivent pas toujours contenir le répertoire et cela fait partie du problème. Les règles générées n'ont pas de cible réelle dans votre fichier à cause des répertoires que vous ajoutez à tout. Vous devez penser en termes de ce qui est généré par chaque cible: le sens, le résultat. Donc, si c8_dasm obtient une sortie, c'est votre cible. Le répertoire n'a rien à voir avec cela. Vous devez donc supprimer toutes vos substitutions de répertoires là où elles ne sont pas nécessaires.

Mais avant de faire cela, posez-vous la question suivante: si votre première solution fonctionnait, pourquoi la changer? Il est préférable de ne même pas créer de répertoires lorsque vous utilisez Make. Mettez tout simplement dans le même répertoire que celui avec lequel vous avez commencé. Vous pouvez même voir que cela permet à votre Makefile d'être beaucoup plus propre.


7 commentaires

Ouais, j'ai commencé à ajouter beaucoup de ces chemins de répertoire dans le but d'obtenir quelque chose - n'importe quoi! -- travailler! :) La documentation à ce sujet est en fait assez bonne mais je trouve que la plupart a été écrite sans l'idée de la rendre cohérente. Cela étant dit, j'ai vu beaucoup d'utiliser des caractères génériques, donc je ne sais pas pourquoi vous suggérez de ne jamais les utiliser. Je ne sais pas non plus pourquoi un projet avec des répertoires serait si difficile pour Make. Je sais que beaucoup de gens font cela, donc c'est clairement mon manque de compréhension de la façon dont.


Voir ma mise à jour ci-dessus. J'en ai une partie qui fonctionne, mais cela conduit simplement à d'autres bizarreries.


Le nettoyage (de votre mise à jour) devrait fonctionner correctement de toute façon.


Bon travail! En fait, vous venez de vous montrer comment faire évoluer un Makefile. Il semble que la dernière chose que vous devez encore ajouter est d'avoir vos fichiers objets générés dans le répertoire obj, en supposant que vous vouliez toujours le faire.


Je doute que je fasse cela à ce stade. Je l'ai essayé mais cela mène juste au chaos, en particulier avec ce besoin de mon fichier chip8.h. Si je comprends cela, je posterai probablement cela comme réponse à moins que quelqu'un ne me batte.


À titre indicatif, vous allez avoir des problèmes avec vos lignes qui se trouvent sur vos cibles exécutables. Par exemple "c8_dasm: $ (SRC_DIR) /c8_dasm.o". Vous allez également vouloir trouver un moyen de condenser ces lignes qui font toutes référence à "$ (SRC_DIR) /chip8.h". Aussi maintenant que vous faites le "$ (BIN_DIR) / $ @" dans chacun de vos exécutables, cela ne fonctionnera plus pour généraliser pour votre répertoire obj. (Parce qu'il essaierait de construire dans bin / obj / c8_dasm.o et ainsi de suite.)


Voir ma troisième mise à jour. L'un des défis est que tous mes fichiers objets ne seront pas nécessaires pour chaque exécutable. Par exemple, je vais éventuellement avoir un fichier dasm.c et un fichier asm.c et donc un dasm.o et un < fichier objet code> asm.o . Mais le dasm.o n'est pertinent que pour l'exécutable c8_dasm alors que asm.o ne sera pertinent que pour le c8_asm exécutable. J'ai vu des exemples de la façon de gérer cela où vous avez juste sur l'exécutable et tous les fichiers objets sont nécessaires pour cela. Les exemples de plusieurs exécutables où seuls certains fichiers objets sont nécessaires pour chacun sont souvent extrêmement flous.



0
votes

Stackoverflow se plaint d'un trop grand nombre de commentaires, je vais donc faire une autre "réponse". Après nos allers-retours avec mon commentaire initial, votre dernier commentaire est correct. C'est ce que je voulais que vous voyiez.

Comprenez que vous ne pouvez pas utiliser Make pour faire exactement ce que vous voulez faire.

Voici donc vraiment la réponse: Vous ne pouvez pas créer plusieurs exécutables ET avec seulement certains des fichiers objets s’appliquant à chacun ET en utilisant une structure de répertoires. Make n’est en aucun cas capable de gérer ça.

En ce moment, vous essayez d'utiliser Make d'une manière qui n'était pas prévue, c'est pourquoi vous rencontrez tant de problèmes. Si vous continuez à jouer, vous allez rencontrer une série d'erreurs qui disent "symbole en double" parce que vous compilerez chacun de vos fichiers plusieurs fois pour chaque exécutable, en supposant que vous suivez la plupart des conseils que vous trouverez.

Découvrez ceci Comment puis-je créer un Makefile pour des projets C avec des sous-répertoires SRC, OBJ et BIN? pour voir ce que je veux dire. Celui-ci fonctionne car tous les fichiers objets sont utilisés pour créer un seul exécutable. Mais comme vous l'avez dit, ce ne sera pas le cas pour vous. Et c'est ce que Make ne peut pas gérer. C'est pourquoi vous ne trouvez pas de réponse à cela.

Et bien que votre fichier chip8.h ne posera plus de problèmes pour vous permettre de compiler, votre Makefile avec cette troisième mise à jour ne reconnaîtra pas quand le fichier chip8.h lui-même a changé. Vous devrez modifier un fichier .c pour forcer une recompilation afin que les modifications apportées à votre .h soient reconnues. Vous devez donc soit vous en tenir à votre deuxième mise à jour, soit utiliser autre chose que Make.


2 commentaires

Hmm. Voir ma réponse ci-dessous. Il semble faire ce que je veux, mais d'après ce que vous dites, je ne sais pas si je me fais des illusions.


Impressionnant! Je n'avais pas vu cela et je n'ai pas fait de suivi, mais c'est cool que vous ayez compris ce que vous deviez faire.



1
votes

Je crois avoir compris cela. Voici mon Makefile. Il semble faire ce que je veux. Il fait ce qui suit:

  • Compile tous les fichiers objets dans le répertoire obj .
  • Compile et crée des liens pour que les exécutables soient générés dans le répertoire bin .
  • Reconnaît si des fichiers .c sont modifiés et se recompile en conséquence.
  • Reconnaît si le fichier .h a été modifié et recompile tous les fichiers C qui le référencent.

Cela semble répondre à tous les critères mais je ne peux pas dire si je me suis peint dans un coin que je ne peux pas encore voir.

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin

$(shell mkdir -p $(BIN_DIR))
$(shell mkdir -p $(OBJ_DIR))

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

EXECUTABLES := c8_dasm c8_asm c8_terp

all: $(EXECUTABLES)

c8_dasm: $(OBJ_DIR)/c8_dasm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Disassembler Built"

c8_asm: $(OBJ_DIR)/c8_asm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Assembler Built"

c8_terp: $(OBJ_DIR)/c8_terp.o
   $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
   @echo "C8 Interpreter Built"

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/chip8.h
    $(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean
clean:
    rm -rf $(BIN_DIR)
    rm -rf $(OBJ_DIR)


0 commentaires