Thrift: C# Bindings.

Summary:
C# generator, library, and MS Build task contributed by imeem.

Reviewed By: mcslee

Test Plan:
Built the Thrift compiler and generated some C# code.
I'd love to say I installed Mono or Portable.NET and built the C# code,
but I did not.

Revert Plan: ok


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665421 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs b/lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4375a52
--- /dev/null
+++ b/lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ThriftMSBuildTask")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ThriftMSBuildTask")]
+[assembly: AssemblyCopyright("Copyright ©  2007")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5095e09d-7b95-4be1-b250-e1c1db1c485e")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/lib/csharp/ThriftMSBuildTask/ThriftBuild.cs b/lib/csharp/ThriftMSBuildTask/ThriftBuild.cs
new file mode 100644
index 0000000..9cf5f41
--- /dev/null
+++ b/lib/csharp/ThriftMSBuildTask/ThriftBuild.cs
@@ -0,0 +1,202 @@
+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);
+
+				if (lastComp == lastWrite)
+				{
+					//the .thrift dir hasn't been written to since last compilation, don't need to do anything
+					LogMessage("ThriftImpl up-to-date", MessageImportance.Normal);
+					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))
+			{
+				Directory.Delete(genDir, true);
+			}
+			
+			//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();
+			}
+
+			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;
+		}
+	}
+}
diff --git a/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj b/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj
new file mode 100644
index 0000000..68c6bd1
--- /dev/null
+++ b/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>9.0.21022</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{EC0A0231-66EA-4593-A792-C6CA3BB8668E}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ThriftMSBuildTask</RootNamespace>
+    <AssemblyName>ThriftMSBuildTask</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SccProjectName>SAK</SccProjectName>
+    <SccLocalPath>SAK</SccLocalPath>
+    <SccAuxPath>SAK</SccAuxPath>
+    <SccProvider>SAK</SccProvider>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Build.Framework" />
+    <Reference Include="Microsoft.Build.Tasks" />
+    <Reference Include="Microsoft.Build.Utilities" />
+    <Reference Include="System" />
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data.DataSetExtensions">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ThriftBuild.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file