blob: 69ae8d3eed02da947cf179e4b06c3aea2ddcfef4 [file] [log] [blame]
David Reiss7f42bcf2008-01-11 20:59:12 +00001using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using Microsoft.Build.Framework;
6using Microsoft.Build.Utilities;
7using Microsoft.Build.Tasks;
8using System.IO;
9using System.Diagnostics;
10
11namespace ThriftMSBuildTask
12{
13 /// <summary>
14 /// MSBuild Task to generate csharp from .thrift files, and compile the code into a library: ThriftImpl.dll
15 /// </summary>
16 public class ThriftBuild : Task
17 {
18 /// <summary>
19 /// The full path to the thrift.exe compiler
20 /// </summary>
21 [Required]
22 public ITaskItem ThriftExecutable
23 {
24 get;
25 set;
26 }
27
28 /// <summary>
29 /// The full path to a thrift.dll C# library
30 /// </summary>
31 [Required]
32 public ITaskItem ThriftLibrary
33 {
34 get;
35 set;
36 }
37
38 /// <summary>
39 /// A direcotry containing .thrift files
40 /// </summary>
41 [Required]
42 public ITaskItem ThriftDefinitionDir
43 {
44 get;
45 set;
46 }
47
48 /// <summary>
49 /// The name of the auto-gen and compiled thrift library. It will placed in
50 /// the same directory as ThriftLibrary
51 /// </summary>
52 [Required]
53 public ITaskItem OutputName
54 {
55 get;
56 set;
57 }
58
59 /// <summary>
60 /// The full path to the compiled ThriftLibrary. This allows msbuild tasks to use this
61 /// output as a variable for use elsewhere.
62 /// </summary>
63 [Output]
64 public ITaskItem ThriftImplementation
65 {
66 get { return thriftImpl; }
67 }
68
69 private ITaskItem thriftImpl;
70 private const string lastCompilationName = "LAST_COMP_TIMESTAMP";
71
72 //use the Message Build Task to write something to build log
73 private void LogMessage(string text, MessageImportance importance)
74 {
75 Message m = new Message();
76 m.Text = text;
77 m.Importance = importance.ToString();
78 m.BuildEngine = this.BuildEngine;
79 m.Execute();
80 }
81
82 //recursively find .cs files in srcDir, paths should initially be non-null and empty
83 private void FindSourcesHelper(string srcDir, List<string> paths)
84 {
85 string[] files = Directory.GetFiles(srcDir, "*.cs");
86 foreach (string f in files)
87 {
88 paths.Add(f);
89 }
90 string[] dirs = Directory.GetDirectories(srcDir);
91 foreach (string dir in dirs)
92 {
93 FindSourcesHelper(dir, paths);
94 }
95 }
96
97 /// <summary>
98 /// Quote paths with spaces
99 /// </summary>
100 private string SafePath(string path)
101 {
102 if (path.Contains(' ') && !path.StartsWith("\""))
103 {
104 return "\"" + path + "\"";
105 }
106 return path;
107 }
108
109 private ITaskItem[] FindSources(string srcDir)
110 {
111 List<string> files = new List<string>();
112 FindSourcesHelper(srcDir, files);
113 ITaskItem[] items = new ITaskItem[files.Count];
114 for (int i = 0; i < items.Length; i++)
115 {
116 items[i] = new TaskItem(files[i]);
117 }
118 return items;
119 }
120
121 private string LastWriteTime(string defDir)
122 {
123 string[] files = Directory.GetFiles(defDir, "*.thrift");
124 DateTime d = (new DirectoryInfo(defDir)).LastWriteTime;
125 foreach(string file in files)
126 {
127 FileInfo f = new FileInfo(file);
128 DateTime curr = f.LastWriteTime;
129 if (DateTime.Compare(curr, d) > 0)
130 {
131 d = curr;
132 }
133 }
134 return d.ToFileTimeUtc().ToString();
135 }
136
137 public override bool Execute()
138 {
139 string defDir = SafePath(ThriftDefinitionDir.ItemSpec);
140 //look for last compilation timestamp
141 string lastBuildPath = Path.Combine(defDir, lastCompilationName);
142 DirectoryInfo defDirInfo = new DirectoryInfo(defDir);
143 string lastWrite = LastWriteTime(defDir);
144 if (File.Exists(lastBuildPath))
145 {
146 string lastComp = File.ReadAllText(lastBuildPath);
David Reissfc78b232008-03-18 18:22:52 +0000147 //don't recompile if the thrift library has been updated since lastComp
148 FileInfo f = new FileInfo(ThriftLibrary.ItemSpec);
149 string thriftLibTime = f.LastWriteTimeUtc.ToFileTimeUtc().ToString();
150 if (lastComp.CompareTo(thriftLibTime) < 0)
151 {
152 //new thrift library, do a compile
153 lastWrite = thriftLibTime;
154 }
155 else if (lastComp == lastWrite || (lastComp == thriftLibTime && lastComp.CompareTo(lastWrite) > 0))
David Reiss7f42bcf2008-01-11 20:59:12 +0000156 {
157 //the .thrift dir hasn't been written to since last compilation, don't need to do anything
David Reissfc78b232008-03-18 18:22:52 +0000158 LogMessage("ThriftImpl up-to-date", MessageImportance.High);
David Reiss7f42bcf2008-01-11 20:59:12 +0000159 return true;
160 }
161 }
162
163 //find the directory of the thriftlibrary (that's where output will go)
164 FileInfo thriftLibInfo = new FileInfo(SafePath(ThriftLibrary.ItemSpec));
165 string thriftDir = thriftLibInfo.Directory.FullName;
166
167 string genDir = Path.Combine(thriftDir, "gen-csharp");
168 if (Directory.Exists(genDir))
169 {
David Reissfc78b232008-03-18 18:22:52 +0000170 try
171 {
172 Directory.Delete(genDir, true);
173 }
174 catch { /*eh i tried, just over-write now*/}
David Reiss7f42bcf2008-01-11 20:59:12 +0000175 }
David Reissfc78b232008-03-18 18:22:52 +0000176
David Reiss7f42bcf2008-01-11 20:59:12 +0000177 //run the thrift executable to generate C#
178 foreach (string thriftFile in Directory.GetFiles(defDir, "*.thrift"))
179 {
180 LogMessage("Generating code for: " + thriftFile, MessageImportance.Normal);
181 Process p = new Process();
182 p.StartInfo.FileName = SafePath(ThriftExecutable.ItemSpec);
183 p.StartInfo.Arguments = "-csharp -o " + SafePath(thriftDir) + " -r " + thriftFile;
184 p.StartInfo.UseShellExecute = false;
185 p.StartInfo.CreateNoWindow = true;
186 p.StartInfo.RedirectStandardOutput = false;
187 p.Start();
188 p.WaitForExit();
Mark Slee1e150182008-01-22 04:04:30 +0000189 if (p.ExitCode != 0)
190 {
191 LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High);
192 return false;
193 }
David Reissfc78b232008-03-18 18:22:52 +0000194 if (p.ExitCode != 0)
195 {
196 LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High);
197 return false;
198 }
David Reiss7f42bcf2008-01-11 20:59:12 +0000199 }
200
201 Csc csc = new Csc();
202 csc.TargetType = "library";
203 csc.References = new ITaskItem[] { new TaskItem(ThriftLibrary.ItemSpec) };
204 csc.EmitDebugInformation = true;
205 string outputPath = Path.Combine(thriftDir, OutputName.ItemSpec);
206 csc.OutputAssembly = new TaskItem(outputPath);
207 csc.Sources = FindSources(Path.Combine(thriftDir, "gen-csharp"));
208 csc.BuildEngine = this.BuildEngine;
209 LogMessage("Compiling generated cs...", MessageImportance.Normal);
210 if (!csc.Execute())
211 {
212 return false;
213 }
214
215 //write file to defDir to indicate a build was successfully completed
216 File.WriteAllText(lastBuildPath, lastWrite);
217
218 thriftImpl = new TaskItem(outputPath);
219
220 return true;
221 }
222 }
223}