# Ne modifiez pas ce fichier. Par contre, si ça vous intéresse, vous pouvez le
# lire pour comprendre ce qu'il fait (il est abondamment commenté)

# Le répertoire où générer les fichiers temporaires objets (.o) et de dépendances (.d).
BUILD_DIR := build

# Le répertoire où se trouvent nos sources (fichiers .c).
SRC_DIR := src

# Le répertoire où se trouvent nos fichiers d'en-tête (fichiers .h).
INCLUDE_DIR := include

# Le nom de l'exécutable à générer
EXECUTABLE := matrix

# Les options du compilateur activées (explications ci-dessous pour chacune).
override CFLAGS += -std=gnu23 -MMD -Wall -pedantic -Wextra -Wpointer-arith \
-Wstrict-prototypes -Wmissing-prototypes -Wuninitialized \
-Wimplicit-function-declaration -Wunused-result -Wshadow -Wformat=2 \
-Wnull-dereference -Wvla -Wwrite-strings \
-fsanitize=address,undefined -fno-sanitize-recover=undefined \
-fno-omit-frame-pointer -g -Wcast-qual

# Explications des options
# -std=gnu23                                          : Compile avec la norme C23, en autorisant en plus les extensions
#                                                       GNU/POSIX (utile par exemple si on utilise random()/srandom()).
# -MMD                                                : Génère les dépendances entre fichiers sources et en-têtes.
# -Wall                                               : Active un ensemble important d'avertissements.
# -pedantic                                           : Signale les extensions du langage C qui ne suivent pas la norme.
# -Wextra                                             : Active des avertissements supplémentaires (dont -Wunused-parameter, voir plus bas).
# -Wpointer-arith                                     : Signale des opérations arithmétiques non standard sur certains pointeurs.
# -Wstrict-prototypes                                 : Exige des prototypes de fonctions avec leurs paramètres.
# -Wmissing-prototypes                                : Signale une fonction non static définie sans prototype préalable.
# -Wuninitialized                                     : Signale les utilisations possibles de variables non initialisées.
# -Wimplicit-function-declaration                     : Signale l'appel d'une fonction sans déclaration connue.
# -Wunused-result                                     : Signale le résultat ignoré de certaines fonctions.
# -Wshadow                                            : Signale une variable qui masque une autre variable.
# -Wformat=2                                          : Renforce la vérification des formats de printf, scanf, etc.
# -Wnull-dereference                                  : Signale certains déréférencements de pointeurs pouvant être nuls.
# -Wvla                                               : Signale l'utilisation de tableaux de taille variable.
# -Wwrite-strings                                     : Signale les tentatives de modification de chaînes littérales.
# -fsanitize=address,undefined                        : Détecte à l'exécution de nombreuses erreurs de mémoire et comportements indéfinis.
# -fno-sanitize-recover=undefined                     : Arrête le programme dès la première erreur détectée par -fsanitize=undefined
#                                                       (sinon, un comportement indéfini n'affiche qu'un message et l'exécution continue,
#                                                        au risque de passer inaperçu au milieu du reste de l'affichage).
# -fno-omit-frame-pointer                             : Conserve les pointeurs des cadres, notamment pour faciliter les diagnostics.
# -Wcast-qual                                         : Signale les conversions de pointeurs qui suppriment const ou volatile.
# -g                                                  : Ajoute les informations nécessaires au débogueur.

# -Wno-incompatible-pointer-types-discards-qualifiers désactive un avertissement sur la perte de const, mais ce nom
# d'avertissement n'est reconnu QUE par le compilateur clang -- y compris /usr/bin/gcc sous macOS, qui est en réalité
# Apple clang sous un autre nom. Le "vrai" GCC (celui de Linux, ou gcc-16 installé via Homebrew sous macOS) ne le
# reconnaît pas et échoue dessus une fois combiné à -Werror (option activée plus bas). On ne l'active donc que pour
# clang, détecté ci-dessous en interrogeant directement le compilateur plutôt qu'en supposant quoi que ce soit sur
# son nom : clang définit toujours la macro-préprocesseur __clang__, y compris quand il se fait passer pour gcc ; le
# vrai GCC ne la définit jamais.

CC_IS_CLANG := $(shell $(CC) -dM -E -x c /dev/null 2>/dev/null | grep -q '__clang__' && echo yes || echo no)
ifeq ($(CC_IS_CLANG),yes)
override CFLAGS += -Wno-incompatible-pointer-types-discards-qualifiers
endif

# Options de vérification supplémentaires, plus strictes et potentiellement très bavardes (non activées) :

# -Wsign-conversion / -Wconversion : signalent les conversions implicites
# signé/non signé (ex. int -> size_t) et les troncatures potentielles.
#
# Non activées ici car on utilise souvent volontairement des int (pas des size_t) pour les tailles et indices, ce qui
# rend ces avertissements très bavards sur du code par ailleurs correct (ex. malloc(n * sizeof(int)) avec n de type
# int). Les activer forcerait des casts (size_t) systématiques, sans rapport avec l'objectif.

# À propos de -fsanitize=address sur macOS : la détection de FUITES mémoire (LeakSanitizer) n'y est pas activée par
# défaut, contrairement à Linux, et ce Makefile ne cherche pas à l'y forcer (voir la remarque détaillée plus bas,
# à côté de RUN_ENV) : selon le compilateur utilisé, forcer ASAN_OPTIONS=detect_leaks=1 sous macOS provoque soit un
# arrêt immédiat (Apple clang, qui ne l'implémente pas), soit un blocage en fin d'exécution (un vrai gcc, dont le
# support de LeakSanitizer sous macOS est fragile). Les fuites mémoire restent donc uniquement vérifiées sous Linux.

# Ajoute ./include comme répertoire où chercher des fichiers d'en-tête (.h)
CPPFLAGS+=-I./$(INCLUDE_DIR)

# Configuration additionnelle sous MacOS et Linux
ifeq ($(shell uname -s), Darwin)
	CPPFLAGS+=-I/opt/homebrew/include
	LDFLAGS+=-L/opt/homebrew/lib
	SED := gsed
	# On ne force PAS ASAN_OPTIONS=detect_leaks=1 sous macOS (contrairement
	# à Linux, où LeakSanitizer est actif par défaut et fiable). Deux
	# raisons, selon le compilateur :
	#   - cc/clang d'Apple (le cas par défaut) : LeakSanitizer n'est pas
	#     implémenté du tout dans son runtime ASan. Forcer detect_leaks=1
	#     ne l'active pas : ça fait avorter le programme immédiatement
	#     avec "detect_leaks is not supported on this platform".
	#   - un vrai gcc (ex. gcc-16 de Homebrew) : le code de LeakSanitizer
	#     existe, mais son support de la suspension de tous les threads
	#     pour scanner la mémoire à la sortie du programme est fragile
	#     sous macOS (notamment sur Apple Silicon) : ça peut se traduire
	#     par un blocage total à la fin de l'exécution plutôt que par un
	#     crash -- pas un bug de logique dans votre code.
	# Sous macOS, ce Makefile détecte donc toujours les erreurs mémoire et
	# les comportements indéfinis (ASan/UBSan restent actifs), mais pas
	# les fuites. Pour vérifier aussi les fuites, utiliser Linux.
else
	SED := sed
endif

