2025-02-04 10:18:26 +01:00
|
|
|
package fr.monlouyan.bakefile;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Paths;
|
2025-02-04 16:27:07 +01:00
|
|
|
import java.util.*;
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
2025-02-04 10:18:26 +01:00
|
|
|
|
|
|
|
public class BakefileParser {
|
2025-02-04 19:27:56 +01:00
|
|
|
/**
|
|
|
|
* Nom du fichier Bakefile à parser (donc Bakefile...).
|
|
|
|
*/
|
2025-02-04 10:18:26 +01:00
|
|
|
private String filename;
|
|
|
|
|
2025-02-04 16:27:07 +01:00
|
|
|
/**
|
|
|
|
* Regex pour détecter les targets et leurs dépendances.
|
|
|
|
* Format : "nom : dépendance1 dépendance2"
|
|
|
|
*/
|
|
|
|
private static final Pattern TARGET_PATTERN = Pattern.compile("^(\\S+)\\s*:\\s*(.*)$");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Regex pour détecter les lignes de commande associées à une target.
|
|
|
|
* Format : " gcc -o program program.c" (ligne indentée)
|
|
|
|
*/
|
2025-02-08 19:23:39 +01:00
|
|
|
private static final Pattern COMMAND_PATTERN = Pattern.compile("^\\t(.+)$");
|
2025-02-04 16:27:07 +01:00
|
|
|
|
2025-02-04 19:58:04 +01:00
|
|
|
/**
|
|
|
|
* Regex pour détecter les définitions de variables.
|
|
|
|
* Format : "FLAGS = -ansi -pedantic"
|
|
|
|
*/
|
|
|
|
private static final Pattern VARIABLE_PATTERN = Pattern.compile("^(\\w+)\\s*=\\s*(.*)$");
|
|
|
|
|
2025-02-04 19:27:56 +01:00
|
|
|
/**
|
|
|
|
* Première cible trouvée dans le fichier Bakefile.
|
|
|
|
*/
|
|
|
|
private static String firstTarget;
|
2025-02-04 19:58:04 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Stocke les variables définies dans le Bakefile.
|
|
|
|
*/
|
|
|
|
private Map<String, String> variables = new HashMap<>();
|
2025-02-04 19:27:56 +01:00
|
|
|
|
|
|
|
|
2025-02-04 10:18:26 +01:00
|
|
|
public BakefileParser(String filename) {
|
|
|
|
this.filename = filename;
|
2025-02-04 19:27:56 +01:00
|
|
|
firstTarget = null;
|
2025-02-04 10:18:26 +01:00
|
|
|
}
|
2025-02-04 16:27:07 +01:00
|
|
|
|
2025-02-04 17:31:25 +01:00
|
|
|
public List<Rule> parse() {
|
|
|
|
List<Rule> rules = new ArrayList<>();
|
|
|
|
Set<String> phonyTargets = new HashSet<>();
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 10:34:06 +01:00
|
|
|
if (!Files.exists(Paths.get(filename))) {
|
|
|
|
System.out.println("*** No targets specified and no makefile found. Stop.");
|
|
|
|
System.exit(1);
|
2025-02-06 20:49:12 +01:00
|
|
|
}
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 10:18:26 +01:00
|
|
|
try {
|
|
|
|
List<String> lines = Files.readAllLines(Paths.get(filename));
|
2025-02-04 16:27:07 +01:00
|
|
|
String currentTarget = null;
|
|
|
|
List<String> dependencies = new ArrayList<>();
|
|
|
|
List<String> commands = new ArrayList<>();
|
2025-02-08 19:23:39 +01:00
|
|
|
int lineNumber = 0;
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 10:18:26 +01:00
|
|
|
for (String line : lines) {
|
2025-02-08 19:23:39 +01:00
|
|
|
lineNumber++;
|
|
|
|
|
2025-02-04 19:58:04 +01:00
|
|
|
Matcher varMatcher = VARIABLE_PATTERN.matcher(line);
|
2025-02-04 16:27:07 +01:00
|
|
|
Matcher targetMatcher = TARGET_PATTERN.matcher(line);
|
|
|
|
Matcher commandMatcher = COMMAND_PATTERN.matcher(line);
|
2025-02-08 19:23:39 +01:00
|
|
|
|
|
|
|
if (line.trim().isEmpty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (line.matches("^ +.*$")) { // Détecte les lignes commençant par des espaces
|
|
|
|
System.err.println(filename + ":" + lineNumber + ": *** missing separator. Stop.");
|
|
|
|
System.exit(1);
|
|
|
|
}
|
|
|
|
|
2025-02-04 19:58:04 +01:00
|
|
|
if (varMatcher.matches()) {
|
|
|
|
variables.put(varMatcher.group(1), varMatcher.group(2));
|
|
|
|
} else if (targetMatcher.matches()) {
|
2025-02-04 16:27:07 +01:00
|
|
|
if (currentTarget != null) {
|
2025-02-08 20:42:11 +01:00
|
|
|
rules.add(new Rule(
|
|
|
|
replaceVariables(currentTarget),
|
|
|
|
replaceVariablesInList(dependencies),
|
|
|
|
replaceVariablesInList(commands),
|
|
|
|
phonyTargets.contains(currentTarget)
|
|
|
|
));
|
2025-02-04 16:27:07 +01:00
|
|
|
}
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 16:27:07 +01:00
|
|
|
currentTarget = targetMatcher.group(1);
|
2025-02-06 12:31:07 +01:00
|
|
|
String depStr = targetMatcher.group(2).trim();
|
|
|
|
dependencies = depStr.isEmpty() ? new ArrayList<>() : new ArrayList<>(Arrays.asList(depStr.split("\\s+")));
|
2025-02-08 20:42:11 +01:00
|
|
|
|
|
|
|
if (firstTarget == null) {
|
|
|
|
firstTarget = replaceVariables(currentTarget);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-06 12:31:07 +01:00
|
|
|
if (currentTarget.equals("clean")) {
|
|
|
|
phonyTargets.add(currentTarget);
|
|
|
|
}
|
|
|
|
|
2025-02-04 16:27:07 +01:00
|
|
|
commands = new ArrayList<>();
|
|
|
|
} else if (commandMatcher.matches()) {
|
|
|
|
commands.add(commandMatcher.group(1));
|
2025-02-04 10:18:26 +01:00
|
|
|
}
|
|
|
|
}
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 16:27:07 +01:00
|
|
|
if (currentTarget != null) {
|
2025-02-08 20:42:11 +01:00
|
|
|
rules.add(new Rule(
|
|
|
|
replaceVariables(currentTarget),
|
|
|
|
replaceVariablesInList(dependencies),
|
|
|
|
replaceVariablesInList(commands),
|
|
|
|
phonyTargets.contains(currentTarget)
|
|
|
|
));
|
2025-02-04 16:27:07 +01:00
|
|
|
}
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 10:18:26 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2025-02-04 17:31:25 +01:00
|
|
|
return rules;
|
2025-02-04 16:27:07 +01:00
|
|
|
}
|
2025-02-04 19:27:56 +01:00
|
|
|
|
2025-02-08 20:42:11 +01:00
|
|
|
private String replaceVariables(String input) {
|
|
|
|
if (input == null) return null;
|
|
|
|
String result = input;
|
|
|
|
boolean replaced;
|
|
|
|
do {
|
|
|
|
replaced = false;
|
|
|
|
for (Map.Entry<String, String> entry : variables.entrySet()) {
|
|
|
|
String key = "$(" + entry.getKey() + ")";
|
|
|
|
if (result.contains(key)) {
|
|
|
|
result = result.replace(key, entry.getValue());
|
|
|
|
replaced = true;
|
2025-02-04 19:58:04 +01:00
|
|
|
}
|
2025-02-08 20:42:11 +01:00
|
|
|
}
|
|
|
|
} while (replaced);
|
|
|
|
return result;
|
2025-02-04 19:58:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-08 20:42:11 +01:00
|
|
|
private List<String> replaceVariablesInList(List<String> items) {
|
|
|
|
List<String> resolved = new ArrayList<>();
|
|
|
|
for (String item : items) {
|
|
|
|
resolved.add(replaceVariables(item));
|
|
|
|
}
|
|
|
|
return resolved;
|
|
|
|
}
|
|
|
|
|
2025-02-04 19:58:04 +01:00
|
|
|
|
2025-02-04 19:27:56 +01:00
|
|
|
/**
|
|
|
|
* 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; }
|
2025-02-04 16:27:07 +01:00
|
|
|
}
|