/**
* Copyright (C) 2010 STMicroelectronics
*
* This file is part of "Mind Compiler" is free software: you can redistribute
* it and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Contact: mind@ow2.org
*
* Authors: Matthieu Leclercq
* Contributors:
*/
package org.ow2.mind.compilation;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.objectweb.fractal.adl.util.FractalADLLogManager;
import org.ow2.mind.ForceRegenContextHelper;
public final class DependencyHelper {
protected static Logger depLogger = FractalADLLogManager.getLogger("dep");
private DependencyHelper() {
}
public static boolean recompile(final File outputFile,
final File dependencyFile, final Map<Object, Object> context,
final File... additionalDependencies) {
if (context != null && ForceRegenContextHelper.getForceRegen(context))
return true;
if (!outputFile.exists()) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Output file '" + outputFile
+ "' does not exist, compile it.");
return true;
}
final long outputTimestamp = outputFile.lastModified();
if (outputTimestamp == 0) {
if (depLogger.isLoggable(Level.WARNING))
depLogger.warning("Unable to determine timestamp of file '"
+ outputFile + "', recompile.");
return true;
}
if (dependencyFile != null) {
if (!dependencyFile.exists()) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Dependency file of '" + outputFile
+ "' does not exist, recompile.");
return true;
}
final Map<File, List<File>> depMap = parseDepFile(dependencyFile);
if (depMap == null) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Error in dependency file of '" + outputFile
+ "', recompile.");
return true;
}
Collection<File> depFiles = depMap.get(outputFile);
// if depFiles is null (i.e. the dependencyFile is invalid), recompile.
if (depFiles == null) {
// try with absolute path
depFiles = depMap.get(outputFile.getAbsoluteFile());
if (depFiles == null) {
// try with single file name
depFiles = depMap.get(new File(outputFile.getName()));
if (depFiles == null) {
if (depLogger.isLoggable(Level.WARNING))
depLogger.warning("Invalid dependency file '" + dependencyFile
+ "'. Can't find rule for target '" + outputFile
+ "', recompile.");
return true;
}
}
}
for (final File depfile : depFiles) {
if (!depfile.exists()) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Missing input file '" + depfile + "'.");
} else if (depfile.lastModified() > outputTimestamp) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Input file '" + depfile
+ "' is more recent than output file '" + outputFile
+ "', recompile.");
return true;
}
}
}
if (additionalDependencies != null) {
for (final File depfile : additionalDependencies) {
if (!depfile.exists()) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Missing input file '" + depfile + "'.");
} else if (depfile.lastModified() > outputTimestamp) {
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Input file '" + depfile
+ "' is more recent than output file '" + outputFile
+ "', recompile.");
return true;
}
}
}
if (depLogger.isLoggable(Level.FINE))
depLogger.fine("Output file '" + outputFile
+ "' is up to date, do not recompile.");
return false;
}
public static void writeDepFile(final File depFile,
final Map<File, List<File>> deps) throws IOException {
final PrintStream ps = new PrintStream(depFile);
for (final Map.Entry<File, List<File>> dep : deps.entrySet()) {
ps.printf("%s : ", dep.getKey().getPath());
final Iterator<File> iter = dep.getValue().iterator();
while (iter.hasNext()) {
final File file = iter.next();
ps.print(file.getPath());
if (iter.hasNext()) {
ps.print(" \\\n ");
} else {
ps.print("\n\n");
}
}
}
ps.close();
}
/**
* The old parseDepFile method parsed the make rule as one line, with entries
* separated by the space character. This is the pure "make" rule, however
* it's an issue with folders containing spaces, since the GCC dependency file
* generation doesn't use quotation marks to differentiate the entries. The
* new parseDepFile was developed to split the rule with '\' + newline, We
* follow the convention as mentioned in GCC's documentation. @see
* {@link https ://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html}.
* Concerning the -M option: Unless specified explicitly (with -MT or -MQ),
* the object file name consists of the name of the source file with any
* suffix replaced with object file suffix and with any leading directory
* parts removed. If there are many included files then the rule is split into
* several lines using ‘\’-newline.
*
* @param depfile
* @return
*/
public static Map<File, List<File>> parseDepFile(final File depfile) {
final Map<File, List<File>> rules = new HashMap<File, List<File>>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(depfile));
String line = null;
List<String> ruleLines = new ArrayList<String>();
while ((line = reader.readLine()) != null) {
if (line.endsWith("\\")) {
line = line.substring(0, line.length() - 1);
if (line.length() > 0) ruleLines.add(line);
} else {
if (line.length() > 0) ruleLines.add(line);
// end of rule, start a new one
if (ruleLines.size() > 0) {
parseRule(ruleLines, rules);
ruleLines = new ArrayList<String>();
}
}
}
if (ruleLines.size() > 0) parseRule(ruleLines, rules);
} catch (final IOException e) {
depLogger.log(Level.WARNING, "An error occurs while reading \"" + depfile
+ "\".", e);
return null;
} finally {
if (reader != null) try {
reader.close();
} catch (final IOException e) {
// ignore
}
}
return rules;
}
private static void parseRule(final List<String> ruleLines,
final Map<File, List<File>> rules) {
final String[] line0parts = ruleLines.get(0).split(":\\s+");
if (line0parts.length > 2) {
throw new IllegalArgumentException("Erroneous rule target format");
}
String target = line0parts[0].trim();
ruleLines.remove(0);
if (line0parts.length == 2) ruleLines.add(line0parts[1]);
final List<File> dependencies = new ArrayList<File>();
// Split dependency part.
for (String dependency : ruleLines) {
// un-escape dollar signs.
dependency = DirectiveHelper.formatOptionString(dependency.replace("$$",
"$").trim());
final File depFile = new File(dependency);
if (dependency.length() > 0 && depFile.exists())
dependencies.add(depFile);
}
// un-escape dollar signs.
target = target.replace("$$", "$");
rules.put(new File(target), dependencies);
}
}