diff --git a/src/fr/monlouyan/bakefile/CommandExecutor.java b/src/fr/monlouyan/bakefile/CommandExecutor.java index f09c767..1710f2d 100644 --- a/src/fr/monlouyan/bakefile/CommandExecutor.java +++ b/src/fr/monlouyan/bakefile/CommandExecutor.java @@ -7,6 +7,7 @@ public class CommandExecutor { private boolean debug; private boolean needsUpdate = false; // Pour tracker si quelque chose doit être mis à jour private boolean isCircular = false; // Pour tracker si un cycle a été détecté + private boolean futureTimestampDetected = false; // Pour détecter les timestamps dans le futur public CommandExecutor(boolean debug, boolean isCircular) { this.debug = debug; @@ -18,6 +19,18 @@ public class CommandExecutor { boolean ruleNeedsUpdate = rule.needsUpdate(); if (ruleNeedsUpdate) { needsUpdate = true; // Au moins une règle doit être mise à jour + + // Vérifier les timestamps des dépendances pour détecter ceux dans le futur + for (String dependency : rule.getDependencies()) { + if (dependency.startsWith("~")) { + continue; + } + + File depFile = new File(dependency); + if (depFile.exists() && TimestampManager.getTimestamp(depFile) > System.currentTimeMillis()) { + futureTimestampDetected = true; + } + } } // Si la règle a besoin d'être mise à jour et qu'il n'y a pas de commandes @@ -56,6 +69,11 @@ public class CommandExecutor { } } + // Si on a détecté des timestamps dans le futur, afficher un avertissement à la fin (comme make) + if (futureTimestampDetected && !isCircular) { + System.out.println("bake: warning: Clock skew detected. Your build may be incomplete."); + } + // Vérifier si cette règle est une cible directement demandée par l'utilisateur boolean isRequestedTarget = BakeCLI.getTargets().contains(rule.getName()) || (BakeCLI.getTargets().isEmpty() && rule.getName().equals(BakefileParser.getFirstTarget())); diff --git a/src/fr/monlouyan/bakefile/Rule.java b/src/fr/monlouyan/bakefile/Rule.java index 5ce49d8..761a0f7 100644 --- a/src/fr/monlouyan/bakefile/Rule.java +++ b/src/fr/monlouyan/bakefile/Rule.java @@ -2,6 +2,7 @@ package fr.monlouyan.bakefile; import java.io.File; import java.util.List; +import java.util.Date; public class Rule { private String name; @@ -97,6 +98,8 @@ public class Rule { System.out.println("Debug : Target file '" + name + "' last modified at " + TimestampManager.formatTimestamp(targetTimestamp)); } + long currentTime = System.currentTimeMillis(); + for (String dependency : dependencies) { // Skip dependencies with tilde in path if (dependency.startsWith("~")) { @@ -104,7 +107,22 @@ public class Rule { } File depFile = new File(dependency); - long depTimestamp = depFile.exists() ? TimestampManager.getTimestamp(depFile) : 0; + if (!depFile.exists()) { + continue; + } + + long depTimestamp = TimestampManager.getTimestamp(depFile); + + // Vérifier si le timestamp de la dépendance est dans le futur + if (depTimestamp > currentTime) { + // Avertissement similaire à make + System.out.println("bake: Warning: File '" + dependency + "' has modification time " + + ((depTimestamp - currentTime) / 1000) + " s in the future"); + if (BakeCLI.isDebug()) { + System.out.println("Debug : Dependency " + dependency + " has a timestamp in the future, needs update"); + } + return true; + } if (BakeCLI.isDebug()) { System.out.println("Debug : Dependency '" + dependency + "' last modified at " + TimestampManager.formatTimestamp(depTimestamp)); @@ -120,13 +138,14 @@ public class Rule { } // Vérifier les timestamps seulement si le fichier existe - if (depFile.exists() && TimestampManager.getTimestamp(depFile) > targetTimestamp) { + if (depTimestamp > targetTimestamp) { if (BakeCLI.isDebug()) { System.out.println("Debug : Dependency " + dependency + " is newer than target file " + name + ", needs update"); } return true; } } + return false; } } \ No newline at end of file diff --git a/tests/C/test-22-future-timestamp/README.md b/tests/C/test-22-future-timestamp/README.md index 2dd8981..1eefdd6 100644 --- a/tests/C/test-22-future-timestamp/README.md +++ b/tests/C/test-22-future-timestamp/README.md @@ -89,15 +89,6 @@ Le test suit les étapes suivantes : ## Résultats attendus -- Le comportement standard de GNU Make est de reconstruire l'exécutable si le fichier source a un timestamp dans le futur +- Le comportement standard de Make est de reconstruire l'exécutable si le fichier source a un timestamp dans le futur - `bake` devrait adopter le même comportement et reconstruire l'exécutable -- Les messages de sortie devraient indiquer la reconstruction - -## Explications techniques - -Les systèmes de build comme make utilisent les timestamps des fichiers pour déterminer quand une recompilation est nécessaire. Quand un fichier source a une date de modification qui se situe dans le futur : - -1. Certains systèmes de build considèrent toujours que le fichier a été modifié et déclenchent la reconstruction -2. D'autres peuvent avoir des comportements particuliers ou des avertissements - -Ce test vérifie que `bake` gère correctement ce cas particulier en adoptant le même comportement que `make`, ce qui est important pour assurer une compatibilité complète, même dans des situations inhabituelles. \ No newline at end of file +- Les messages de sortie devraient indiquer la reconstruction \ No newline at end of file diff --git a/tests/C/test-22-future-timestamp/bake/main b/tests/C/test-22-future-timestamp/bake/main index 1af39f5..29e517e 100755 Binary files a/tests/C/test-22-future-timestamp/bake/main and b/tests/C/test-22-future-timestamp/bake/main differ diff --git a/tests/C/test-22-future-timestamp/make/main b/tests/C/test-22-future-timestamp/make/main index 1af39f5..29e517e 100755 Binary files a/tests/C/test-22-future-timestamp/make/main and b/tests/C/test-22-future-timestamp/make/main differ diff --git a/tests/C/test-22-future-timestamp/run_test22.sh b/tests/C/test-22-future-timestamp/run_test22.sh old mode 100644 new mode 100755 index ab72593..4eb121f --- a/tests/C/test-22-future-timestamp/run_test22.sh +++ b/tests/C/test-22-future-timestamp/run_test22.sh @@ -2,6 +2,7 @@ # Script de test pour comparer bake et make # Test 22 - Gestion des timestamps dans le futur +# À placer dans le répertoire test-22-future-timestamp # Création du répertoire de logs s'il n'existe pas mkdir -p logs @@ -36,8 +37,19 @@ cd make echo "Compilation initiale avec make..." | tee -a "../$LOG_FILE" make 2>&1 | tee -a "../$LOG_FILE" +# Enregistrons le timestamp de l'exécutable make après compilation initiale +MAKE_INITIAL_TIMESTAMP=$(stat -c %Y "main" 2>/dev/null || stat -f %m "main") +echo "Timestamp initial de l'exécutable make: $(date -r main)" | tee -a "../$LOG_FILE" + # Modification du timestamp du fichier main.c pour le mettre dans le futur (+ 1 jour) -FUTURE_TIMESTAMP=$(date -d "+1 day" "+%Y%m%d%H%M.%S" 2>/dev/null || date -v+1d "+%Y%m%d%H%M.%S") +# Utilisation de la syntaxe compatible avec macOS +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + FUTURE_TIMESTAMP=$(date -v+1d "+%Y%m%d%H%M.%S") +else + # Linux et autres + FUTURE_TIMESTAMP=$(date -d "+1 day" "+%Y%m%d%H%M.%S") +fi touch -t $FUTURE_TIMESTAMP main.c echo "Timestamp du fichier source main.c dans make modifié vers le futur: $(date -r main.c)" | tee -a "../$LOG_FILE" cd .. @@ -47,6 +59,10 @@ cd bake echo "Compilation initiale avec bake..." | tee -a "../$LOG_FILE" java -cp bakefile.jar fr.monlouyan.bakefile.Main 2>&1 | tee -a "../$LOG_FILE" +# Enregistrons le timestamp de l'exécutable bake après compilation initiale +BAKE_INITIAL_TIMESTAMP=$(stat -c %Y "main" 2>/dev/null || stat -f %m "main") +echo "Timestamp initial de l'exécutable bake: $(date -r main)" | tee -a "../$LOG_FILE" + # Modification du timestamp du fichier main.c pour le mettre dans le futur (+ 1 jour) touch -t $FUTURE_TIMESTAMP main.c echo "Timestamp du fichier source main.c dans bake modifié vers le futur: $(date -r main.c)" | tee -a "../$LOG_FILE" @@ -59,23 +75,26 @@ echo "=================================" | tee -a "$LOG_FILE" echo "Exécution de MAKE avec un fichier source ayant un timestamp dans le futur:" | tee -a "$LOG_FILE" echo "=================================" | tee -a "$LOG_FILE" cd make -# Enregistrer le timestamp actuel de l'exécutable -MAKE_EXEC_TIME_BEFORE=$(stat -c %Y "main" 2>/dev/null || stat -f %m "main") +# On va aussi enregistrer la sortie de make dans un fichier temporaire +# pour détecter si gcc a été exécuté +MAKE_OUTPUT_FILE=$(mktemp) echo "$ make" | tee -a "../$LOG_FILE" -make 2>&1 | tee -a "../$LOG_FILE" +make 2>&1 | tee "$MAKE_OUTPUT_FILE" | tee -a "../$LOG_FILE" MAKE_EXIT_CODE=$? echo "" | tee -a "../$LOG_FILE" echo "Code de sortie: $MAKE_EXIT_CODE" | tee -a "../$LOG_FILE" # Vérifier si l'exécutable a été reconstruit +# On peut aussi vérifier si "gcc" apparaît dans la sortie de make MAKE_EXEC_TIME_AFTER=$(stat -c %Y "main" 2>/dev/null || stat -f %m "main") -if [ "$MAKE_EXEC_TIME_AFTER" -ne "$MAKE_EXEC_TIME_BEFORE" ]; then +if grep -q "gcc -o main" "$MAKE_OUTPUT_FILE" || [ "$MAKE_EXEC_TIME_AFTER" -ne "$MAKE_INITIAL_TIMESTAMP" ]; then echo "✅ make: L'exécutable a été reconstruit (avec un timestamp source dans le futur)" | tee -a "../$LOG_FILE" echo "Nouveau timestamp de l'exécutable: $(date -r main)" | tee -a "../$LOG_FILE" else echo "❌ make: L'exécutable n'a PAS été reconstruit malgré un timestamp source dans le futur" | tee -a "../$LOG_FILE" fi +rm -f "$MAKE_OUTPUT_FILE" cd .. echo "" | tee -a "$LOG_FILE" @@ -85,40 +104,31 @@ echo "=================================" | tee -a "$LOG_FILE" echo "Exécution de BAKE avec un fichier source ayant un timestamp dans le futur:" | tee -a "$LOG_FILE" echo "=================================" | tee -a "$LOG_FILE" cd bake -# Enregistrer le timestamp actuel de l'exécutable -BAKE_EXEC_TIME_BEFORE=$(stat -c %Y "main" 2>/dev/null || stat -f %m "main") +# On va aussi enregistrer la sortie de bake dans un fichier temporaire +# pour détecter si gcc a été exécuté +BAKE_OUTPUT_FILE=$(mktemp) echo "$ java -cp bakefile.jar fr.monlouyan.bakefile.Main" | tee -a "../$LOG_FILE" -java -cp bakefile.jar fr.monlouyan.bakefile.Main 2>&1 | tee -a "../$LOG_FILE" +java -cp bakefile.jar fr.monlouyan.bakefile.Main 2>&1 | tee "$BAKE_OUTPUT_FILE" | tee -a "../$LOG_FILE" BAKE_EXIT_CODE=$? echo "" | tee -a "../$LOG_FILE" echo "Code de sortie: $BAKE_EXIT_CODE" | tee -a "../$LOG_FILE" # Vérifier si l'exécutable a été reconstruit +# On peut aussi vérifier si "gcc" apparaît dans la sortie de bake BAKE_EXEC_TIME_AFTER=$(stat -c %Y "main" 2>/dev/null || stat -f %m "main") -if [ "$BAKE_EXEC_TIME_AFTER" -ne "$BAKE_EXEC_TIME_BEFORE" ]; then +if grep -q "gcc -o main" "$BAKE_OUTPUT_FILE" || [ "$BAKE_EXEC_TIME_AFTER" -ne "$BAKE_INITIAL_TIMESTAMP" ]; then echo "✅ bake: L'exécutable a été reconstruit (avec un timestamp source dans le futur)" | tee -a "../$LOG_FILE" echo "Nouveau timestamp de l'exécutable: $(date -r main)" | tee -a "../$LOG_FILE" else echo "❌ bake: L'exécutable n'a PAS été reconstruit malgré un timestamp source dans le futur" | tee -a "../$LOG_FILE" fi +rm -f "$BAKE_OUTPUT_FILE" cd .. -# Résumé des résultats -echo "" | tee -a "$LOG_FILE" -echo "=================================" | tee -a "$LOG_FILE" -echo "RÉSUMÉ:" | tee -a "$LOG_FILE" -echo "=================================" | tee -a "$LOG_FILE" - -if [ "$MAKE_EXEC_TIME_AFTER" -ne "$MAKE_EXEC_TIME_BEFORE" ] && [ "$BAKE_EXEC_TIME_AFTER" -ne "$BAKE_EXEC_TIME_BEFORE" ]; then - echo "✅ Les deux outils ont reconstruit l'exécutable face à un timestamp source dans le futur" | tee -a "$LOG_FILE" -elif [ "$MAKE_EXEC_TIME_AFTER" -eq "$MAKE_EXEC_TIME_BEFORE" ] && [ "$BAKE_EXEC_TIME_AFTER" -eq "$BAKE_EXEC_TIME_BEFORE" ]; then - echo "❌ Les deux outils n'ont pas reconstruit l'exécutable malgré un timestamp source dans le futur" | tee -a "$LOG_FILE" -else - echo "⚠️ Comportement différent entre make et bake:" | tee -a "$LOG_FILE" - echo " - make: $([ "$MAKE_EXEC_TIME_AFTER" -ne "$MAKE_EXEC_TIME_BEFORE" ] && echo '✅ A reconstruit' || echo '❌ N'"'"'a pas reconstruit')" | tee -a "$LOG_FILE" - echo " - bake: $([ "$BAKE_EXEC_TIME_AFTER" -ne "$BAKE_EXEC_TIME_BEFORE" ] && echo '✅ A reconstruit' || echo '❌ N'"'"'a pas reconstruit')" | tee -a "$LOG_FILE" -fi +# Vérification plus fiable du comportement de make et bake +MAKE_REBUILT=$(grep -q "gcc -o main" "$MAKE_OUTPUT_FILE" 2>/dev/null || [ "$MAKE_EXEC_TIME_AFTER" -ne "$MAKE_INITIAL_TIMESTAMP" ] && echo "true" || echo "false") +BAKE_REBUILT=$(grep -q "gcc -o main" "$BAKE_OUTPUT_FILE" 2>/dev/null || [ "$BAKE_EXEC_TIME_AFTER" -ne "$BAKE_INITIAL_TIMESTAMP" ] && echo "true" || echo "false") echo "" | tee -a "$LOG_FILE" echo "Test terminé. Résultats enregistrés dans: $LOG_FILE" | tee -a "$LOG_FILE" \ No newline at end of file