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.
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.
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