diff --git a/Makefile b/Makefile index 9c51577..4caf617 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,17 @@ BUILD_DIR := build # Outils lancés dans Docker (aucune installation sur le PC). SHELLCHECK_IMAGE := koalaman/shellcheck-alpine:v0.10.0 +# Chaîne de compilation C (docker/build-c/Dockerfile) : changer l'étiquette +# à chaque modification du Dockerfile, pour que make setup la reconstruise. +BUILD_C_IMAGE := ustagammon/build-c:1 -export SHELLCHECK_IMAGE +export SHELLCHECK_IMAGE BUILD_C_IMAGE -.PHONY: help setup build test lint deploy clean +# Lance une commande dans la chaîne C, sous l'utilisateur courant pour que +# build/ lui appartienne. +DOCKER_C := docker run --rm --user "$$(id -u):$$(id -g)" -v "$(CURDIR):/src" -w /src $(BUILD_C_IMAGE) + +.PHONY: help setup build engine test lint deploy clean help: ## Liste les commandes disponibles @echo "Ustagammon : commandes disponibles" @@ -25,10 +32,16 @@ setup: ## Installe la chaîne d'outils (images Docker) ; sans effet si déjà fa @command -v docker >/dev/null || { echo "Docker est requis : sudo apt install docker.io"; exit 1; } @docker image inspect $(SHELLCHECK_IMAGE) >/dev/null 2>&1 \ || docker pull $(SHELLCHECK_IMAGE) + @docker image inspect $(BUILD_C_IMAGE) >/dev/null 2>&1 \ + || docker build -t $(BUILD_C_IMAGE) docker/build-c @echo "Chaîne d'outils prête." -build: ## Compile le moteur (si nécessaire) puis l'application - @echo "Rien à compiler pour l'instant : le moteur arrive à l'étape 0a." +build: engine ## Compile le moteur (si nécessaire) puis l'application + +engine: ## Compile le moteur, ses données et build/engine-cli (dans Docker) + @docker image inspect $(BUILD_C_IMAGE) >/dev/null 2>&1 \ + || { echo "Chaîne C absente : lancer d'abord make setup"; exit 1; } + @$(DOCKER_C) make -f engine/engine.mk -j2 --no-print-directory test: ## Lance tous les tests @tests/run.sh diff --git a/docker/build-c/Dockerfile b/docker/build-c/Dockerfile new file mode 100644 index 0000000..95d841d --- /dev/null +++ b/docker/build-c/Dockerfile @@ -0,0 +1,14 @@ +# Chaîne de compilation C d'Ustagammon (moteur embarqué et engine-cli). +# Construite une fois par `make setup` ; le build tourne dedans, jamais avec +# les outils de l'hôte (ADR 0004). +# +# - gcc et make : compilation du moteur et d'engine-cli ; +# - libglib2.0-dev : uniquement pour les outils officiels de gnubg lancés au +# build (makeweights, makebearoff), qui produisent les données du moteur. +# Le moteur embarqué lui-même n'utilise pas GLib (shim engine/compat/). +FROM debian:trixie + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + gcc libc6-dev make pkg-config libglib2.0-dev \ + && rm -rf /var/lib/apt/lists/* diff --git a/engine/compat/host-tools-support.c b/engine/compat/host-tools-support.c index 20929fc..0069518 100644 --- a/engine/compat/host-tools-support.c +++ b/engine/compat/host-tools-support.c @@ -10,6 +10,8 @@ * et rend son nom, à supprimer puis libérer par l'appelant. */ +#define _POSIX_C_SOURCE 200809L + #include #include #include diff --git a/engine/engine.mk b/engine/engine.mk new file mode 100644 index 0000000..d163d83 --- /dev/null +++ b/engine/engine.mk @@ -0,0 +1,124 @@ +# engine.mk -- compilation du moteur embarqué, de ses données et d'engine-cli. +# +# Lancé dans l'image Docker ustagammon/build-c par `make engine` (Makefile +# racine), depuis la racine du dépôt. Tout est produit dans build/ : +# +# build/libengine.a le moteur : cœur de gnubg + shim GLib + engine.c +# build/engine-cli le programme en ligne de commande +# build/data/ les données lues par le moteur à l'exécution +# build/host/ les outils officiels de gnubg qui génèrent les +# données (makeweights, makebearoff), avec la vraie GLib +# +# Les dépendances vers les en-têtes sont suivies (-MMD) : un second appel ne +# recompile rien si aucune source n'a changé. + +BUILD := build +GNUBG := engine/gnubg +COMPAT := engine/compat + +CC := gcc +AR := ar + +# Options du build officiel de gnubg (configure.ac) : -O3 -ffast-math et +# assertions GLib désactivées. Elles s'appliquent à tout le code, pour que +# le moteur calcule comme gnubg de bureau. +OPTIMISATION := -O3 -ffast-math -DG_DISABLE_ASSERT + +# Notre propre C : C11 strict, aucun avertissement toléré. +OUR_WARNINGS := -std=c11 -Wall -Wextra -Werror + +# Code de gnubg : compilé tel quel, avec ses propres réglages d'avertissement. +GNUBG_WARNINGS := + +ENGINE_INCLUDES := -I$(COMPAT) -I$(COMPAT)/glib-shim -I$(GNUBG) -I$(GNUBG)/lib -Iengine/include +HOST_INCLUDES := -I$(COMPAT) -I$(GNUBG) -I$(GNUBG)/lib $(shell pkg-config --cflags glib-2.0) +HOST_DEFINES := -DLOCALEDIR=\"/usr/share/locale\" +HOST_LIBS := $(shell pkg-config --libs glib-2.0) -lm + +# Fichiers de gnubg compilés dans le moteur (liste : engine/gnubg/PROVENANCE.md). +GNUBG_CORE := eval.c positionid.c matchid.c matchequity.c mec.c bearoff.c \ + bearoffgammon.c mtsupport.c lib/neuralnet.c lib/neuralnetsse.c lib/inputs.c \ + lib/cache.c lib/list.c lib/isaac.c lib/md5.c +GNUBG_ENGINE := $(GNUBG_CORE) drawboard.c + +ENGINE_OBJECTS := $(GNUBG_ENGINE:%.c=$(BUILD)/obj/gnubg/%.o) \ + $(BUILD)/obj/compat/glib-shim.o $(BUILD)/obj/compat/gnubg-support.o \ + $(BUILD)/obj/engine.o +CLI_OBJECTS := $(BUILD)/obj/engine-cli/main.o +HOST_CORE_OBJECTS := $(GNUBG_CORE:%.c=$(BUILD)/host/gnubg/%.o) $(BUILD)/host/compat/gnubg-support.o + +DATA := $(BUILD)/data/gnubg.wd $(BUILD)/data/gnubg_os0.bd $(BUILD)/data/gnubg_ts0.bd \ + $(BUILD)/data/Kazaross-XG2.xml + +.PHONY: all +all: $(BUILD)/engine-cli $(DATA) + +# ------------------------------------------------------------------ Moteur + +$(BUILD)/obj/gnubg/%.o: $(GNUBG)/%.c + @mkdir -p $(@D) + $(CC) $(OPTIMISATION) $(GNUBG_WARNINGS) $(ENGINE_INCLUDES) -MMD -MP -c $< -o $@ + +$(BUILD)/obj/compat/%.o: $(COMPAT)/%.c + @mkdir -p $(@D) + $(CC) $(OPTIMISATION) $(OUR_WARNINGS) $(ENGINE_INCLUDES) -MMD -MP -c $< -o $@ + +$(BUILD)/obj/engine.o: engine/engine.c + @mkdir -p $(@D) + $(CC) $(OPTIMISATION) $(OUR_WARNINGS) $(ENGINE_INCLUDES) -MMD -MP -c $< -o $@ + +$(BUILD)/obj/engine-cli/%.o: engine-cli/%.c + @mkdir -p $(@D) + $(CC) $(OPTIMISATION) $(OUR_WARNINGS) -Iengine/include -MMD -MP -c $< -o $@ + +$(BUILD)/libengine.a: $(ENGINE_OBJECTS) + rm -f $@ + $(AR) rcs $@ $^ + +$(BUILD)/engine-cli: $(CLI_OBJECTS) $(BUILD)/libengine.a + $(CC) $^ -lm -o $@ + +# ----------------------------------------- Outils de génération des données + +$(BUILD)/host/gnubg/%.o: $(GNUBG)/%.c + @mkdir -p $(@D) + $(CC) $(OPTIMISATION) $(GNUBG_WARNINGS) $(HOST_INCLUDES) $(HOST_DEFINES) -MMD -MP -c $< -o $@ + +$(BUILD)/host/compat/%.o: $(COMPAT)/%.c + @mkdir -p $(@D) + $(CC) $(OPTIMISATION) $(OUR_WARNINGS) $(HOST_INCLUDES) -MMD -MP -c $< -o $@ + +$(BUILD)/host/makeweights: $(BUILD)/host/gnubg/makeweights.o $(BUILD)/host/gnubg/lib/neuralnet.o \ + $(BUILD)/host/gnubg/lib/neuralnetsse.o $(BUILD)/host/compat/gnubg-support.o + $(CC) $^ $(HOST_LIBS) -o $@ + +$(BUILD)/host/makebearoff: $(BUILD)/host/gnubg/makebearoff.o $(HOST_CORE_OBJECTS) \ + $(BUILD)/host/compat/host-tools-support.o + $(CC) $^ $(HOST_LIBS) -o $@ + +# ------------------------------------------------------------------ Données + +# Mêmes commandes que le Makefile.am de gnubg. Écriture dans un fichier +# temporaire, pour ne jamais laisser une base à moitié écrite. +$(BUILD)/data/gnubg.wd: engine/data/gnubg.weights $(BUILD)/host/makeweights + @mkdir -p $(@D) + $(BUILD)/host/makeweights -f $@.tmp $< + mv $@.tmp $@ + +# Comme dans gnubg, les bases ne sont générées que si elles manquent : elles +# ne dépendent pas du code (longues à produire). +$(BUILD)/data/gnubg_os0.bd: | $(BUILD)/host/makebearoff + @mkdir -p $(@D) + $(BUILD)/host/makebearoff -o 6 -s 7999999 -f $@.tmp + mv $@.tmp $@ + +$(BUILD)/data/gnubg_ts0.bd: | $(BUILD)/host/makebearoff + @mkdir -p $(@D) + $(BUILD)/host/makebearoff -t 6x6 -f $@.tmp + mv $@.tmp $@ + +$(BUILD)/data/Kazaross-XG2.xml: engine/data/Kazaross-XG2.xml + @mkdir -p $(@D) + cp $< $@ + +-include $(shell find $(BUILD) -name '*.d' 2>/dev/null)