Chapitre M · Makefile

Make Make Make Make Make

Le Makefile expliqué

Notre Makefile fait trois choses : compiler les sources C++/ObjC++, compiler les shaders .metal en .metallib, et assembler le tout dans un bundle .app.

C'est ce que fait Xcode automatiquement et avec une interface graphique.

Le Makefile est écrit en Shell, il remplace toutes tes lignes de commandes pour chaque re-compilation en binaire de tous tes fichiers, de manière ordonnée, par des dépendances configurées telles que tu les définis. En bref il va automatiser nos compilations et il peut aussi effectuer n'importe quelle opération disponible via le shell du système d'exploitation (OS). C'est un énorme projet, de communiquer avec sa carte graphique.

1. Le compilateur — pourquoi ObjC++ ?

# On utilise clang++ en mode objective-c++
# Obligatoire pour mélanger metal-cpp (C++) et AppKit/MTKView (ObjC)
CXX = clang++ -x objective-c++

2. Les flags critiques :

Représenté en shell par "-"

FLAGS = -std=c++17 -ObjC++ \ traite les fichiers d'entrée source comme des entrées Objective-C et Object-C++ respectivement.
        -g \
        -I./includes \
        -I./Frameworks/metal-cpp \
        -I./Frameworks/metal-cpp-extensions \
        -fobjc-weak                     weak references ObjC sans ARC
        -ferror-limit=10                si erreurs, imprime les 10 premières et s'arrête.
Pas de -fobjc-arc
On n'utilise pas l'ARC (Automatic Reference Counting). metal-cpp gère sa propre mémoire via NS::TransferPtr et release(). Activer l'ARC casserait la gestion mémoire du wrapper. Les flags -Warc-bridge-casts-disallowed-in-nonarc et -fobjc-weak remplacent l'ARC proprement.

3. Compiler les shaders Metal — deux étapes

Un shader .metal ne se compile pas directement en binaire. Il passe par deux étapes :

# Étape 1 : .metal → .air (Apple Intermediate Representation)
$(OBJS_DIR)/%.air: $(SHADERS)/%.metal
    xcrun -sdk macosx metal -c $< -o $@

# Étape 2 : .air → .metallib (bibliothèque de shaders)
$(OBJS_DIR)/%.metallib: $(OBJS_DIR)/%.air
    xcrun -sdk macosx metallib $< -o $@

Le fichier .metallib est copié dans Resources/ du bundle app. Au runtime, Metal le charge via device->newDefaultLibrary().

4. Les frameworks Apple linkés

FRAMEWORKS = Metal MetalKit QuartzCore AppKit Foundation \
             GameController Cocoa CoreHaptics CoreMotion \
             PHASE AVFAudio MetalFX CoreText CoreGraphics \
             CoreVideo ModelIO

LDFLAGS = $(addprefix -framework , $(FRAMEWORKS))

5. Le bundle .app

Sur macOS, un exécutable GUI doit être dans un bundle pour avoir une vraie fenêtre. Structure minimale :

triangle.app/
└── Contents/
    ├── Info.plist      ← métadonnées (nom, bundle ID, version)
    ├── MacOS/
    │   └── triangle    ← l'exécutable
    └── Resources/
        └── triangle.metallib

MAKEFILE

Le même, pour tout le cours.

C'est un fichier sans extension nommé Makefile avec un M majuscule.

erreurs courantes

Makefile:x: *** missing separator. Stop.

→ Signifie qu’il y a des espaces au lieu de tabulation(s)

