blob: 4389e0a6e40336adeb7d4c7fa769241ee308c8b4 [file] [log] [blame]
Gavin McDonald0b75e1a2010-10-28 02:12:01 +00001/**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20using System;
21using System.Collections.Generic;
22using System.Linq;
23using System.Text;
24using Microsoft.Build.Framework;
25using Microsoft.Build.Utilities;
26using Microsoft.Build.Tasks;
27using System.IO;
28using System.Diagnostics;
29
30namespace ThriftMSBuildTask
31{
32 /// <summary>
33 /// MSBuild Task to generate csharp from .thrift files, and compile the code into a library: ThriftImpl.dll
34 /// </summary>
35 public class ThriftBuild : Task
36 {
37 /// <summary>
38 /// The full path to the thrift.exe compiler
39 /// </summary>
40 [Required]
41 public ITaskItem ThriftExecutable
42 {
43 get;
44 set;
45 }
46
47 /// <summary>
48 /// The full path to a thrift.dll C# library
49 /// </summary>
50 [Required]
51 public ITaskItem ThriftLibrary
52 {
53 get;
54 set;
55 }
56
57 /// <summary>
58 /// A direcotry containing .thrift files
59 /// </summary>
60 [Required]
61 public ITaskItem ThriftDefinitionDir
62 {
63 get;
64 set;
65 }
66
67 /// <summary>
68 /// The name of the auto-gen and compiled thrift library. It will placed in
69 /// the same directory as ThriftLibrary
70 /// </summary>
71 [Required]
72 public ITaskItem OutputName
73 {
74 get;
75 set;
76 }
77
78 /// <summary>
79 /// The full path to the compiled ThriftLibrary. This allows msbuild tasks to use this
80 /// output as a variable for use elsewhere.
81 /// </summary>
82 [Output]
83 public ITaskItem ThriftImplementation
84 {
85 get { return thriftImpl; }
86 }
87
88 private ITaskItem thriftImpl;
89 private const string lastCompilationName = "LAST_COMP_TIMESTAMP";
90
91 //use the Message Build Task to write something to build log
92 private void LogMessage(string text, MessageImportance importance)
93 {
94 Message m = new Message();
95 m.Text = text;
96 m.Importance = importance.ToString();
97 m.BuildEngine = this.BuildEngine;
98 m.Execute();
99 }
100
101 //recursively find .cs files in srcDir, paths should initially be non-null and empty
102 private void FindSourcesHelper(string srcDir, List<string> paths)
103 {
104 string[] files = Directory.GetFiles(srcDir, "*.cs");
105 foreach (string f in files)
106 {
107 paths.Add(f);
108 }
109 string[] dirs = Directory.GetDirectories(srcDir);
110 foreach (string dir in dirs)
111 {
112 FindSourcesHelper(dir, paths);
113 }
114 }
115
116 /// <summary>
117 /// Quote paths with spaces
118 /// </summary>
119 private string SafePath(string path)
120 {
121 if (path.Contains(' ') && !path.StartsWith("\""))
122 {
123 return "\"" + path + "\"";
124 }
125 return path;
126 }
127
128 private ITaskItem[] FindSources(string srcDir)
129 {
130 List<string> files = new List<string>();
131 FindSourcesHelper(srcDir, files);
132 ITaskItem[] items = new ITaskItem[files.Count];
133 for (int i = 0; i < items.Length; i++)
134 {
135 items[i] = new TaskItem(files[i]);
136 }
137 return items;
138 }
139
140 private string LastWriteTime(string defDir)
141 {
142 string[] files = Directory.GetFiles(defDir, "*.thrift");
143 DateTime d = (new DirectoryInfo(defDir)).LastWriteTime;
144 foreach(string file in files)
145 {
146 FileInfo f = new FileInfo(file);
147 DateTime curr = f.LastWriteTime;
148 if (DateTime.Compare(curr, d) > 0)
149 {
150 d = curr;
151 }
152 }
153 return d.ToFileTimeUtc().ToString();
154 }
155
156 public override bool Execute()
157 {
158 string defDir = SafePath(ThriftDefinitionDir.ItemSpec);
159 //look for last compilation timestamp
160 string lastBuildPath = Path.Combine(defDir, lastCompilationName);
161 DirectoryInfo defDirInfo = new DirectoryInfo(defDir);
162 string lastWrite = LastWriteTime(defDir);
163 if (File.Exists(lastBuildPath))
164 {
165 string lastComp = File.ReadAllText(lastBuildPath);
166 //don't recompile if the thrift library has been updated since lastComp
167 FileInfo f = new FileInfo(ThriftLibrary.ItemSpec);
168 string thriftLibTime = f.LastWriteTimeUtc.ToFileTimeUtc().ToString();
169 if (lastComp.CompareTo(thriftLibTime) < 0)
170 {
171 //new thrift library, do a compile
172 lastWrite = thriftLibTime;
173 }
174 else if (lastComp == lastWrite || (lastComp == thriftLibTime && lastComp.CompareTo(lastWrite) > 0))
175 {
176 //the .thrift dir hasn't been written to since last compilation, don't need to do anything
177 LogMessage("ThriftImpl up-to-date", MessageImportance.High);
178 return true;
179 }
180 }
181
182 //find the directory of the thriftlibrary (that's where output will go)
183 FileInfo thriftLibInfo = new FileInfo(SafePath(ThriftLibrary.ItemSpec));
184 string thriftDir = thriftLibInfo.Directory.FullName;
185
186 string genDir = Path.Combine(thriftDir, "gen-csharp");
187 if (Directory.Exists(genDir))
188 {
189 try
190 {
191 Directory.Delete(genDir, true);
192 }
193 catch { /*eh i tried, just over-write now*/}
194 }
195
196 //run the thrift executable to generate C#
197 foreach (string thriftFile in Directory.GetFiles(defDir, "*.thrift"))
198 {
199 LogMessage("Generating code for: " + thriftFile, MessageImportance.Normal);
200 Process p = new Process();
201 p.StartInfo.FileName = SafePath(ThriftExecutable.ItemSpec);
202 p.StartInfo.Arguments = "--gen csharp -o " + SafePath(thriftDir) + " -r " + thriftFile;
203 p.StartInfo.UseShellExecute = false;
204 p.StartInfo.CreateNoWindow = true;
205 p.StartInfo.RedirectStandardOutput = false;
206 p.Start();
207 p.WaitForExit();
208 if (p.ExitCode != 0)
209 {
210 LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High);
211 return false;
212 }
213 if (p.ExitCode != 0)
214 {
215 LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High);
216 return false;
217 }
218 }
219
220 Csc csc = new Csc();
221 csc.TargetType = "library";
222 csc.References = new ITaskItem[] { new TaskItem(ThriftLibrary.ItemSpec) };
223 csc.EmitDebugInformation = true;
224 string outputPath = Path.Combine(thriftDir, OutputName.ItemSpec);
225 csc.OutputAssembly = new TaskItem(outputPath);
226 csc.Sources = FindSources(Path.Combine(thriftDir, "gen-csharp"));
227 csc.BuildEngine = this.BuildEngine;
228 LogMessage("Compiling generated cs...", MessageImportance.Normal);
229 if (!csc.Execute())
230 {
231 return false;
232 }
233
234 //write file to defDir to indicate a build was successfully completed
235 File.WriteAllText(lastBuildPath, lastWrite);
236
237 thriftImpl = new TaskItem(outputPath);
238
239 return true;
240 }
241 }
242}