diff --git a/bakefile.jar b/bakefile.jar new file mode 100644 index 0000000..a268f9a Binary files /dev/null and b/bakefile.jar differ diff --git a/src/fr/monlouyan/bakefile/BakeCLI.java b/src/fr/monlouyan/bakefile/BakeCLI.java index e3462ee..34d1981 100644 --- a/src/fr/monlouyan/bakefile/BakeCLI.java +++ b/src/fr/monlouyan/bakefile/BakeCLI.java @@ -15,7 +15,7 @@ public class BakeCLI { /* * Mode debug activé ou non */ - private boolean debug; + private static boolean debug; /* * Liste des arguments passés en ligne de commande @@ -30,7 +30,7 @@ public class BakeCLI { * @see Main */ public BakeCLI(String[] args){ - this.debug = false; + debug = false; this.targets = new ArrayList<>(); parseArgs(args); } @@ -54,7 +54,7 @@ public class BakeCLI { * Permet de savoir si le mode debug est activé ou non. * @return true si le mode debug est activé, false sinon */ - public boolean isDebug(){ return debug; } + public static boolean isDebug(){ return debug; } /** * Permet de récupérer les arguments autres que "-d" passés en ligne de commande diff --git a/src/fr/monlouyan/bakefile/BakeEngine.java b/src/fr/monlouyan/bakefile/BakeEngine.java index 7751a62..574058b 100644 --- a/src/fr/monlouyan/bakefile/BakeEngine.java +++ b/src/fr/monlouyan/bakefile/BakeEngine.java @@ -2,7 +2,6 @@ package fr.monlouyan.bakefile; import java.util.List; - public class BakeEngine { private BakeCLI cli; private BakefileParser parser; @@ -12,15 +11,16 @@ public class BakeEngine { public BakeEngine(BakeCLI cli) { this.cli = cli; this.parser = new BakefileParser("Bakefile"); - this.resolver = new DependencyResolver(cli.isDebug()); - this.executor = new CommandExecutor(cli.isDebug()); + this.resolver = new DependencyResolver(BakeCLI.isDebug()); + this.executor = new CommandExecutor(BakeCLI.isDebug()); } public void run() { - List<Target> targets = parser.parse(); - List<Target> targetsToBuild = resolver.resolve(targets, cli.getTargets()); - for (Target target : targetsToBuild) { - executor.execute(target); + List<Rule> rules = parser.parse(); + List<Rule> rulesToBuild = resolver.resolve(rules, cli.getTargets()); + + for (Rule rule : rulesToBuild) { + 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 07cae52..6202da4 100644 --- a/src/fr/monlouyan/bakefile/BakefileParser.java +++ b/src/fr/monlouyan/bakefile/BakefileParser.java @@ -26,8 +26,10 @@ public class BakefileParser { this.filename = filename; } - public List<Target> parse() { - List<Target> targets = new ArrayList<>(); + public List<Rule> parse() { + List<Rule> rules = new ArrayList<>(); + Set<String> phonyTargets = new HashSet<>(); + if (!Files.exists(Paths.get(filename))) { System.out.println("*** No targets specified and no makefile found. Stop."); System.exit(1); @@ -44,30 +46,29 @@ public class BakefileParser { Matcher commandMatcher = COMMAND_PATTERN.matcher(line); if (targetMatcher.matches()) { - // Sauvegarde de la précédente target si elle existe + // Sauvegarde de la règle précédente si elle existe if (currentTarget != null) { - targets.add(new Target(currentTarget, dependencies, String.join(" && ", commands))); + rules.add(new Rule(currentTarget, dependencies, commands, phonyTargets.contains(currentTarget))); } - // Nouvelle target détectée + // Nouvelle cible détectée currentTarget = targetMatcher.group(1); dependencies = new ArrayList<>(Arrays.asList(targetMatcher.group(2).trim().split("\\s+"))); commands = new ArrayList<>(); - } else if (commandMatcher.matches()) { - // Ligne de commande associée à la dernière target trouvée + // Ligne de commande associée à la dernière cible trouvée commands.add(commandMatcher.group(1)); } } - // Ajout de la dernière target après la boucle + // Ajout de la dernière règle après la boucle if (currentTarget != null) { - targets.add(new Target(currentTarget, dependencies, String.join(" && ", commands))); + rules.add(new Rule(currentTarget, dependencies, commands, phonyTargets.contains(currentTarget))); } } catch (IOException e) { e.printStackTrace(); } - return targets; + return rules; } } diff --git a/src/fr/monlouyan/bakefile/CommandExecutor.java b/src/fr/monlouyan/bakefile/CommandExecutor.java index dd37dfc..786ecd3 100644 --- a/src/fr/monlouyan/bakefile/CommandExecutor.java +++ b/src/fr/monlouyan/bakefile/CommandExecutor.java @@ -9,20 +9,31 @@ public class CommandExecutor { this.debug = debug; } - public void execute(Target target) { - if (!target.needsUpdate()){ - System.out.println("bake: '" + target.getName() + "' is up to date."); + public void execute(Rule rule) { + if (rule.getCommands().isEmpty()) { + System.out.println("bake: Nothing to be done for '" + rule.getName() + "'."); return; - }; + } + + if (!rule.needsUpdate()){ + System.out.println("bake: '" + rule.getName() + "' is up to date."); + return; + } + try { - System.out.println(target.getCommand()); - ProcessBuilder pb = new ProcessBuilder("sh", "-c", target.getCommand()); - Process process = pb.start(); - int exitCode = process.waitFor(); - if (debug) System.out.println("Executed: " + target.getCommand() + " with exit code " + exitCode); - if (exitCode != 0) System.err.println("Error executing " + target.getName()); + for (String command : rule.getCommands()) { + System.out.println(command); // Affichage de la commande executée + ProcessBuilder pb = new ProcessBuilder("sh", "-c", command); + Process process = pb.start(); + int exitCode = process.waitFor(); + if (debug) System.out.println("Executed: " + command + " with exit code " + exitCode); + if (exitCode != 0) { + System.err.println("Error executing " + rule.getName() + ""); + break; + } + } } catch (IOException | InterruptedException e) { e.printStackTrace(); } } -} \ No newline at end of file +} diff --git a/src/fr/monlouyan/bakefile/DependencyResolver.java b/src/fr/monlouyan/bakefile/DependencyResolver.java index 133d492..8ca21fc 100644 --- a/src/fr/monlouyan/bakefile/DependencyResolver.java +++ b/src/fr/monlouyan/bakefile/DependencyResolver.java @@ -10,16 +10,17 @@ public class DependencyResolver { this.debug = debug; } - public List<Target> resolve(List<Target> allTargets, List<String> requestedTargets) { - List<Target> targetsToBuild = new ArrayList<>(); - for (Target target : allTargets) { - if (requestedTargets.isEmpty() || requestedTargets.contains(target.getName())) { + public List<Rule> resolve(List<Rule> allRules, List<String> requestedRules) { + List<Rule> rulesToBuild = new ArrayList<>(); + + for (Rule rule : allRules) { + if (requestedRules.isEmpty() || requestedRules.contains(rule.getName()) || rule.isPhony()) { if (debug){ - System.out.println("Target " + target.getName() + " is requested"); + System.out.println("Rule " + rule.getName() + " is requested"); } - targetsToBuild.add(target); + rulesToBuild.add(rule); } } - return targetsToBuild; + return rulesToBuild; } } \ No newline at end of file diff --git a/src/fr/monlouyan/bakefile/Rule.java b/src/fr/monlouyan/bakefile/Rule.java new file mode 100644 index 0000000..c15ab74 --- /dev/null +++ b/src/fr/monlouyan/bakefile/Rule.java @@ -0,0 +1,116 @@ +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; + this.commands = commands; + 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"); + } + + 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 + } + + 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; + + for (String dependency : dependencies) { + File depFile = new File(dependency); + if (depFile.exists() && TimestampManager.getTimestamp(depFile) > targetTimestamp) { + if (BakeCLI.isDebug()) { + System.out.println("Debug : Dependency " + dependency + " is newer than target file " + name + ", needs update"); + } + return true; + } + } + return false; + } +} diff --git a/src/fr/monlouyan/bakefile/Target.java b/src/fr/monlouyan/bakefile/Target.java deleted file mode 100644 index 29b32ca..0000000 --- a/src/fr/monlouyan/bakefile/Target.java +++ /dev/null @@ -1,30 +0,0 @@ -package fr.monlouyan.bakefile; - -import java.io.File; -import java.util.List; - -public class Target { - private String name; - private List<String> dependencies; - private String command; - - public Target(String name, List<String> dependencies, String command) { - this.name = name; - this.dependencies = dependencies; - this.command = command; - } - - public boolean needsUpdate() { - File targetFile = new File(name); - if (!targetFile.exists()) return true; - long lastModified = targetFile.lastModified(); - for (String dep : dependencies) { - File depFile = new File(dep); - if (depFile.exists() && depFile.lastModified() > lastModified) return true; - } - return false; - } - - public String getCommand() { return command; } - public String getName() { return name; } -} \ No newline at end of file diff --git a/src/fr/monlouyan/bakefile/TimestampManager.java b/src/fr/monlouyan/bakefile/TimestampManager.java new file mode 100644 index 0000000..fc37bda --- /dev/null +++ b/src/fr/monlouyan/bakefile/TimestampManager.java @@ -0,0 +1,39 @@ +package fr.monlouyan.bakefile; + +import java.io.File; + +/** + * Classe utilitaire pour la gestion des timestamps des fichiers. + * Dernière modification : 04/02/2025 + * + * @author Moncef STITI, Yanis HAMOUDI + * @version 1.0 + * @date 04/02/2025 + */ +public class TimestampManager { + + /** + * Récupère le timestamp d'un fichier. + * @param filePath Le chemin du fichier. + * @return Le timestamp du fichier, ou 0 si le fichier n'existe pas. + */ + public static long getTimestamp(File file) { + if (file.exists()) { + return file.lastModified(); // Récupère le timestamp du fichier + } + return 0; // Le fichier n'existe pas + } + + /** + * Compare deux fichiers en fonction de leurs timestamps de modification. + * + * @param file1 Premier fichier + * @param file2 Deuxième fichier + * @return 1 si file1 est plus récent, -1 si file2 est plus récent, 0 s'ils ont le même timestamp + */ + public static int compareTimestamps(File file1, File file2) { + long time1 = getTimestamp(file1); + long time2 = getTimestamp(file2); + return Long.compare(time1, time2); + } +} diff --git a/tests/bakefile.jar b/tests/bakefile.jar new file mode 100644 index 0000000..a268f9a Binary files /dev/null and b/tests/bakefile.jar differ diff --git a/tests/test-01-depuis-rien/bakefile.jar b/tests/test-01-depuis-rien/bakefile.jar new file mode 100644 index 0000000..a268f9a Binary files /dev/null and b/tests/test-01-depuis-rien/bakefile.jar differ diff --git a/tests/test-02-existe-deja/bakefile.jar b/tests/test-02-existe-deja/bakefile.jar new file mode 100644 index 0000000..a268f9a Binary files /dev/null and b/tests/test-02-existe-deja/bakefile.jar differ diff --git a/tests/test-03-circulaire/Bakefile b/tests/test-03-circulaire/Bakefile index 369b433..b458a3d 100644 --- a/tests/test-03-circulaire/Bakefile +++ b/tests/test-03-circulaire/Bakefile @@ -1,14 +1,11 @@ -main: a.o b.o c.o - gcc a.o b.o c.o -o main +main: a b c + gcc a b c -o main -a.o: a.c a.h b.h - gcc -Wall -Werror -Wextra -c a.c -o a.o +a: a.c a.h b.h + gcc -Wall -Werror -Wextra -Pendatic -c a.c -o a -b.o: b.c b.h c.h - gcc -Wall -Werror -Wextra -c b.c -o b.o +b: b.c b.h c.h + gcc -Wall -Werror -Wextra -Pendatic -c b.c -o b -c.o: c.c c.h a.h - gcc -Wall -Werror -Wextra -c c.c -o c.o - -clean: - rm -f a.o b.o c.o main +c: c.c c.h a.h + gcc -Wall -Werror -Wextra -Pendatic -c c.c -o c diff --git a/tests/test-03-circulaire/bakefile.jar b/tests/test-03-circulaire/bakefile.jar new file mode 100644 index 0000000..a268f9a Binary files /dev/null and b/tests/test-03-circulaire/bakefile.jar differ