# Par défaut, TOUS les fichiers du répertoire SRC_DIR sont compilés par ce Makefile.
# On en supprimera un, matric1.c ou matrix2.c, en fonction de l'implémentation voulue.
SOURCES := $(wildcard $(SRC_DIR)/*.c)
_OBJECTS := $(SOURCES:%.c=%.o)
OBJECTS := $(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(_OBJECTS))
OBJECTS_TO_CLEAN := $(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(_OBJECTS))

# DEPS est utilisé pour savoir quels fichiers recompiler, grâce aux fichiers .d générés par gcc.
_DEPS := $(SOURCES:%.c=%.d)
DEPS := $(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(_DEPS))

# Spécifique à ce TP : la version de l'implémentation des matrices.
# Elle est lue dans le fichier version.h.
VERSION := $(shell $(SED) -n 's/^[[:space:]]*\#define[[:space:]]\+VERSION[[:space:]]\+\([0-9]\+\).*/\1/p' $(INCLUDE_DIR)/version.h)

# OBJECTS_COR est la liste des fichiers .o corrigés dont les sources ne sont pas fournis.
# Ils doivent être fournis dans le répertoire build. Il y en a deux imposés :
# - print_cor.o,
# - et, selon la version choisie, matrix1_cor.o ou matrix2_cor.o.
# On met tous ceux de ./build/ de suffixe _cor.o, et on supprime la version non voulue.

OBJECTS_COR := $(wildcard $(BUILD_DIR)/*_cor.o)

ifeq ($(VERSION),1)
  SOURCES := $(filter-out $(SRC_DIR)/matrix2.c,$(SOURCES))
  OBJECTS := $(filter-out $(BUILD_DIR)/matrix2.o,$(OBJECTS))
  OBJECTS_COR := $(filter-out $(BUILD_DIR)/matrix2_cor.o,$(OBJECTS_COR))
else ifeq ($(VERSION),2)
  SOURCES := $(filter-out $(SRC_DIR)/matrix1.c,$(SOURCES))
  OBJECTS := $(filter-out $(BUILD_DIR)/matrix1.o,$(OBJECTS))
  OBJECTS_COR := $(filter-out $(BUILD_DIR)/matrix1_cor.o,$(OBJECTS_COR))
else
  $(error VERSION must be 1 or 2 in $(INCLUDE_DIR)/version.h, got '$(VERSION)')
endif

# Construction de l'exécutable.
#
# -Werror : traite tous les avertissements activés ci-dessus comme des
# erreurs, SAUF -Wunused-parameter (voir juste en dessous) : la
# compilation doit être totalement propre pour être considérée correcte.
#
# -Wno-error=unused-parameter : le paramètre non utilisé reste affiché
# comme un avertissement (donc toujours visible), mais ne bloque pas la
# compilation. Objectif : tant qu'une fonction est en cours d'écriture
# (juste après avoir basculé son #if 0 en #if 1, avant d'avoir utilisé
# tous ses paramètres), la compilation continue de réussir ; -Werror
# reste actif pour tous les autres avertissements.
$(EXECUTABLE): CFLAGS += -O3 -Werror -Wno-error=unused-parameter \
-Wno-error=unused-function

$(EXECUTABLE): $(OBJECTS) $(OBJECTS_COR)
	$(CC) -o $@ $(LDFLAGS) $(CFLAGS) $^ $(LDLIBS)

# Mode debug : -O0 (plus lisible sous gdb et dans les traces d'ASan/UBSan,
# qui restent actifs), et n'impose pas -Werror : utile pour voir tous les
# avertissements activés sans être bloqué par eux.
.PHONY: debug
debug: CFLAGS += -O0

debug: $(OBJECTS) $(OBJECTS_COR)
	$(CC) -o $(EXECUTABLE)_debug $(LDFLAGS) $(CFLAGS) $^ $(LDLIBS)

$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
	mkdir -p $(BUILD_DIR) && $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
# -MJ$@.json pour clang

# Cible et répertoire dédiés à la génération de compile_commands.json (voir
# plus bas), sans -Werror et sans rien lier. Pourquoi un répertoire séparé
# (CC_JSON_DIR) plutôt que de simplement compiler dans $(BUILD_DIR) comme
# `matrix` ou `debug` : Make ne recompile un .o que si le .c est plus récent
# que lui, sans tenir compte des options de compilation utilisées. Si cette
# cible écrivait ses .o directement dans $(BUILD_DIR), un `make` normal
# lancé juste après un `make -B compile_commands.json` verrait des .o à jour
# (donc ne les recompilerait pas) et `matrix` serait relié à partir d'objets
# en réalité compilés sans -Werror ni -O3, sans que -Werror n'ait jamais été
# réellement vérifié. Un répertoire séparé élimine ce risque : les objets de
# `matrix` et ceux de cette cible ne se marchent jamais dessus.
CC_JSON_DIR := $(BUILD_DIR)/cc-json
CC_JSON_OBJECTS := $(patsubst $(BUILD_DIR)/%,$(CC_JSON_DIR)/%,$(OBJECTS))

$(CC_JSON_DIR)/%.o: $(SRC_DIR)/%.c
	mkdir -p $(CC_JSON_DIR) && $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<

.PHONY: cc-json-objects
cc-json-objects: $(CC_JSON_OBJECTS)

compile_commands.json:
	bear -- make -B cc-json-objects
# `matrix` compile avec -Werror ; cc-json-objects compile avec exactement
# les mêmes avertissements activés mais sans -Werror, et sans rien lier (pas
# d'exécutable produit). C_Cpp/clangd donnent leur sévérité aux diagnostics
# d'après les options trouvées dans compile_commands.json : avec -Werror
# capturé dedans, tout avertissement (sauf unused-parameter et
# unused-function, exemptés plus haut) apparaît en rouge (erreur) au lieu
# d'orange (avertissement). En utilisant cc-json-objects, les erreurs
# réelles (indépendantes de -Werror, comme une erreur de syntaxe) restent
# en rouge, et les avertissements restent en orange.

# Lance l'exécutable (RUN_ENV est vide sur macOS -- voir la remarque plus
# haut sur LeakSanitizer -- et sur Linux, où rien de spécial n'est requis).
.PHONY: run
run: $(EXECUTABLE)
	$(RUN_ENV) ./$(EXECUTABLE)

# Inclus les dépendances genérées par gcc -MMD.
-include $(DEPS)

# Garde le répertoire BUILD_DIR sauf s'il est vide. Garde les fichiers OBJECTS_COR et compile_commands.json.
.PHONY: clean
clean:
	rm -rf $(EXECUTABLE) $(EXECUTABLE)_debug $(OBJECTS_TO_CLEAN) $(DEPS) $(SRC_DIR)/TAGS src/core src/*.dSYM/ $(CC_JSON_DIR)
	rmdir $(BUILD_DIR) >/dev/null 2>&1 || true
