From 79f285d0202d3907d0d88f6104a4246677e24510 Mon Sep 17 00:00:00 2001
From: Moncef STITI <moncef.stiti@etu.u-pec.fr>
Date: Sat, 8 Feb 2025 23:15:07 +0100
Subject: [PATCH] =?UTF-8?q?Ajout=20de=20la=20m=C3=A9thode=20getRule=20dans?=
 =?UTF-8?q?=20BakeEngine=20et=20am=C3=A9lioration=20de=20la=20gestion=20de?=
 =?UTF-8?q?s=20d=C3=A9pendances=20dans=20la=20classe=20Rule?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/fr/monlouyan/bakefile/BakeEngine.java     |   7 +-
 src/fr/monlouyan/bakefile/BakefileParser.java |  71 +++++++-----
 src/fr/monlouyan/bakefile/Rule.java           | 101 +++++++-----------
 3 files changed, 84 insertions(+), 95 deletions(-)

diff --git a/src/fr/monlouyan/bakefile/BakeEngine.java b/src/fr/monlouyan/bakefile/BakeEngine.java
index be1f436..a536064 100644
--- a/src/fr/monlouyan/bakefile/BakeEngine.java
+++ b/src/fr/monlouyan/bakefile/BakeEngine.java
@@ -17,9 +17,12 @@ public class BakeEngine {
     }
 
     public static boolean hasRule(String target) {
-        // Vérifie si une règle existe pour créer cette cible
         return ruleMap.containsKey(target);
     }
