blob: bf3fac779dda5fb17085fe4a519a23553e78eb4e [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Windows.Forms;
using Microsoft.Build.Framework;
using Microsoft.Win32;
using Microsoft.Build.Utilities;
using System.Collections.Specialized;
using System.Diagnostics;
namespace NaCl.Build.CPPTasks
{
public abstract class NaClToolTask : ToolTask
{
protected NaClToolTask(ResourceManager taskResources) : base(taskResources) { }
public bool BuildingInIDE { get; set; }
protected ITaskItem[] excludedInputPaths;
private ITaskItem[] tlogReadFiles;
private ITaskItem tlogCommandFile;
private ITaskItem[] tlogWriteFiles;
private CanonicalTrackedInputFiles trackedInputFiles;
private bool skippedExecution;
private bool trackFileAccess;
protected ITaskItem[] compileSourceList;
protected XamlParser xamlParser;
[Required]
public string PropertiesFile { get; set; }
[Required]
public string TrackerLogDirectory { get; set; }
[Required]
public virtual ITaskItem[] Sources { get; set; }
[Required]
public bool OutputCommandLine { get; set; }
[Required]
public bool MinimalRebuildFromTracking { get; set; }
[Required]
public string Platform { get; set; }
public virtual string OutputFile { get; set; }
// Override default StandardOutputLoggingImportance so that we see the stdout from the
// toolchain from within visual studio.
protected override MessageImportance StandardOutputLoggingImportance
{
get { return MessageImportance.Normal; }
}
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;
}
}
protected override bool SkipTaskExecution()
{
return this.skippedExecution;
}
protected string BaseTool()
{
return Path.GetFileNameWithoutExtension(ToolName);
}
protected bool IsPNaCl()
{
return Platform.Equals("pnacl", StringComparison.OrdinalIgnoreCase);
}
protected bool Setup()
{
SkippedExecution = false;
if (!ValidateParameters())
{
return false;
}
if (IsPNaCl())
{
if (!SDKUtilities.FindPython())
{
Log.LogError("PNaCl linking requires python in your executable path.");
return false;
}
}
if (TrackFileAccess || MinimalRebuildFromTracking)
{
SetTrackerLogPaths();
}
CalcSourcesToBuild();
return true;
}
protected virtual CanonicalTrackedOutputFiles OutputWriteTLog(ITaskItem[] inputs)
{
string path = Path.Combine(TlogDirectory, WriteTLogFilename);
TaskItem item = new TaskItem(path);
CanonicalTrackedOutputFiles trackedFiles =
new CanonicalTrackedOutputFiles(new TaskItem[] { item });
foreach (ITaskItem sourceItem in Sources)
{
//remove this entry associated with compiled source which is about to be recomputed
trackedFiles.RemoveEntriesForSource(sourceItem);
//add entry with updated information
string upper = Path.GetFullPath(sourceItem.ItemSpec).ToUpperInvariant();
trackedFiles.AddComputedOutputForSourceRoot(upper, OutputFile);
}
//output tlog
trackedFiles.SaveTlog();
return trackedFiles;
}
protected virtual void OutputReadTLog(ITaskItem[] compiledSources,
CanonicalTrackedOutputFiles outputs)
{
string trackerPath = Path.GetFullPath(TlogDirectory + ReadTLogFilenames[0]);
using (var writer = new StreamWriter(trackerPath, false, Encoding.Unicode))
{
string sourcePath = "";
foreach (ITaskItem source in Sources)
{
if (sourcePath != "")
sourcePath += "|";
sourcePath += Path.GetFullPath(source.ItemSpec).ToUpperInvariant();
}
writer.WriteLine("^" + sourcePath);
foreach (ITaskItem source in Sources)
{
writer.WriteLine(Path.GetFullPath(source.ItemSpec).ToUpperInvariant());
}
writer.WriteLine(Path.GetFullPath(OutputFile).ToUpperInvariant());
}
}
protected virtual string TLogCommandForSource(ITaskItem source)
{
return GenerateResponseFileCommands();
}
protected virtual void OutputCommandTLog(ITaskItem[] compiledSources)
{
string fullpath = TLogCommandFile.GetMetadata("FullPath");
using (var writer = new StreamWriter(fullpath, false, Encoding.Unicode))
{
string cmds = TLogCommandForSource(Sources[0]);
string sourcePath = "";
foreach (ITaskItem source in Sources)
{
if (sourcePath != "")
sourcePath += "|";
sourcePath += Path.GetFullPath(source.ItemSpec).ToUpperInvariant();
}
writer.WriteLine("^" + sourcePath);
writer.WriteLine(cmds);
}
}
public override bool Execute()
{
xamlParser = new XamlParser(PropertiesFile);
if (!Setup())
return false;
if (SkippedExecution)
return true;
bool res = base.Execute();
// Update tracker log files if execution was successful
if (res)
{
CanonicalTrackedOutputFiles outputs = OutputWriteTLog(compileSourceList);
OutputReadTLog(compileSourceList, outputs);
OutputCommandTLog(compileSourceList);
}
return res;
}
protected override Encoding ResponseFileEncoding
{
get
{
return Encoding.ASCII;
}
}
protected virtual void SetTrackerLogPaths()
{
if (TLogCommandFile == null)
{
string commandFile = Path.Combine(TlogDirectory, CommandTLogFilename);
TLogCommandFile = new TaskItem(commandFile);
}
if (TLogReadFiles == null)
{
TLogReadFiles = new ITaskItem[ReadTLogFilenames.Length];
for (int n = 0; n < ReadTLogFilenames.Length; n++)
{
string readFile = Path.Combine(TlogDirectory, ReadTLogFilenames[n]);
TLogReadFiles[n] = new TaskItem(readFile);
}
}
if (this.TLogWriteFiles == null)
{
TLogWriteFiles = new ITaskItem[1];
string writeFile = Path.Combine(TlogDirectory, WriteTLogFilename);
TLogWriteFiles[0] = new TaskItem(writeFile);
}
}
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 IDictionary<string, string> GenerateCommandLinesFromTlog()
{
IDictionary<string, string> cmdLineDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string tlogFilename = this.TLogCommandFile.GetMetadata("FullPath");
if (!File.Exists(tlogFilename))
return cmdLineDictionary;
using (StreamReader reader = File.OpenText(tlogFilename))
{
string[] filenames = null;
for (string lineStr = reader.ReadLine(); lineStr != null; lineStr = reader.ReadLine())
{
if (lineStr.Length == 0 || (lineStr[0] == '^' && lineStr.Length == 1))
{
Log.LogError("Invalid line in command tlog");
break;
}
else if (lineStr[0] == '^')
{
filenames = lineStr.Substring(1).Split('|');
}
else
{
foreach (string filename in filenames)
{
cmdLineDictionary[filename] = lineStr;
}
}
}
}
return cmdLineDictionary;
}
protected List<ITaskItem> GetOutOfDateSourcesFromCmdLineChanges()
{
//get dictionary of source + command lines
IDictionary<string, string> dictionary = 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(TLogCommandForSource(sourceItem));
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 void CalcSourcesToBuild()
{
//check if full recompile is required otherwise perform incremental
if (ForcedRebuildRequired() || MinimalRebuildFromTracking == false)
{
CompileSourceList = Sources;
return;
}
//retrieve list of sources out of date due to command line changes
List<ITaskItem> outOfDateSourcesFromCommandLine = GetOutOfDateSourcesFromCmdLineChanges();
//retrieve sources out of date due to tracking
CanonicalTrackedOutputFiles outputs = new CanonicalTrackedOutputFiles(this, TLogWriteFiles);
TrackedInputFiles = new CanonicalTrackedInputFiles(this,
TLogReadFiles,
Sources,
ExcludedInputPaths,
outputs,
true,
false);
ITaskItem[] outOfDateSourcesFromTracking = TrackedInputFiles.ComputeSourcesNeedingCompilation();
//merge out of date lists
CompileSourceList = MergeOutOfDateSources(outOfDateSourcesFromTracking, outOfDateSourcesFromCommandLine);
if (CompileSourceList.Length == 0)
{
SkippedExecution = true;
return;
}
//remove sources to compile from tracked file list
TrackedInputFiles.RemoveEntriesForSource(CompileSourceList);
outputs.RemoveEntriesForSource(CompileSourceList);
TrackedInputFiles.SaveTlog();
outputs.SaveTlog();
}
[Output]
public bool SkippedExecution
{
get
{
return this.skippedExecution;
}
set
{
this.skippedExecution = value;
}
}
[Output]
public ITaskItem[] CompileSourceList
{
get
{
return this.compileSourceList;
}
set
{
this.compileSourceList = 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 ITaskItem[] TLogReadFiles
{
get
{
return this.tlogReadFiles;
}
set
{
this.tlogReadFiles = value;
}
}
public ITaskItem[] TLogWriteFiles
{
get
{
return this.tlogWriteFiles;
}
set
{
this.tlogWriteFiles = value;
}
}
public bool TrackFileAccess
{
get
{
return this.trackFileAccess;
}
set
{
this.trackFileAccess = value;
}
}
protected CanonicalTrackedInputFiles TrackedInputFiles
{
get
{
return this.trackedInputFiles;
}
set
{
this.trackedInputFiles = value;
}
}
public ITaskItem[] ExcludedInputPaths
{
get
{
return this.excludedInputPaths;
}
set
{
this.excludedInputPaths = value;
}
}
protected abstract string CommandTLogFilename { get; }
protected abstract string WriteTLogFilename { get; }
protected abstract string[] ReadTLogFilenames { get; }
public virtual string PlatformToolset
{
get
{
return "GCC";
}
}
protected override string GenerateFullPathToTool()
{
return this.ToolName;
}
}
}