View Javadoc

1   /***
2    * Cecilia ADL Compiler
3    * Copyright (C) 2008 STMicroelectronics
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   *
19   * Contact: fractal@objectweb.org
20   *
21   * Author:Matthieu Leclercq
22   */
23  
24  package org.objectweb.fractal.cecilia.adl.compiler.gnu;
25  
26  import java.io.BufferedReader;
27  import java.io.File;
28  import java.io.FileReader;
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.List;
33  import java.util.logging.Level;
34  
35  import org.objectweb.fractal.adl.ADLException;
36  import org.objectweb.fractal.cecilia.adl.file.SourceFile;
37  import org.objectweb.fractal.cecilia.adl.directives.DirectiveHelper;
38  
39  public class GnuDepCompilerTask extends GnuCompilerTask {
40  
41    /** The default file extension of dependency files. */
42    protected static final String DEPENDENCY_EXTENSION = ".d";
43  
44    // ---------------------------------------------------------------------------
45    // Task constructor
46    // ---------------------------------------------------------------------------
47  
48    /**
49     * @param gccCommand the command of the Gnu C compiler to be executed by this
50     *            task.
51     * @param outputDir the directory into which the compiled file will be placed.
52     * @param flags compilation flags.
53     */
54    public GnuDepCompilerTask(final String gccCommand, final File outputDir,
55        final List<String> flags) {
56      super(gccCommand, outputDir, flags);
57    }
58  
59    // ---------------------------------------------------------------------------
60    // Overridden methods
61    // ---------------------------------------------------------------------------
62  
63    @Override
64    protected void compile(final SourceFile sourceFile, final File outputFile)
65        throws ADLException, InterruptedException {
66      final File depFile = getDependencyFile(outputFile);
67      List<String> newFlags = new ArrayList<String>();
68  
69      if (needRecompile(outputFile, depFile)) {
70        newFlags.add("-MMD");
71        newFlags.add("-MF");
72        newFlags.add(depFile.toString());
73        newFlags.addAll(flags);
74        flags = newFlags;
75        super.compile(sourceFile, outputFile);
76      }
77    }
78  
79    // ---------------------------------------------------------------------------
80    // Utility methods
81    // ---------------------------------------------------------------------------
82  
83    protected File getDependencyFile(final File outputFile) {
84      String name = outputFile.getName();
85      name = name.substring(0, name.lastIndexOf('.')) + DEPENDENCY_EXTENSION;
86      return new File(outputFile.getParent(), name);
87    }
88  
89    protected boolean needRecompile(final File outputFile,
90        final File dependencyFile) {
91  
92      if (!outputFile.exists()) {
93        if (depLogger.isLoggable(Level.FINE))
94          depLogger.fine("Output file '" + outputFile
95              + "' does not exist, compile it.");
96        return true;
97      }
98      final long outputTimestamp = outputFile.lastModified();
99      if (outputTimestamp == 0) {
100       if (depLogger.isLoggable(Level.WARNING))
101         depLogger.warning("Unable to determine timestamp of file '"
102             + outputFile + "', recompile.");
103       return true;
104     }
105 
106     if (!dependencyFile.exists()) {
107       if (depLogger.isLoggable(Level.FINE))
108         depLogger.fine("Dependency file of '" + outputFile
109             + "' does not exist, recompile.");
110       return true;
111     }
112 
113     final Collection<File> depFiles = getDependencies(dependencyFile,
114         outputFile);
115 
116     // if depFiles is null (i.e. the dependencyFile is invalid), recompile.
117     if (depFiles == null) return true;
118 
119     for (final File depfile : depFiles) {
120       if (!depfile.exists()) {
121         if (ioLogger.isLoggable(Level.FINE))
122           ioLogger.fine("Warning input file '" + depfile + " does not exist.");
123       } else if (depfile.lastModified() > outputTimestamp) {
124         if (depLogger.isLoggable(Level.FINE))
125           depLogger.fine("Input file '" + depfile
126               + " is more recent than output file '" + outputFile
127               + "', recompile.");
128         return true;
129       }
130     }
131 
132     if (depLogger.isLoggable(Level.FINE))
133       depLogger.fine("Output file '" + outputFile
134           + "' is up to date, do not recompile.");
135     return false;
136   }
137 
138   protected Collection<File> getDependencies(final File dependencyFile,
139       final File outputFile) {
140     if (depLogger.isLoggable(Level.FINEST)) {
141       depLogger.finest("Parsing dependency file \"" + dependencyFile + "\"...");
142     }
143     final List<Rule> rules = parseRules(dependencyFile);
144     if (depLogger.isLoggable(Level.FINEST)) {
145       depLogger.finest("Parsed rules=" + rules);
146     }
147 
148     for (final Rule rule : rules) {
149       final File targetFile = new File(rule.target);
150       if (depLogger.isLoggable(Level.FINEST)) {
151         depLogger.finest("rule target file is \"" + targetFile + "\".");
152       }
153 
154       // Checks if the rule target correspond to the outputFile. Note that some
155       // old compilers may specify only the name of the output file without its
156       // path.
157       if (!outputFile.equals(targetFile)
158           && !new File(outputFile.getName()).equals(targetFile)) {
159         if (depLogger.isLoggable(Level.FINEST)) {
160           depLogger
161               .finest("target file does not match \"" + outputFile + "\".");
162         }
163         continue;
164       }
165 
166       final List<File> dependentFiles = new ArrayList<File>();
167       for (final String dependency : rule.dependencies) {
168         if (dependency.length() == 0) continue;
169         dependentFiles.add(new File(dependency));
170       }
171       return dependentFiles;
172     }
173 
174     depLogger.warning("Invalid dependency file \"" + dependencyFile
175         + "\". Can't find a rule for file \"" + outputFile + "\"");
176     return null;
177   }
178 
179   protected List<Rule> parseRules(final File dependencyFile) {
180     final List<Rule> rules = new ArrayList<Rule>();
181     try {
182       final BufferedReader reader = new BufferedReader(new FileReader(
183           dependencyFile));
184       String line = null, rule = "";
185       while ((line = reader.readLine()) != null) {
186         if (line.endsWith("\\")) {
187           rule += line.substring(0, line.length() - 1);
188         } else {
189           rule += line.trim();
190 
191           // end of rule, start a new one
192           if (rule.length() > 0) {
193           rules.add(new Rule(rule));
194             rule = "";
195           }
196         }
197       }
198       if (rule.length() > 0) rules.add(new Rule(rule));
199     } catch (final IOException e) {
200       depLogger.log(Level.WARNING, "An error occurs while reading \""
201           + dependencyFile + "\".", e);
202     }
203     return rules;
204   }
205 
206   static final class Rule {
207     String       target;
208     List<String> dependencies;
209 
210     Rule(final String rule) {
211       final String[] parts = rule.split(":\\s+");
212       if (parts.length != 2) {
213         throw new IllegalArgumentException("Can't find rule target");
214       }
215       target = parts[0].trim();
216       // Unescape spaces.
217       target = DirectiveHelper.splitOptionString(target).get(0);
218       dependencies = new ArrayList<String>();
219       // Split dependency part.
220       for (final String dependency : DirectiveHelper.splitOptionString(parts[1])) {
221         if (dependency.length() > 0) dependencies.add(dependency);
222       }
223     }
224   }
225 }