+
+    public static Rule getRule(String target) {
+        return ruleMap.get(target);
+    }
     
     public void run() {
         List<Rule> rules = parser.parse();
@@ -35,4 +38,4 @@ public class BakeEngine {
             executor.execute(rule);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/fr/monlouyan/bakefile/BakefileParser.java b/src/fr/monlouyan/bakefile/BakefileParser.java
index 7d1a0ac..3177d7a 100644
--- a/src/fr/monlouyan/bakefile/BakefileParser.java
+++ b/src/fr/monlouyan/bakefile/BakefileParser.java
@@ -32,6 +32,12 @@ public class BakefileParser {
      */
     private static final Pattern VARIABLE_PATTERN = Pattern.compile("^(\\w+)\\s*=\\s*([^#]*?)\\s*(?:#.*)?$");
 
+    /**
+     * Regex pour détecter les déclarations .PHONY
+     * Format : ".PHONY: clean all"
+     */
+    private static final Pattern PHONY_PATTERN = Pattern.compile("^\\.PHONY:\\s*([^#]*?)\\s*(?:#.*)?$");
+
     /**
      * Première cible trouvée dans le fichier Bakefile.
      */
@@ -42,13 +48,12 @@ public class BakefileParser {
      */
     private Map<String, String> variables = new HashMap<>();
     
-
     public BakefileParser(String filename) {
         this.filename = filename;
         firstTarget = null;
     }
 
-	private List<String> splitDependencies(String depStr) {
+    private List<String> splitDependencies(String depStr) {
         if (depStr == null || depStr.trim().isEmpty()) {
             return new ArrayList<>();
         }
@@ -63,7 +68,6 @@ public class BakefileParser {
                     .collect(Collectors.toList());
     }
 
-
     public List<Rule> parse() {
         List<Rule> rules = new ArrayList<>();
         Set<String> phonyTargets = new HashSet<>();
@@ -86,6 +90,7 @@ public class BakefileParser {
                 Matcher varMatcher = VARIABLE_PATTERN.matcher(line);
                 Matcher targetMatcher = TARGET_PATTERN.matcher(line);
                 Matcher commandMatcher = COMMAND_PATTERN.matcher(line);
+                Matcher phonyMatcher = PHONY_PATTERN.matcher(line);
 
                 if (line.trim().isEmpty()) {
                     continue;
@@ -96,32 +101,35 @@ public class BakefileParser {
                     System.exit(1);
                 }
                 
+                if (phonyMatcher.matches()) {
+                    // Ajouter les cibles .PHONY à l'ensemble des cibles phony
+                    String[] phonies = phonyMatcher.group(1).trim().split("\\s+");
+                    Collections.addAll(phonyTargets, phonies);
+                    continue;
+                }
+                
                 if (varMatcher.matches()) {
-                    // Stocke la variable en enlevant les espaces en début et fin
                     variables.put(varMatcher.group(1), varMatcher.group(2).trim());
                 } else if (targetMatcher.matches()) {
                     if (currentTarget != null) {
+                        String resolvedTarget = replaceVariables(currentTarget.trim());
                         rules.add(new Rule(
-							replaceVariables(currentTarget.trim()),
-							splitDependencies(dependencies.stream()
-								.collect(Collectors.joining(" "))),
-							replaceVariablesInList(commands),
-							phonyTargets.contains(currentTarget.trim())
-						));
+                            resolvedTarget,
+                            splitDependencies(dependencies.stream()
+                                .collect(Collectors.joining(" "))),
+                            replaceVariablesInList(commands),
+                            phonyTargets.contains(resolvedTarget)
+                        ));
+                        
+                        // Ne définir firstTarget que si ce n'est pas une cible .PHONY
+                        if (firstTarget == null && !phonyTargets.contains(resolvedTarget)) {
+                            firstTarget = resolvedTarget;
+                        }
                     }
     
                     currentTarget = targetMatcher.group(1);
-					String depStr = targetMatcher.group(2);
-					dependencies = splitDependencies(depStr);
-
-					if (firstTarget == null) {
-						firstTarget = replaceVariables(currentTarget.trim());
-					}
-
-                    if (currentTarget.equals("clean")) {
-                        phonyTargets.add(currentTarget);
-                    }
-                    
+                    String depStr = targetMatcher.group(2);
+                    dependencies = splitDependencies(depStr);
                     commands = new ArrayList<>();
                 } else if (commandMatcher.matches()) {
                     commands.add(commandMatcher.group(1));
@@ -129,12 +137,18 @@ public class BakefileParser {
             }
     
             if (currentTarget != null) {
+                String resolvedTarget = replaceVariables(currentTarget.trim());
                 rules.add(new Rule(
-                    replaceVariables(currentTarget.trim()),
+                    resolvedTarget,
                     replaceVariablesInList(dependencies),
                     replaceVariablesInList(commands),
-                    phonyTargets.contains(currentTarget.trim())
+                    phonyTargets.contains(resolvedTarget)
                 ));
+                
+                // Vérifier une dernière fois pour firstTarget
+                if (firstTarget == null && !phonyTargets.contains(resolvedTarget)) {
+                    firstTarget = resolvedTarget;
+                }
             }
     
         } catch (IOException e) {
@@ -143,7 +157,7 @@ public class BakefileParser {
         return rules;
     }
 
-	private String replaceVariables(String input) {
+    private String replaceVariables(String input) {
         if (input == null) return null;
         String result = input;
         boolean replaced;
@@ -161,7 +175,7 @@ public class BakefileParser {
         return result.trim();
     }
 
-	private List<String> replaceVariablesInList(List<String> items) {
+    private List<String> replaceVariablesInList(List<String> items) {
         List<String> resolved = new ArrayList<>();
         for (String item : items) {
             resolved.add(replaceVariables(item));
@@ -169,10 +183,11 @@ public class BakefileParser {
         return resolved;
     }
 
-
     /**
      * Permet de récupérer la première cible trouvée dans le fichier Bakefile.
      * @return La première cible trouvée
      */
-    public static String getFirstTarget() { return firstTarget; }
-}
+    public static String getFirstTarget() { 
+        return firstTarget; 
+    }
+}
\ No newline at end of file
diff --git a/src/fr/monlouyan/bakefile/Rule.java b/src/fr/monlouyan/bakefile/Rule.java
index 84ebae2..7a7d2ab 100644
--- a/src/fr/monlouyan/bakefile/Rule.java
+++ b/src/fr/monlouyan/bakefile/Rule.java
@@ -3,42 +3,12 @@ package fr.monlouyan.bakefile;
 import java.io.File;
 import java.util.List;
 
-/**
- * Représente une règle dans un fichier Bakefile
- * Dernière modification : 04/02/2025
- * 
- * @author Moncef STITI, Yanis HAMOUDI
- * @version 1.0
- * @date 04/02/2025
- */
 public class Rule {
-    /**
-     * Nom de la règle
-     */
     private String name;
-
-    /**
-     * Liste des dépendances de la règle
-     */
     private List<String> dependencies;
-
-    /**
-     * Liste des commandes de la règle (actions à exécuter)
-     */
     private List<String> commands;
-
-    /**
-     * Indique si la règle est une règle phony
-     */
     private boolean isPhony;
 
-    /**
-     * Constructeur de la classe Rule
-     * @param name Nom de la règle
-     * @param dependencies Liste des dépendances de la règle
-     * @param commands Liste des commandes de la règle à exécuter
-     * @param isPhony Si la règle est une règle phony ou non
-     */
     public Rule(String name, List<String> dependencies, List<String> commands, boolean isPhony) {
         this.name = name;
         this.dependencies = dependencies;
@@ -46,60 +16,50 @@ public class Rule {
         this.isPhony = isPhony;
     }
 
-    /**
-     * Permet de récupérer le nom de la règle
-     * @return Le nom de la règle
-     */
     public String getName() { return name; }
-
-    /**
-     * Permet de récupérer les dépendances de la règle
-     * @return La liste des dépendances de la règle
-     */
     public List<String> getDependencies() { return dependencies; }
-
-    /**
-     * Permet de récupérer les commandes de la règle
-     * @return La liste des commandes de la règle
-     */
     public List<String> getCommands() { return commands; }
-
-    /**
-     * Permet de savoir si la règle est une règle phony
-     * @return true si la règle est une règle phony, false sinon
-     */
     public boolean isPhony() { return isPhony; }
-
-    /**
-     * Permet de savoir si la règle est vide (sans dépendances ni commandes)
-     * @return true si la règle est vide, false sinon
-     */
     public boolean isEmpty() { return dependencies.isEmpty() && commands.isEmpty(); }
 
-    /**
-     * Détermine si la règle doit être mise à jour.
-     * Une règle doit être mise à jour si l'un de ses fichiers de sortie est plus ancien qu'un de ses fichiers de dépendance.
-     * De plus, les règles phony sont toujours mises à jour.
-     * 
-     * @return true si la règle doit être mise à jour, false sinon.
-     */
     public boolean needsUpdate() {
         if (BakeCLI.isDebug()){
             System.out.println("Debug : Checking if rule " + name + " needs update");
         }
 
+        // Les règles phony sont toujours mises à jour
         if (isPhony) {
             if (BakeCLI.isDebug()) {
                 System.out.println("Debug : Rule " + name + " is phony, always needs update");
             }
-            return true; // Les règles phony sont toujours mises à jour
+            return true;
         }
-    
-        File targetFile = new File(name);
 
+        // Si la règle n'a pas de commandes, on vérifie seulement si une dépendance doit être mise à jour
+        if (commands.isEmpty()) {
+            for (String dependency : dependencies) {
+                File depFile = new File(dependency);
+                if (!depFile.exists() && !dependency.isEmpty() && !BakeEngine.hasRule(dependency)) {
+                    System.out.println("bake: *** No rule to make target '" + dependency + "', needed by '" + name + "'.  Stop.");
+                    System.exit(1);
+                }
+                Rule depRule = BakeEngine.getRule(dependency);
+                if (depRule != null && depRule.needsUpdate()) {
+                    if (BakeCLI.isDebug()) {
+                        System.out.println("Debug : Dependency rule " + dependency + " needs update");
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // Pour les règles avec des commandes, on vérifie aussi les timestamps
+        File targetFile = new File(name);
         if (BakeCLI.isDebug()){
             System.out.println("Debug : Checking if target file " + name + " exist and is up to date");
         }
+
         long targetTimestamp = targetFile.exists() ? TimestampManager.getTimestamp(targetFile) : 0;
         
         if (BakeCLI.isDebug()) {
@@ -114,6 +74,7 @@ public class Rule {
                 System.out.println("Debug : Dependency '" + dependency + "' last modified at " + TimestampManager.formatTimestamp(depTimestamp));
             }
 
+            // Vérifier si une règle existe pour cette dépendance
             boolean hasRule = BakeEngine.hasRule(dependency);
 
             if (!depFile.exists() && !dependency.isEmpty() && !hasRule) {
@@ -121,6 +82,16 @@ public class Rule {
                 System.exit(1);
             }
 
+            // Si la dépendance est une règle et qu'elle a besoin d'être mise à jour
+            Rule depRule = BakeEngine.getRule(dependency);
+            if (depRule != null && depRule.needsUpdate()) {
+                if (BakeCLI.isDebug()) {
+                    System.out.println("Debug : Dependency rule " + dependency + " needs update");
+                }
+                return true;
+            }
+
+            // Vérifier les timestamps seulement si le fichier existe
             if (depFile.exists() && TimestampManager.getTimestamp(depFile) > targetTimestamp) {
                 if (BakeCLI.isDebug()) {
                     System.out.println("Debug : Dependency " + dependency + " is newer than target file " + name + ", needs update");
@@ -130,4 +101,4 @@ public class Rule {
         }
         return false;
     }
-}
+}
\ No newline at end of file