###############################################################
#                                         +       +           #
#      File: Makefile            +++     +++                  #
#                                         +       +           #
#       By: Laboitederemdal                +       +          #
#                                        +           +        #
#       Created: 19/09/2025 16:48:39      + + + + + +         #
#       Updated: 17/05/2026                                   #
###############################################################
CXX             =   clang++ -x objective-c++
CC              =   clang # pour compiler du langage C
NAME            =   LearnMSL # personnalise (c'est fragile)
SRCS            =   Renderer Application
INCLUDES        =   includes Renderer
SHADERS         =   Shaders
APP_NAME        =   $(NAME).app
APP_CONTENTS    =   $(APP_NAME)/Contents
APP_MACOS       =   $(APP_CONTENTS)/MacOS
APP_RESOURCES   =   $(APP_CONTENTS)/Resources
CPP_FILES       =   $(shell find $(SRCS) -name '*.cpp')
C_FILES         =   $(shell find $(SRCS) -name '*.c')
MM_FILES        =   $(shell find $(SRCS) -name '*.mm')
M_FILES         =   $(shell find $(SRCS) -name '*.m')
HEADERS_H       =   $(shell find $(INCLUDES) -name '*.h') /usr/local/include
HEADERS_HPP     =   $(shell find $(INCLUDES) -name '*.hpp')
METAL_FILES     =   $(shell find $(SHADERS) -name '*.metal')
METAL_AIR       =   $(patsubst $(SHADERS)/%.metal,$(OBJS_DIR)/%.air,$(METAL_FILES))
METAL_METALLIB  =   $(patsubst $(SHADERS)/%.metal,$(OBJS_DIR)/%.metallib,$(METAL_FILES))
METAL_SRCS      :=  $(wildcard Shaders/*.metal)
METAL_AIR       :=  $(METAL_SRCS:.metal=.air)
METAL_FLAGS     =   # -std=macos-metal4.0
CFLAGS          =   -I./includes -Wall -Wextra -Werror -O2
OBJS_DIR        =   Product
DEPS_DIR        =   $(OBJS_DIR)
PLIST           =   Application/macOS/macOSInfo.plist
ICON            =   AppIcon.icns # ajuste et place-le à côté de ton macOSInfo.plist
ASSIMP_PREFIX   :=  $(shell brew --prefix assimp)
FLAGS           =   -std=c++17 -ObjC++ -g -I./includes -I./Shaders -I./Frameworks/metal-cpp \
                    -I./Frameworks/metal-cpp-extensions -ferror-limit=100 -fobjc-weak
# pas en Makefile : -fobjc-arc

LINKERFLAGS     =   -Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker $(PLIST)

CPP_OBJS        =    $(patsubst $(SRCS)/%.cpp,$(OBJS_DIR)/%.o,$(CPP_FILES))
C_OBJS          =    $(patsubst $(SRCS)/%.c,$(OBJS_DIR)/%.o,$(C_FILES))
MM_OBJS         =    $(patsubst $(SRCS)/%.mm,$(OBJS_DIR)/%.o,$(MM_FILES))
M_OBJS          =    $(patsubst $(SRCS)/%.m,$(OBJS_DIR)/%.o,$(M_FILES))

$(shell mkdir -p $(sort $(dir $(C_OBJS))) $(sort $(dir $(CPP_OBJS))) $(sort $(dir $(MM_OBJS))) $(sort $(dir $(M_OBJS))) $(sort $(dir $(METAL_AIR))) $(sort $(dir $(METAL_METALLIB))))

C_DEPS          =    $(patsubst $(OBJS_DIR)/%.o,$(DEPS_DIR)/%.d,$(C_OBJS))
CPP_DEPS        =    $(patsubst $(OBJS_DIR)/%.o,$(DEPS_DIR)/%.d,$(CPP_OBJS))
MM_DEPS         =    $(patsubst $(OBJS_DIR)/%.o,$(DEPS_DIR)/%.d,$(MM_OBJS))
M_DEPS          =    $(patsubst $(OBJS_DIR)/%.o,$(DEPS_DIR)/%.d,$(M_OBJS))
DEP_FILES       =    $(CPP_DEPS) $(C_DEPS) $(MM_DEPS) $(M_DEPS)

$(shell mkdir -p $(sort $(dir $(DEP_FILES))))

# prompts colors
_GREEN          =    \033[92;5;118m
_YELLOW         =    \033[93;5;226m
RED             =    \033[31m
GREEN           =    \033[32m
YELLOW          =    \033[33m
CYAN            =    \033[96m
RESET           =    \033[0m
CURSIVE         =    \033[33;3m
GRAY            =    \033[33;2;37m

BOLDBLACK       =    \033[1;30m
BOLDRED         =    \033[1;31m
BOLDGREEN       =    \033[1;32m
BOLDYELLOW      =    \033[1;33m
BOLDBLUE        =    \033[1;34m
BOLDPURPLE      =    \033[1;35m
BOLDCYAN        =    \033[1;36m
BOLDWHITE       =    \033[1;37m

HIBLACK         =    \033[0;90m
HIRED           =    \033[0;91m
HIGREEN         =    \033[0;92m
HIYELLOW        =    \033[0;93m
HIBLUE          =    \033[0;94m
HIPURPLE        =    \033[0;95m
HICYAN          =    \033[0;96m
HIWHITE         =    \033[0;97m

UBLACK          =    \033[4;30m
URED            =    \033[4;31m
UGREEN          =    \033[4;32m
UYELLOW         =    \033[4;33m
UBLUE           =    \033[4;34m
UPURPLE         =    \033[4;35m
UCYAN           =    \033[4;36m
UWHITE          =    \033[4;37m

BBLACK          =    \033[40m
BRED            =    \033[41m
BGREEN          =    \033[42m
BYELLOW         =    \033[43m
BBLUE           =    \033[44m
BPURPLE         =    \033[45m
BCYAN           =    \033[46m
BWHITE          =    \033[47m

SOULIGNAGE      =    \033[4'm

VPATH=./metal-cpp
vpath %.cpp $(SRCS)
vpath %.c $(SRCS)
vpath %.mm $(SRCS)
vpath %.m $(SRCS)

FRAMEWORKS      =   Metal MetalKit QuartzCore AppKit Foundation GameController Cocoa CoreHaptics \
                    CoreMotion PHASE AVFAudio MetalFX CoreText CoreGraphics CoreVideo ModelIO CloudKit
LDFLAGS         =   $(addprefix -framework , $(FRAMEWORKS))
LDFLAGS         +=  -L$(ASSIMP_PREFIX)/lib -lassimp
MACOS_SDK       =   $(shell xcrun --sdk macosx --show-sdk-path)
CXXFLAGS        +=  -isysroot $(MACOS_SDK)
CXXFLAGS        +=  -I$(ASSIMP_PREFIX)/include
CFLAGS          +=  -isysroot $(MACOS_SDK)

$(OBJS_DIR)/%.air: $(SHADERS)/%.metal
    @echo "\t$(_YELLOW) compiling metal.. $*.metal$(RESET)"
    @mkdir -p $(dir $@)
    xcrun -sdk macosx metal $(METAL_FLAGS) -c $< -o $@

$(OBJS_DIR)/%.o: %.c
    @ echo "\t$(_YELLOW) compiling.. $*.c$(RESET)"
    @mkdir -p $(dir $@)
    $(CC) $(CFLAGS) -c $< -o $@

$(OBJS_DIR)/%.o: %.cpp
    @ echo "\t$(_YELLOW) compiling.. $*.cpp$(RESET)"
    $(CXX) $(FLAGS) -c $< -o $@

$(OBJS_DIR)/%.o: %.mm #$(SRCS_DIR)/%.mm $(HEADERS_H)
    @ echo "\t$(_YELLOW) compiling.. $*.mm$(RESET)"
    $(CXX) $(FLAGS) -c $< -o $@

$(OBJS_DIR)/%.o: %.m
    @ echo "\t$(_YELLOW) compiling.. $*.m$(RESET)"
    $(CC) $(FLAGS) -c $< -o $@

$(DEPS_DIR)/%.d: $(SRCS_DIR)/%.cpp | $(DEPS_DIR)
    @ echo "\t$(_YELLOW) compiling.. cpp $*.d$(RESET)"
    $(CXX) $(FLAGS) -MM $< -MT $(@:.d=.o) -MF $@

$(DEPS_DIR)/%.d: $(SRCS_DIR)/%.c | $(DEPS_DIR)
    @ echo "\t$(_YELLOW) compiling.. $*.d$(RESET)"
    $(CC) $(FLAGS) -MM $< -MT $(@:.d=.o) -MF $@

$(DEPS_DIR)/%.d: $(SRCS_DIR)/%.mm | $(DEPS_DIR)
    @ echo "\t$(_YELLOW) compiling.. $*.d$(RESET)"
    $(CXX) $(FLAGS) -MM $< -MT $(@:.d=.o) -MF $@

$(DEPS_DIR)/%.d: $(SRCS_DIR)/%.m | $(DEPS_DIR)
    @ echo "\t$(_YELLOW) compiling.. $*.d$(RESET)"
    $(CC) $(FLAGS) -MM $< -MT $(@:.d=.o) -MF $@

all: init $(APP_NAME)

init:
    @ if test -f $(NAME); \
        then echo "$(CYAN)\t[program already created]$(RESET)"; \
    else \
        echo "\t$(BPURPLE)[Initialize program]$(RESET)"; \
        $(shell mkdir -p $(sort $(dir $(CPP_OBJS))) $(sort $(dir $(C_OBJS))) $(sort $(dir $(MM_OBJS)))) \
    fi

$(APP_NAME): $(NAME)
    @echo "\t$(CYAN)[Creating application bundle]$(RESET)"
    @mkdir -p $(APP_MACOS) $(APP_RESOURCES)
    @cp $(NAME) $(APP_MACOS)/
    @mkdir -p $(APP_CONTENTS)
    @cp $(PLIST) $(APP_CONTENTS)/Info.plist
    @if [ -f $(ICON) ]; then cp $(ICON) $(APP_RESOURCES)/AppIcon.icns; fi
    @for metallib in $(METAL_METALLIB); do \
        if [ -f $$metallib ]; then \
            cp $$metallib $(APP_RESOURCES)/; \
                echo "\t$(HIWHITE)Metallib copied: $$metallib$(RESET)"; \
        fi \
    done
    cp default.metallib $(APP_RESOURCES)/default.metallib # .exe lit à la racine / .app lit dans Product/
    @find . -maxdepth 3 \( -name "*.png" -o -name "*.csv" -o -name "*.m4a" -o -name "*.mp3" \) \
            ! -path "./*.app/*" -exec cp {} $(APP_RESOURCES)/ \;
    @echo "\t$(SOULIGNAGE)Application bundle created: $(APP_NAME)$(RESET)"

$(NAME): $(C_OBJS) $(CPP_OBJS) $(MM_OBJS) $(M_OBJS) default.metallib
    @echo "\t$(CYAN)[Creating program]$(RESET)xv
    @echo "\t$(YELLOW) compiling$(_YELLOW)... $(RESET)$(YELLOW)$*.c++$*.cpp$*.mm$*.m$(RESET)"
    $(CXX) $(FLAGS) $(C_OBJS) $(CPP_OBJS) $(MM_OBJS) $(M_OBJS) -o $(NAME) $(LDFLAGS) $(LINKERFLAGS)
    @echo "$(CURSIVE)[Executable created & ready]$(RESET)\n\033[32mCompiled! ᕦ(\033[31m♥\033[32m_\033[31m♥\033[32m)ᕤ"
    @echo "$(_GREEN)object files were in test$(RESET)"

default.metallib: $(METAL_AIR) # créer une seule librairie contenant tous les shaders, comme Xcode
ifeq ($(strip $(METAL_AIR)),)
    @echo ⚠  "Aucun shader .metal — metallib ignoré (chapitre sans GPU)"
    @touch default.metallib
else
    xcrun -sdk macosx metallib -o $@ $(METAL_AIR)
endif

%.air: %.metal
    xcrun -sdk macosx metal -c $< -o $@

clean:
    @echo "$(GRAY)[cleaning up .out & objects files]$(RESET)"
    rm -rf $(OBJS_DIR) Shaders/*.air Shaders/*.metallib $(APP_NAME)

fclean: clean
    @printf "$(RED)[cleaning up .out, objects & library files]$(_NC)\n$(URED)Deleting EVERYTHING! ⌐(ಠ۾ಠ)¬$(RESET)$(BOLDRED)$(RESET)\n"
    rm -f $(NAME)

re:     fclean all

run:
    $(shell open $(APP_NAME))

.PHONY: all clean fclean re

Ce Makefile peut compiler tous tes fichiers à partir de n'importe quel checkpoint compilation que tu suis dans le cours.

J'ai inclu clang dans le cas où tu veux remplacer des fonctions par les tiennes (ce qui est redondant en production). memcpy est une fonction écrite en C, souvent utilisées.

Tu peux retirer les Frameworks inutiles (CoreText), les couleurs qui décorent, …

Comment l'utiliser ?

Tu peux reprendre ce modèle, corrige les noms de dossiers et autres dans le cas où tu ne reprends pas les même que le cours :

* AppIcon.icns → […].icns
* Dossier(s) Renderer/ → […]/
* Nom de ton application qui doit correspondre .plist

Si tu as quelques erreurs, c'est normal car certains composants de ton application sont probablement absent. Par exemple pour l'icon d'application, jusqu'à ajout de ton fichier icon.

Inclus le fichier dans ton dossier, LearnMSL/ dans le cours.

# Usage — avec le terminal, cd vers LearnMSL/
make
# Ton projet devrait alors est compilé, tu peux l'exécuter :
./LearnMSL
# Ou :
open LearnMSL.app
# d'autres commandes :
make re && make fclean && make clean && make all