blob: 0459386385e8c515adec69ac7db432df25247123 [file] [log] [blame]

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Resources;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;
namespace NaCl.Build.CPPTasks
{
public class NaClCompile : ToolTask
{
private XamlParser m_XamlParser;
private ITaskItem[] excludedInputPaths;
private ITaskItem[] tlogReadFiles;
private ITaskItem tlogCommandFile;
private ITaskItem[] tlogWriteFiles;
private CanonicalTrackedInputFiles trackedInputFiles;
private bool skippedExecution;
private ITaskItem[] compileSourceList;
public bool BuildingInIDE { get; set; }
private string m_toolname;
private bool trackFileAccess;
private bool minimalRebuildFromTracking;
private string pathToLog;
[Required]
public string PropertiesFile { get; set; }
[Required]
public ITaskItem[] Sources { get; set; }
[Required]
public string NaCLCompilerPath { get; set; }
[Required]
public string OutputCommandLine { get; set; }
[Required]
public string TrackerLogDirectory { get; set; }
protected override string GenerateFullPathToTool() { return ToolName; }
public NaClCompile()
: base(new ResourceManager("NaCl.Build.CPPTasks.Properties.Resources", Assembly.GetExecutingAssembly()))
{
this.pathToLog = string.Empty;
this.EnvironmentVariables = new string []{"CYGWIN=nodosfilewarning", "LC_CTYPE=C"};
}
protected IDictionary<string, string> GenerateCommandLinesFromTlog()
{
IDictionary<string, string> cmdLineDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string tlogFilename = this.TLogCommandFile.GetMetadata("FullPath");
if (File.Exists(tlogFilename))
{
using (StreamReader reader = File.OpenText(tlogFilename))
{
string filename = string.Empty;
for (string lineStr = reader.ReadLine(); lineStr != null; lineStr = reader.ReadLine())
{
if (lineStr.Length == 0 ||
(lineStr[0] == '^' && lineStr.Length == 1))
{
Log.LogMessage(MessageImportance.High, "Invalid line in command tlog");
break;
}
else if (lineStr[0] == '^')
{
filename = lineStr.Substring(1);
}
else
{
cmdLineDictionary[filename] = lineStr;
}
}
}
}
return cmdLineDictionary;
}
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
{
base.LogEventsFromTextOutput(GCCUtilities.Convert_Output_GCC_to_VS(singleLine), messageImportance);
}
private void ConstructReadTLog(ITaskItem[] compiledSources, CanonicalTrackedOutputFiles outputs)
{
string trackerPath = Path.GetFullPath(TlogDirectory + ReadTLogFilenames[0]);
//save tlog for sources not compiled during this execution
TaskItem readTrackerItem = new TaskItem(trackerPath);
CanonicalTrackedInputFiles files = new CanonicalTrackedInputFiles(new TaskItem[] { readTrackerItem }, Sources, outputs, false, false);
files.RemoveEntriesForSource(compiledSources);
files.SaveTlog();
//add tlog information for compiled sources
using (StreamWriter writer = new StreamWriter(trackerPath, true, Encoding.Unicode))
{
foreach (ITaskItem source in compiledSources)
{
string sourcePath = Path.GetFullPath(source.ItemSpec).ToUpperInvariant();
string objectFilePath = Path.GetFullPath(source.GetMetadata("ObjectFileName"));
string depFilePath = Path.ChangeExtension(objectFilePath, ".d");
try
{
if (File.Exists(depFilePath) == false)
{
Log.LogMessage(MessageImportance.High, depFilePath + " not found");
}
else
{
writer.WriteLine("^" + sourcePath);
DependencyParser parser = new DependencyParser(depFilePath);
foreach (string filename in parser.Dependencies)
{
//source itself not required
if (filename == sourcePath)
continue;
if (File.Exists(filename) == false)
{
Log.LogMessage(MessageImportance.High, "File " + sourcePath + " is missing dependency " + filename);
}
writer.WriteLine(filename);
}
//remove d file
try
{
File.Delete(depFilePath);
}
finally
{
}
}
}
catch (Exception)
{
Log.LogError("Failed to update " + readTrackerItem + " for " + sourcePath);
}
}
}
}
private CanonicalTrackedOutputFiles OutputWriteTrackerLog(ITaskItem[] compiledSources)
{
string path = Path.Combine(TlogDirectory, WriteTLogFilename);
TaskItem item = new TaskItem(path);
CanonicalTrackedOutputFiles trackedFiles = new CanonicalTrackedOutputFiles(new TaskItem[] { item });
foreach (ITaskItem sourceItem in compiledSources)
{
//remove this entry associated with compiled source which is about to be recomputed
trackedFiles.RemoveEntriesForSource(sourceItem);
//add entry with updated information
trackedFiles.AddComputedOutputForSourceRoot( Path.GetFullPath(sourceItem.ItemSpec).ToUpperInvariant(),
Path.GetFullPath(sourceItem.GetMetadata("ObjectFileName")).ToUpperInvariant());
}
//output tlog
trackedFiles.SaveTlog();
return trackedFiles;
}
private void OutputCommandTrackerLog(ITaskItem[] compiledSources)
{
IDictionary<string, string> commandLines = GenerateCommandLinesFromTlog();
//
if (compiledSources != null)
{
foreach (ITaskItem source in compiledSources)
{
string rmSource = FileTracker.FormatRootingMarker(source);
commandLines[rmSource] = GenerateCommandLineFromProps(source) + " " + source.GetMetadata("FullPath").ToUpperInvariant();
}
}
//write tlog
using (StreamWriter writer = new StreamWriter(this.TLogCommandFile.GetMetadata("FullPath"), false, Encoding.Unicode))
{
foreach (KeyValuePair<string, string> p in commandLines)
{
string keyLine = "^" + p.Key;
writer.WriteLine(keyLine);
writer.WriteLine(p.Value);
}
}
}
protected string GenerateCommandLineFromProps(ITaskItem sourceFile)
{
StringBuilder commandLine = new StringBuilder(GCCUtilities.s_CommandLineLength);
if (sourceFile != null)
{
string sourcePath = GCCUtilities.Convert_Path_Windows_To_Posix(sourceFile.ToString());
// Remove rtti items as they are not relevant in C compilation and will produce warnings
if (SourceIsC(sourceFile.ToString()))
{
commandLine.Replace("-fno-rtti", "");
commandLine.Replace("-frtti", "");
}
//build command line from components and add required switches
string props = m_XamlParser.Parse(sourceFile);
commandLine.Append(props);
commandLine.Append(" -MD -c ");
commandLine.Append("\"" + sourcePath "\"");
}
return commandLine.ToString();
}
protected ITaskItem[] MergeOutOfDateSources(ITaskItem[] outOfDateSourcesFromTracking, List<ITaskItem> outOfDateSourcesFromCommandLineChanges)
{
List<ITaskItem> mergedSources = new List<ITaskItem>(outOfDateSourcesFromTracking);
foreach (ITaskItem item in outOfDateSourcesFromCommandLineChanges)
{
if (!mergedSources.Contains(item))
{
mergedSources.Add(item);
}
}
return mergedSources.ToArray();
}
protected bool ForcedRebuildRequired()
{
string tlogCommandPath = null;
try
{
tlogCommandPath = this.TLogCommandFile.GetMetadata("FullPath");
}
catch (Exception exception)
{
if (exception is InvalidOperationException || exception is NullReferenceException)
return true;
else
throw;
}
//if command tlog file does not exist then force rebuild is required
if (File.Exists(tlogCommandPath) == false)
{
return true;
}
else
{
return false;
}
}
private int Compile(string pathToTool)
{
int returnCode = 0;
foreach (ITaskItem sourceFileItem in CompileSourceList)
{
try
{
string commandLine = GenerateCommandLineFromProps(sourceFileItem);
base.Log.LogMessageFromText(Path.GetFileName(sourceFileItem.ToString()), MessageImportance.High);
if (OutputCommandLine == "true")
{
string logMessage = pathToTool + " " + commandLine;
Log.LogMessageFromText(logMessage, MessageImportance.High);
}
// compile
returnCode = base.ExecuteTool(pathToTool, commandLine, string.Empty);
}
catch (Exception)
{
returnCode = base.ExitCode;
}
//abort if an error was encountered
if (returnCode != 0)
{
return returnCode;
}
}
return returnCode;
}
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
{
if (File.Exists(pathToTool) == false)
{
base.Log.LogMessageFromText("Unable to find NaCL compiler: " + pathToTool, MessageImportance.High);
return -1;
}
int returnCode = -1;
try
{
returnCode = Compile(pathToTool);
}
finally
{
}
return returnCode;
}
protected override bool SkipTaskExecution()
{
return this.skippedExecution;
}
protected void CalcSourcesToCompile()
{
if (this.TrackFileAccess || this.MinimalRebuildFromTracking)
{
this.SetTrackerLogPaths();
}
//check if full recompile is required otherwise perform incremental
if (this.ForcedRebuildRequired() || this.MinimalRebuildFromTracking == false)
{
this.CompileSourceList = this.Sources;
if ((this.CompileSourceList == null) || (this.CompileSourceList.Length == 0))
{
this.SkippedExecution = true;
}
}
else
{
//retrieve list of sources out of date due to command line changes
List<ITaskItem> outOfDateSourcesFromCommandLineChanges = this.GetOutOfDateSourcesFromCommandLineChanges();
//retrieve sources out of date due to tracking
CanonicalTrackedOutputFiles trackedOutputFiles = new CanonicalTrackedOutputFiles(this, this.TLogWriteFiles);
this.TrackedInputFiles = new CanonicalTrackedInputFiles(this, this.TLogReadFiles, this.Sources, this.ExcludedInputPaths, trackedOutputFiles, true, false);
ITaskItem[] outOfDateSourcesFromTracking = this.TrackedInputFiles.ComputeSourcesNeedingCompilation();
//merge out of date lists
this.CompileSourceList = this.MergeOutOfDateSources(outOfDateSourcesFromTracking, outOfDateSourcesFromCommandLineChanges);
if (this.CompileSourceList.Length == 0)
{
this.SkippedExecution = true;
}
else
{
//remove sources to compile from tracked file list
this.TrackedInputFiles.RemoveEntriesForSource(this.CompileSourceList);
trackedOutputFiles.RemoveEntriesForSource(this.CompileSourceList);
this.TrackedInputFiles.SaveTlog();
trackedOutputFiles.SaveTlog();
this.SkippedExecution = false;
}
}
}
protected bool SourceIsC(string sourceFilename)
{
string fileExt = Path.GetExtension(sourceFilename.ToString());
if (fileExt == ".c")
return true;
else
return false;
}
public override bool Execute()
{
bool returnResult = false;
try
{
m_XamlParser = new XamlParser(PropertiesFile);
m_toolname = Path.GetFileNameWithoutExtension(ToolName);
ValidateParameters();
CalcSourcesToCompile();
returnResult = base.Execute();
// Update tracker log files if execution occurred
//if (this.skippedExecution == false)
{
CanonicalTrackedOutputFiles outputs = OutputWriteTrackerLog(CompileSourceList);
ConstructReadTLog(CompileSourceList, outputs);
OutputCommandTrackerLog(CompileSourceList);
}
}
finally
{
}
return returnResult;
}
protected List<ITaskItem> GetOutOfDateSourcesFromCommandLineChanges()
{
//get dictionary of source + command lines
IDictionary<string, string> dictionary = this.GenerateCommandLinesFromTlog();
List<ITaskItem> outOfDateSources = new List<ITaskItem>();
//add sources to out of date list if the tlog dictionary string do not match the generated command line string
StringBuilder currentCommandLine = new StringBuilder(GCCUtilities.s_CommandLineLength);
foreach (ITaskItem sourceItem in Sources)
{
currentCommandLine.Length = 0;
currentCommandLine.Append(GenerateCommandLineFromProps(sourceItem));
currentCommandLine.Append(" ");
currentCommandLine.Append(sourceItem.GetMetadata("FullPath").ToUpperInvariant());
string tlogCommandLine = null;
if (dictionary.TryGetValue(FileTracker.FormatRootingMarker(sourceItem), out tlogCommandLine))
{
if ((tlogCommandLine == null) || !currentCommandLine.ToString().Equals(tlogCommandLine, StringComparison.Ordinal))
{
outOfDateSources.Add(sourceItem);
}
}
else
{
outOfDateSources.Add(sourceItem);
}
}
return outOfDateSources;
}
protected virtual void SetTrackerLogPaths()
{
if (this.TLogCommandFile == null)
{
string commandFile = Path.Combine(this.TlogDirectory, this.CommandTLogFilename);
this.TLogCommandFile = new TaskItem(commandFile);
}
if (this.TLogReadFiles == null)
{
this.TLogReadFiles = new ITaskItem[this.ReadTLogFilenames.Length];
for (int n = 0; n < this.ReadTLogFilenames.Length; n++)
{
string readFile = Path.Combine(this.TlogDirectory, this.ReadTLogFilenames[n]);
this.TLogReadFiles[n] = new TaskItem(readFile);
}
}
if (this.TLogWriteFiles == null)
{
this.TLogWriteFiles = new ITaskItem[1];
string writeFile = Path.Combine(this.TlogDirectory, this.WriteTLogFilename);
this.TLogWriteFiles[0] = new TaskItem(writeFile);
}
}
//props
protected string CommandTLogFilename
{
get
{
return m_toolname + ".compile.command.1.tlog";
}
}
protected string[] ReadTLogFilenames
{
get
{
return new string[] { m_toolname + ".compile.read.1.tlog" };
}
}
[Output]
public bool SkippedExecution
{
get
{
return this.skippedExecution;
}
set
{
this.skippedExecution = value;
}
}
public ITaskItem TLogCommandFile
{
get
{
return this.tlogCommandFile;
}
set
{
this.tlogCommandFile = value;
}
}
protected string TlogDirectory
{
get
{
if (this.TrackerLogDirectory != null)
{
return this.TrackerLogDirectory;
}
return string.Empty;
}
}
public bool MinimalRebuildFromTracking
{
get
{
return this.minimalRebuildFromTracking;
}
set
{
this.minimalRebuildFromTracking = value;
}
}
public ITaskItem[] TLogReadFiles
{
get
{
return this.tlogReadFiles;
}
set
{
this.tlogReadFiles = value;
}
}
public ITaskItem[] ExcludedInputPaths
{
get
{
return this.excludedInputPaths;
}
set
{
this.excludedInputPaths = value;
}
}
public ITaskItem[] TLogWriteFiles
{
get
{
return this.tlogWriteFiles;
}
set
{
this.tlogWriteFiles = value;
}
}
protected string WriteTLogFilename
{
get
{
return m_toolname + ".compile.write.1.tlog";
}
}
public bool TrackFileAccess
{
get
{
return this.trackFileAccess;
}
set
{
this.trackFileAccess = value;
}
}
protected CanonicalTrackedInputFiles TrackedInputFiles
{
get
{
return this.trackedInputFiles;
}
set
{
this.trackedInputFiles = value;
}
}
[Output]
public ITaskItem[] CompileSourceList
{
get
{
return this.compileSourceList;
}
set
{
this.compileSourceList = value;
}
}
protected override string ToolName
{
get
{
return NaCLCompilerPath;
}
}
protected override Encoding ResponseFileEncoding
{
get
{
return Encoding.ASCII;
}
}
}
}