|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.Linq; | 
|  | using System.Text; | 
|  | using Microsoft.Build.Framework; | 
|  | using Microsoft.Build.Utilities; | 
|  | using Microsoft.Build.Tasks; | 
|  | using System.IO; | 
|  | using System.Diagnostics; | 
|  |  | 
|  | namespace ThriftMSBuildTask | 
|  | { | 
|  | /// <summary> | 
|  | /// MSBuild Task to generate csharp from .thrift files, and compile the code into a library: ThriftImpl.dll | 
|  | /// </summary> | 
|  | public class ThriftBuild : Task | 
|  | { | 
|  | /// <summary> | 
|  | /// The full path to the thrift.exe compiler | 
|  | /// </summary> | 
|  | [Required] | 
|  | public ITaskItem ThriftExecutable | 
|  | { | 
|  | get; | 
|  | set; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// The full path to a thrift.dll C# library | 
|  | /// </summary> | 
|  | [Required] | 
|  | public ITaskItem ThriftLibrary | 
|  | { | 
|  | get; | 
|  | set; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// A direcotry containing .thrift files | 
|  | /// </summary> | 
|  | [Required] | 
|  | public ITaskItem ThriftDefinitionDir | 
|  | { | 
|  | get; | 
|  | set; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// The name of the auto-gen and compiled thrift library. It will placed in | 
|  | /// the same directory as ThriftLibrary | 
|  | /// </summary> | 
|  | [Required] | 
|  | public ITaskItem OutputName | 
|  | { | 
|  | get; | 
|  | set; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// The full path to the compiled ThriftLibrary. This allows msbuild tasks to use this | 
|  | /// output as a variable for use elsewhere. | 
|  | /// </summary> | 
|  | [Output] | 
|  | public ITaskItem ThriftImplementation | 
|  | { | 
|  | get { return thriftImpl; } | 
|  | } | 
|  |  | 
|  | private ITaskItem thriftImpl; | 
|  | private const string lastCompilationName = "LAST_COMP_TIMESTAMP"; | 
|  |  | 
|  | //use the Message Build Task to write something to build log | 
|  | private void LogMessage(string text, MessageImportance importance) | 
|  | { | 
|  | Message m = new Message(); | 
|  | m.Text = text; | 
|  | m.Importance = importance.ToString(); | 
|  | m.BuildEngine = this.BuildEngine; | 
|  | m.Execute(); | 
|  | } | 
|  |  | 
|  | //recursively find .cs files in srcDir, paths should initially be non-null and empty | 
|  | private void FindSourcesHelper(string srcDir, List<string> paths) | 
|  | { | 
|  | string[] files = Directory.GetFiles(srcDir, "*.cs"); | 
|  | foreach (string f in files) | 
|  | { | 
|  | paths.Add(f); | 
|  | } | 
|  | string[] dirs = Directory.GetDirectories(srcDir); | 
|  | foreach (string dir in dirs) | 
|  | { | 
|  | FindSourcesHelper(dir, paths); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Quote paths with spaces | 
|  | /// </summary> | 
|  | private string SafePath(string path) | 
|  | { | 
|  | if (path.Contains(' ') && !path.StartsWith("\"")) | 
|  | { | 
|  | return "\"" + path + "\""; | 
|  | } | 
|  | return path; | 
|  | } | 
|  |  | 
|  | private ITaskItem[] FindSources(string srcDir) | 
|  | { | 
|  | List<string> files = new List<string>(); | 
|  | FindSourcesHelper(srcDir, files); | 
|  | ITaskItem[] items = new ITaskItem[files.Count]; | 
|  | for (int i = 0; i < items.Length; i++) | 
|  | { | 
|  | items[i] = new TaskItem(files[i]); | 
|  | } | 
|  | return items; | 
|  | } | 
|  |  | 
|  | private string LastWriteTime(string defDir) | 
|  | { | 
|  | string[] files = Directory.GetFiles(defDir, "*.thrift"); | 
|  | DateTime d = (new DirectoryInfo(defDir)).LastWriteTime; | 
|  | foreach(string file in files) | 
|  | { | 
|  | FileInfo f = new FileInfo(file); | 
|  | DateTime curr = f.LastWriteTime; | 
|  | if (DateTime.Compare(curr, d) > 0) | 
|  | { | 
|  | d = curr; | 
|  | } | 
|  | } | 
|  | return d.ToFileTimeUtc().ToString(); | 
|  | } | 
|  |  | 
|  | public override bool Execute() | 
|  | { | 
|  | string defDir = SafePath(ThriftDefinitionDir.ItemSpec); | 
|  | //look for last compilation timestamp | 
|  | string lastBuildPath = Path.Combine(defDir, lastCompilationName); | 
|  | DirectoryInfo defDirInfo = new DirectoryInfo(defDir); | 
|  | string lastWrite = LastWriteTime(defDir); | 
|  | if (File.Exists(lastBuildPath)) | 
|  | { | 
|  | string lastComp = File.ReadAllText(lastBuildPath); | 
|  | //don't recompile if the thrift library has been updated since lastComp | 
|  | FileInfo f = new FileInfo(ThriftLibrary.ItemSpec); | 
|  | string thriftLibTime = f.LastWriteTimeUtc.ToFileTimeUtc().ToString(); | 
|  | if (lastComp.CompareTo(thriftLibTime) < 0) | 
|  | { | 
|  | //new thrift library, do a compile | 
|  | lastWrite = thriftLibTime; | 
|  | } | 
|  | else if (lastComp == lastWrite || (lastComp == thriftLibTime && lastComp.CompareTo(lastWrite) > 0)) | 
|  | { | 
|  | //the .thrift dir hasn't been written to since last compilation, don't need to do anything | 
|  | LogMessage("ThriftImpl up-to-date", MessageImportance.High); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | //find the directory of the thriftlibrary (that's where output will go) | 
|  | FileInfo thriftLibInfo = new FileInfo(SafePath(ThriftLibrary.ItemSpec)); | 
|  | string thriftDir = thriftLibInfo.Directory.FullName; | 
|  |  | 
|  | string genDir = Path.Combine(thriftDir, "gen-csharp"); | 
|  | if (Directory.Exists(genDir)) | 
|  | { | 
|  | try | 
|  | { | 
|  | Directory.Delete(genDir, true); | 
|  | } | 
|  | catch { /*eh i tried, just over-write now*/} | 
|  | } | 
|  |  | 
|  | //run the thrift executable to generate C# | 
|  | foreach (string thriftFile in Directory.GetFiles(defDir, "*.thrift")) | 
|  | { | 
|  | LogMessage("Generating code for: " + thriftFile, MessageImportance.Normal); | 
|  | Process p = new Process(); | 
|  | p.StartInfo.FileName = SafePath(ThriftExecutable.ItemSpec); | 
|  | p.StartInfo.Arguments = "-csharp -o " + SafePath(thriftDir) + " -r " + thriftFile; | 
|  | p.StartInfo.UseShellExecute = false; | 
|  | p.StartInfo.CreateNoWindow = true; | 
|  | p.StartInfo.RedirectStandardOutput = false; | 
|  | p.Start(); | 
|  | p.WaitForExit(); | 
|  | if (p.ExitCode != 0) | 
|  | { | 
|  | LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High); | 
|  | return false; | 
|  | } | 
|  | if (p.ExitCode != 0) | 
|  | { | 
|  | LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | Csc csc = new Csc(); | 
|  | csc.TargetType = "library"; | 
|  | csc.References = new ITaskItem[] { new TaskItem(ThriftLibrary.ItemSpec) }; | 
|  | csc.EmitDebugInformation = true; | 
|  | string outputPath = Path.Combine(thriftDir, OutputName.ItemSpec); | 
|  | csc.OutputAssembly = new TaskItem(outputPath); | 
|  | csc.Sources = FindSources(Path.Combine(thriftDir, "gen-csharp")); | 
|  | csc.BuildEngine = this.BuildEngine; | 
|  | LogMessage("Compiling generated cs...", MessageImportance.Normal); | 
|  | if (!csc.Execute()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //write file to defDir to indicate a build was successfully completed | 
|  | File.WriteAllText(lastBuildPath, lastWrite); | 
|  |  | 
|  | thriftImpl = new TaskItem(outputPath); | 
|  |  | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } |