Polished the build tools' screen output to be in a more coherent form
[mirror_edk2.git] / Tools / Source / FrameworkTasks / org / tianocore / framework / tasks / MakeDeps.java
1 /** @file
2 This file is to wrap MakeDeps.exe tool as ANT task, which is used to generate
3 dependency files for source code.
4
5 Copyright (c) 2006, Intel Corporation
6 All rights reserved. This program and the accompanying materials
7 are licensed and made available under the terms and conditions of the BSD License
8 which accompanies this distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
10
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13
14 **/
15 package org.tianocore.framework.tasks;
16
17 import java.io.File;
18 import java.io.FileReader;
19 import java.io.FileWriter;
20 import java.io.IOException;
21 import java.io.LineNumberReader;
22 import java.util.ArrayList;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.StringTokenizer;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 import org.apache.tools.ant.BuildException;
32 import org.apache.tools.ant.Project;
33 import org.apache.tools.ant.Task;
34 import org.apache.tools.ant.taskdefs.Execute;
35 import org.apache.tools.ant.taskdefs.LogStreamHandler;
36 import org.apache.tools.ant.types.Commandline;
37 import org.apache.tools.ant.types.Path;
38 import org.tianocore.logger.EdkLog;
39
40 /**
41 Class MakeDeps is used to wrap MakeDeps.exe as an ANT task.
42 **/
43 public class MakeDeps extends Task {
44
45 //
46 // private members, use set/get to access them
47 //
48 private static final String cmdName = "MakeDeps";
49 private static final String target = "dummy";
50 private String includePath = null;
51 private String depsFile = null;
52 private String subDir = null;
53 private boolean quietMode = true;
54 private boolean ignoreError = true;
55 private String extraDeps = "";
56 private List<IncludePath> includePathList = new ArrayList<IncludePath>();
57 private List<Input> inputFileList = new ArrayList<Input>();
58
59 public MakeDeps() {
60
61 }
62
63 /**
64 The Standard execute method for ANT task. It will check if it's necessary
65 to generate the dependency list file. If no file is found or the dependency
66 is changed, it will compose the command line and call MakeDeps.exe to
67 generate the dependency list file.
68
69 @throws BuildException
70 **/
71 public void execute() throws BuildException {
72 ///
73 /// check if the dependency list file is uptodate or not
74 ///
75 if (isUptodate()) {
76 return;
77 }
78
79 Project prj = this.getOwningTarget().getProject();
80 String toolPath = prj.getProperty("env.FRAMEWORK_TOOLS_PATH");
81 FrameworkLogger logger = new FrameworkLogger(prj, "makedeps");
82 EdkLog.setLogLevel(prj.getProperty("env.LOGLEVEL"));
83 EdkLog.setLogger(logger);
84
85 ///
86 /// compose full tool path
87 ///
88 if (toolPath == null || toolPath.length() == 0) {
89 toolPath = "./" + cmdName;
90 } else {
91 if (toolPath.endsWith("/") || toolPath.endsWith("\\")) {
92 toolPath = toolPath + cmdName;
93 } else {
94 toolPath = toolPath + "/" + cmdName;
95 }
96 }
97
98 ///
99 /// compose tool arguments
100 ///
101 StringBuffer args = new StringBuffer(4096);
102 if (ignoreError) {
103 args.append(" -ignorenotfound");
104 }
105 if (quietMode) {
106 args.append(" -q");
107 }
108 if (subDir != null && subDir.length() > 0) {
109 args.append(" -s ");
110 args.append(subDir);
111 }
112
113 ///
114 /// if there's no source files, we can do nothing about dependency
115 ///
116 if (inputFileList.size() == 0) {
117 throw new BuildException("No source files specified to scan");
118 }
119
120 ///
121 /// compose source file arguments
122 ///
123 Iterator iterator = inputFileList.iterator();
124 while (iterator.hasNext()) {
125 Input inputFile = (Input)iterator.next();
126 String inputFileString = cleanupPathName(inputFile.getFile());
127 args.append(" -f ");
128 args.append(inputFileString);
129 }
130
131 ///
132 /// compose search pathes argument
133 ///
134 StringBuffer includePathArg = new StringBuffer(4096);
135 if (includePath != null && includePath.length() > 0) {
136 StringTokenizer pathTokens = new StringTokenizer(includePath, ";");
137 while (pathTokens.hasMoreTokens()) {
138 String tmpPath = pathTokens.nextToken().trim();
139 if (tmpPath.length() == 0) {
140 continue;
141 }
142
143 includePathArg.append(" -i ");
144 includePathArg.append(cleanupPathName(tmpPath));
145 }
146 }
147 iterator = includePathList.iterator();
148 while (iterator.hasNext()) {
149 IncludePath path = (IncludePath)iterator.next();
150 includePathArg.append(cleanupPathName(path.getPath()));
151 }
152 args.append(includePathArg);
153
154 ///
155 /// We don't need a real target. So just a "dummy" is given
156 ///
157 args.append(" -target dummy");
158 args.append(" -o ");
159 args.append(cleanupPathName(depsFile));
160
161 ///
162 /// prepare to execute the tool
163 ///
164 Commandline cmd = new Commandline();
165 cmd.setExecutable(toolPath);
166 cmd.createArgument().setLine(args.toString());
167
168 LogStreamHandler streamHandler = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN);
169 Execute runner = new Execute(streamHandler, null);
170
171 runner.setAntRun(prj);
172 runner.setCommandline(cmd.getCommandline());
173
174 EdkLog.log(EdkLog.EDK_VERBOSE, Commandline.toString(cmd.getCommandline()));
175 EdkLog.log(EdkLog.EDK_INFO, " ");
176
177 int result = 0;
178 try {
179 result = runner.execute();
180 } catch (IOException e) {
181 throw new BuildException(e.getMessage());
182 }
183
184 if (result != 0) {
185 EdkLog.log(EdkLog.EDK_INFO, "MakeDeps failed!");
186 return;
187 }
188
189 // change the old DEP file format (makefile compatible) to just file list
190 if (!cleanup()) {
191 throw new BuildException(depsFile + " was not generated");
192 }
193 }
194
195 ///
196 /// Remove any duplicated path separator or inconsistent path separator
197 ///
198 private String cleanupPathName(String path) {
199 try {
200 path = (new File(path)).getCanonicalPath();
201 } catch (IOException e) {
202 String separator = "\\" + File.separator;
203 String duplicateSeparator = separator + "{2}";
204 path = Path.translateFile(path);
205 path = path.replaceAll(duplicateSeparator, separator);
206 return path;
207 }
208
209 return path;
210 }
211
212 /**
213 Set method for "DepsFile" attribute
214
215 @param name The name of dependency list file
216 **/
217 public void setDepsFile(String name) {
218 depsFile = cleanupPathName(name);
219 }
220
221 /**
222 Get method for "DepsFile" attribute
223
224 @returns The name of dependency list file
225 **/
226 public String getDepsFile() {
227 return depsFile;
228 }
229
230 /**
231 Set method for "IgnoreError" attribute
232
233 @param ignore flag to control error handling (true/false)
234 **/
235 public void setIgnoreError(boolean ignore) {
236 ignoreError = ignore;
237 }
238
239 /**
240 Get method for "IgnoreError" attribute
241
242 @returns The value of current IgnoreError flag
243 **/
244 public boolean getIgnoreError() {
245 return ignoreError;
246 }
247
248 /**
249 Set method for "QuietMode" attribute
250
251 @param quiet flag to control the output information (true/false)
252 **/
253 public void setQuietMode(boolean quiet) {
254 quietMode = quiet;
255 }
256
257 /**
258 Get method for "QuietMode" attribute
259
260 @returns value of current QuietMode flag
261 **/
262 public boolean getQuietMode() {
263 return quietMode;
264 }
265
266 /**
267 Set method for "SubDir" attribute
268
269 @param dir The name of sub-directory in which source files will be scanned
270 **/
271 public void setSubDir(String dir) {
272 subDir = dir;
273 }
274
275 /**
276 Get method for "SubDir" attribute
277
278 @returns The name of sub-directory
279 **/
280 public String getSubDir() {
281 return subDir;
282 }
283
284 /**
285 Set method for "IncludePath" attribute
286
287 @param path The name of include path
288 **/
289 public void setIncludePath(String path) {
290 includePath = cleanupPathName(path);
291 }
292
293 /**
294 Get method for "IncludePath" attribute
295
296 @returns The name of include path
297 **/
298 public String getIncludePath() {
299 return includePath;
300 }
301
302 /**
303 Set method for "ExtraDeps" attribute
304
305 @param deps The name of dependency file specified separately
306 **/
307 public void setExtraDeps(String deps) {
308 extraDeps = deps;
309 }
310
311 /**
312 Get method for "ExtraDeps" attribute
313
314 @returns The name of dependency file specified separately
315 **/
316 public String getExtraDeps () {
317 return extraDeps;
318 }
319
320 /**
321 Add method for "IncludePath" nested element
322
323 @param path The IncludePath object from nested IncludePath type of element
324 **/
325 public void addIncludepath(IncludePath path) {
326 includePathList.add(path);
327 }
328
329 /**
330 Add method for "Input" nested element
331
332 @param input The Input object from nested Input type of element
333 **/
334 public void addInput(Input inputFile) {
335 inputFileList.add(inputFile);
336 }
337
338 /**
339 The original file generated by MakeDeps.exe is for makefile uses. The target
340 part (before :) is not useful for ANT. This method will do the removal.
341
342 @returns true if cleaned files is saved successfully
343 @returns false if error occurs in file I/O system
344 **/
345 private boolean cleanup() {
346 File df = new File(depsFile);
347
348 if (!df.exists()) {
349 return false;
350 }
351
352 LineNumberReader lineReader = null;
353 FileReader fileReader = null;
354 Set<String> lineSet = new HashSet<String>(100); // used to remove duplicated lines
355 try {
356 fileReader = new FileReader(df);
357 lineReader = new LineNumberReader(fileReader);
358
359 ///
360 /// clean-up each line in deps file
361 //
362 String line = null;
363 while ((line = lineReader.readLine()) != null) {
364 Pattern pattern = Pattern.compile(target + "[ ]*:[ ]*(.+)");
365 Matcher matcher = pattern.matcher(line);
366
367 while (matcher.find()) {
368 ///
369 /// keep the file name after ":"
370 ///
371 String filePath = line.substring(matcher.start(1), matcher.end(1));
372 filePath = cleanupPathName(filePath);
373 lineSet.add(filePath);
374 }
375 }
376 lineReader.close();
377 fileReader.close();
378
379 ///
380 /// we may have explicitly specified dependency files
381 ///
382 StringTokenizer fileTokens = new StringTokenizer(extraDeps, ";");
383 while (fileTokens.hasMoreTokens()) {
384 lineSet.add(cleanupPathName(fileTokens.nextToken()));
385 }
386
387 ///
388 /// compose the final file content
389 ///
390 StringBuffer cleanedLines = new StringBuffer(40960);
391 Iterator<String> it = lineSet.iterator();
392 while (it.hasNext()) {
393 String filePath = it.next();
394 cleanedLines.append(filePath);
395 cleanedLines.append("\n");
396 }
397 ///
398 /// overwrite old dep file with new content
399 ///
400 FileWriter fileWriter = null;
401 fileWriter = new FileWriter(df);
402 fileWriter.write(cleanedLines.toString());
403 fileWriter.close();
404 } catch (IOException e) {
405 log (e.getMessage());
406 }
407
408 return true;
409 }
410
411 /**
412 Check if the dependency list file should be (re-)generated or not.
413
414 @returns true The dependency list file is uptodate. No re-generation is needed.
415 @returns false The dependency list file is outofdate. Re-generation is needed.
416 **/
417 private boolean isUptodate() {
418 File df = new File(depsFile);
419 if (!df.exists()) {
420 return false;
421 }
422
423 ///
424 /// If the source file(s) is newer than dependency list file, we need to
425 /// re-generate the dependency list file
426 ///
427 long depsFileTimeStamp = df.lastModified();
428 Iterator iterator = inputFileList.iterator();
429 while (iterator.hasNext()) {
430 Input inputFile = (Input)iterator.next();
431 File sf = new File(inputFile.getFile());
432 if (sf.lastModified() > depsFileTimeStamp) {
433 return false;
434 }
435 }
436
437 ///
438 /// If the source files haven't been changed since last time the dependency
439 /// list file was generated, we need to check each file in the file list to
440 /// see if any of them is changed or not. If anyone of them is newer than
441 /// the dependency list file, MakeDeps.exe is needed to run again.
442 ///
443 LineNumberReader lineReader = null;
444 FileReader fileReader = null;
445 boolean ret = true;
446 try {
447 fileReader = new FileReader(df);
448 lineReader = new LineNumberReader(fileReader);
449
450 String line = null;
451 while ((line = lineReader.readLine()) != null) {
452 File sourceFile = new File(line);
453 if (sourceFile.lastModified() > depsFileTimeStamp) {
454 ret = false;
455 break;
456 }
457 }
458 lineReader.close();
459 fileReader.close();
460 } catch (IOException e) {
461 log (e.getMessage());
462 }
463
464 return ret;
465 }
466 }
467