I've seen requests in the past for how to have MSBuild use the Mono compilers. I attach a tool to allow this. It can be useful in troubleshooting to find whether a problem is with xbuild/monodevelop or with the compiler etc.
[[ // // Copyright (c) 2007 Andy Hume. // No restrictions, free for any use. // //============================================================================== // // This tool configures MSBuild to use the Mono compilers. This can be useful // in some situations. For instance I had an issue where neither xbuild nor // monodevelop would compile a project, but on using this tool the issue was shown // to be a compiler issue. // // Two parts are needed, firstly a .targets file to configure MSBuild to look in // a new location for the compilers. The second part is an executable that fulfills // two purposes, firstly MSBuild still looks for compiler filenames csc.exe and // vbc.exe. But secondly, neither of the Mono compilers support all the command- // line options used by the MSFT tools, nor are all the assemblies supported, so // we strip or convert both these -- for details see the code below. // // Note the MSFT versions of the other compiler utilities are still used e.g. resgen // etc. // // // So to use this tool, compile this program creating csc.exe and vbc.exe (e.g. // compile it to csc.exe and copy it to vbc.exe). Then take the xml content below, // update the paths and save it to e.g. Mono.Compilers.targets. // /* <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Based on: http://blogs.msdn.com/jomo_fisher/archive/2005/08/31/458658.aspx --> <!-- Use with: msbuild /p:CustomAfterMicrosoftCommonTargets="D:\Temp\MonoMsbuild\Mono.Compilers.targets" MySolution.sln Note: the targets file path must be absolute and there is no warning if the file isn't found. --> <PropertyGroup> <CscToolPath>D:\Temp\MonoMsbuild</CscToolPath> <VbcToolPath>D:\Temp\MonoMsbuild</VbcToolPath> <UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable> </PropertyGroup> </Project> */ // // That targets file can then be included in a MSBuild project to have it use the // Mono compilers. One way is to use a command-line of the following form: // // Msbuild /p:CustomAfterMicrosoftCommonTargets="D:\Temp\Mono.Compilers.targets" MySolution.sln // // Note: the targets file path must be absolute and there is no warning if the // file isn't found. // // It is also possible to reference the file from the project file. It is further // possible to always include that .targets file and make the content of the .targets // file itself conditional and thus the Mono compilers are used only when a certain // command-line flag is set -- this is how MSBee, the MSBuild FX1.1 builder from // http://www.codeplex.com/MSBee, is configured. // // // * How do I know it's working? // The tool logs the changes it makes to the compiler options, so see warnings // in the MSBuild output like: // MsBMono : warning XXX999: Was: <<.........>> // MsBMono : warning XXX999: Is now: <<.........>> // And if using the up-to-date vbnc case, see below, see also: // VBC : warning : VBNC2009: the option doc was not recognized - ignored // //============================================================================== // // * Changes // The three VBNC bugs mentioned below are all fixed (>=r86687), so those pieces of // functionality are unnecessary in the tool, so if using an up-to-date version // of vbnc, then in UnsupportedOptionsVbnc you must comment-out "define" and may // comment-out "doc". // // // What this program does. Firstly, as noted above the compilers must be files // named csc.exe and vbc.exe. Secondly it handles mapping command-line options // and assemblies to forms supported by Mono. // // The command-line passed from MSBuild is always of the simple form: // // "absolute\path\compiler.exe" /noconfig @"absolute\path\rspfile.tmp" // // From that we parse the compiler name, "csc" or "vbc", and also the path to the // response file, and in the end I we execute e.g. // // "gmcs.bat" /noconfig @"absolute\path\rspfile.tmp" // // However, as noted above we need to modify the command-line options which are // passed in the rsp file. For example gmcs doesn't support /errorprompt and vbnc // doesn't support /doc (though it says it ignores it (bug 325332). For options // in this category we just cut them to the first space character, but leave them // in place it any quote is found. // // However there is also one option that needs special-case handling. VB's "Compiler // Constants" (#defines) support values possibly including spaces, thus the command- // line option used by MSBuild is doubly-quoted, eg: // // /define:"AA=\"aaa\",BB=-1,CC=\"ccc\" // // However vbnc doesn't support this form (bug 325976), so we map this into a supported // form where possible. // // As well as options, we also support removing unsupprted assemblies. The assembly // System.Deployment.dll is referenced by Visual Studio Windows Forms projects // however Mono doesn't include it, by default it isn't used and we can thus remove // its reference. // // There is one remaining feature that could be added. In its current state vbnc // reports many of its errors in raw for or just as exception output and thus because // these are not reported in the standard error format xbuild, monodevelop, and // MSBuild all discard these errors and don't report them to the user (bug 328106). // The tool could be enhanced by detecting such raw errors and prefixing them with // a correctly formatted error prefix such as: // // MonoMsbuild: unhandled error MMB000: // // See http://channel9.msdn.com/wiki/default.aspx/MSBuild.CanonicalErrorWarningSpec // and http://blogs.msdn.com/msbuild/archive/2006/11/03/msbuild-visual-studio-aware-error-messages-and-message-formats.aspx // //============================================================================== using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; using System.Text.RegularExpressions; static class CscVbcCallMonoEquivalent { // // The paths to the Mono compilers, if they're in the path then no path is needed // for them here just "gmcs.bat" etc will suffice. // const string GmcsPath = @"gmcs.bat"; const string VbncPath = @"vbnc.bat"; // TODO ?Read these from somewhere. //const string GmcsPath = @"D:\Program Files\Mono-1.2.5\bin\gmcs.bat"; //const string VbncPath = @"D:\Program Files\Mono-1.2.5\bin\vbnc.bat"; // // The command-line options not supported by each of the Mono compilers. // static readonly String[] UnsupportedOptionsGmcs = { // <<error CS2007: Unrecognized command-line option: `/errorreport:prompt'>> "errorreport", }; static readonly String[] UnsupportedOptionsVbnc = { // vbnc can't handle quoted /define values in rsp files. With this content: // <</define:"CONFIG=\"Release\",TRACE=-1,_MyType=\"WindowsForms\",PLATFORM=\"AnyCPU\"" >> // it produces // <<An error message should have been shown: 'Invalid string constant: "AnyCPU"" /reference:..\MyAsmbly.dll>> // We strip all the quotes here. Hope there's no spaces etc! "define", // <<Error : VBNC2009: the option doc was not recognized - ignored>> // Says ignored but isn't! "doc", // Is supported "errorreport", }; // // Any assembly references we should remove. For example System.Deployment.dll // seems to be added by default for WinForms apps created in VS but it isn't used // by default, Mono doesn't include it so we need to remove its reference. // static readonly String[] UnsupportedAssemblies = { "System.Deployment.dll" }; //---- // The format of the command-line used by MSBuild when running the compiler. // Examples are // <<"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe" /noconfig @"C:\Documents and Settings\andy\Local Settings\Temp\tmpEE.tmp">> // <<"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Vbc.exe" /noconfig @"C:\Documents and Settings\andy\Local Settings\Temp\tmp76.tmp">> // The real compiler options are passed in the rsp file. const string MsBuildExecRxStr = "\".*\\\\([^\\\\]+).exe\"? /noconfig @\"([^\\\"]+)\""; //---- static string s_dbgDescr; //---- static int Main() { try { //Info( "Iaaaaaaaa"); //Warning("Waaaaaaaa"); //RegexPlaying(RxStr); //TestCountInstances(); //Console.WriteLine("----"); //---- // Parse the compiler (csc/vbc) and the rsp file passed. string cl = Environment.CommandLine; //Info("<<cl: " + cl + ">>"); string compiler, rspFilePath; ParseCmdLine(cl, out compiler, out rspFilePath); s_dbgDescr = compiler + "->" + rspFilePath; // Use that information to configure ourselves. string compilerPath; string[] badOptions; switch (compiler.ToLowerInvariant()) { case "csc": compilerPath = GmcsPath; badOptions = UnsupportedOptionsGmcs; break; case "vbc": compilerPath = VbncPath; badOptions = UnsupportedOptionsVbnc; break; default: Error("Unknown compiler '" + compiler + "'."); throw new ArgumentException(); } //---- // Recreate the rsp file with options to suit the equivalent Mono compiler. CleanOptionsFile(badOptions, rspFilePath); //---- // Now run the Mono compiler. string argsString = GetArgsString(); ProcessStartInfo psi = new ProcessStartInfo(compilerPath, argsString); psi.UseShellExecute = false; try { using (Process proc = Process.Start(psi)) { proc.WaitForExit(); return proc.ExitCode; }//using } catch (System.ComponentModel.Win32Exception winex) { // Make a better message... throw new System.ComponentModel.Win32Exception("Failed to run the compiler, probably exe/bat file not found.", winex); } } catch (Exception ex) { //ErrorNoExit(ex.ToString()); //throw; Error(ExceptionToStringMessage(ex)); throw new InvalidOperationException("Internal error -- Reached end of catch"); } } /// <summary> /// Get the first line of the exception ToString -- i.e. containing the type and message, /// and the same for all 'inner' exceptions. /// </summary> static string ExceptionToStringMessage(Exception ex) { System.Diagnostics.Debug.Assert(ex != null, "ExceptionToStringMessage--ArgNullEx"); int end = -1; string message = ex.ToString(); end = message.IndexOf('\n'); int tmp = message.IndexOf('\r'); if (tmp != -1 && tmp < end) { end = tmp; } if (end > -1) { message = message.Substring(0, end); } System.Diagnostics.Debug.Assert(message.IndexOfAny(new char[]{'\n','\r'}) == -1); return message; } enum MessageLevel { None = 0, // ToString'd on output, so nicer if lower case error, warning, info } static void ErrorNoExit(string message) { WriteErrorMessage(MessageLevel.error, message); } static void Error(string message) { ErrorNoExit(message); Environment.Exit(1); } static void Warning(string message) { MessageBox.Show(message, s_dbgDescr); WriteErrorMessage(MessageLevel.warning, message); } static void Info(string message) { // Info level messages are not displayed, so write as Warning. WriteErrorMessage(MessageLevel.warning, message); } const string ToolName = "MsBMono"; static void WriteErrorMessage(MessageLevel category, string text) { WriteErrorMessage(ToolName, null, category, "XXX999", text); } static void WriteErrorMessage(string origin, string subcategory, MessageLevel category, string errorCode, string text) { System.Text.StringBuilder bldr = new System.Text.StringBuilder(); if(!String.IsNullOrEmpty(origin)) { bldr.Append(origin).Append(": "); } if(!String.IsNullOrEmpty(subcategory)) { bldr.Append(subcategory).Append(" "); } bldr.Append(category.ToString()).Append(" "); System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(errorCode)); System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(errorCode.Trim())); bldr.Append(errorCode).Append(":"); if(!String.IsNullOrEmpty(text)) { bldr.Append(" ").Append(text); } string msg = bldr.ToString(); // Test for validity against MSBuild's regex. // (It doesn't allow 'info' etc, so only test for certain levels). if(category == MessageLevel.warning || category == MessageLevel.error) { System.Diagnostics.Debug.Assert(originCategoryCodeTextExpression.Match(msg).Success); } Console.WriteLine(msg); } // From the MSBuild wiki at channel9. This is apparently the pattern MSBuild // uses to check if a compiler output line is a well-formed error message. // Defines the main pattern for matching messages. static private Regex originCategoryCodeTextExpression = new Regex( // Beginning of line and any amount of whitespace. @"^\s*" // Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or // string with no colon followed by a colon. +@"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)" // Origin may also be empty. In this case there's no trailing colon. +"|())" // Match the empty string or a string without a colon that ends with a space +"(?<SUBCATEGORY>(()|([^:]*? )))" // Match 'error' or 'warning' followed by a space. +"(?<CATEGORY>(error|warning)) " // Match anything without a colon, followed by a colon +"(?<CODE>[^:]*):" // Whatever's left on this line, including colons. +"(?<TEXT>.*)$", RegexOptions.IgnoreCase); static void CleanOptionsFile(string[] badOptionsNames, string rspPath) { #if false // test exception handling try { throw new RankException("Iiiii iiii."); } catch(Exception ex) { throw new InvalidOperationException("Eee eeee eeee.", ex); } #endif string content; using (StreamReader rdr = File.OpenText(rspPath)) { content = rdr.ReadToEnd(); } Info( "Was: <<" + content + ">>"); content = RemoveUnsupportedArgsEtc(badOptionsNames, content); Info("Is now: <<" + content + ">>"); using (StreamWriter wtr = File.CreateText(rspPath)) { wtr.Write(content); } } static void ParseCmdLine(string cmdLine, out string compiler, out string rspFilePath) { //compiler = "csc"; //rspFilePath = "tmpB8.tmp.txt"; Regex rx = new Regex(MsBuildExecRxStr); Match m = rx.Match(cmdLine); if (!m.Success) { Error(@"Command-line not in expected " + @"{""<path>\<CCC>.exe"" /noconfig @""<rspFilePath>""} format."); } //DiagPrintMatch(m); if (m.Groups.Count != 3) { Error("Command-line regex didn't find two groups."); } //CaptureCollection cc = g.Captures; compiler = m.Groups[1].Captures[0].Value; rspFilePath = m.Groups[2].Captures[0].Value; } /// <summary>Gets the command-line argument as the original string. /// Uses <see cref="P:System.Environment.CommandLine"/> but removes /// the program name from the front. /// </summary> static string GetArgsString() { string[] cmdArray = Environment.GetCommandLineArgs(); string cl = Environment.CommandLine; string argsString; string cmd = cmdArray[0]; int idx = cl.IndexOf(cmd); //Console.WriteLine("idx: {0}", idx); if (idx == -1) { Error("cmd not in command-line"); throw new ArgumentException(); } int posArgs; if (idx == 0) { posArgs = cmd.Length + 0 + 1; } else if (idx == 1) { //Console.WriteLine("cl0: {0}, clX:{1}", cl[0], cl[cmd.Length + 2 - 1]); if (cl[0] == '"' && cl[cmd.Length + 2 - 1] == '"' && cl[cmd.Length + 2 - 1 + 1] == ' ') { posArgs = cmd.Length + 2 + 2; } else { Error("GetArgsString unsupported#1"); throw new ArgumentException(); } } else { Error("GetArgsString unsupported#2"); throw new ArgumentException(); } // Remove the program and leave only the arguments. argsString = cl.Substring(posArgs) + " "; return argsString; } static string RemoveUnsupportedArgsEtc(string[] badOptionsNames, String argsString) { argsString = RemoveUnsupportedOptions(badOptionsNames, argsString); // // Remove absolute path to FCL assemblies... // string Windir = Environment.GetEnvironmentVariable("windir"); // @"C:\WINDOWS"; string fclPath = Path.Combine(Windir, @"Microsoft.NET\Framework\v2.0.50727\"); // Remove this assert if your windows directory is not C:\WINDOWS. System.Diagnostics.Trace.Assert(fclPath == @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\"); argsString = argsString.Replace(fclPath, null); // // Remove unsupported assemblies -- not crucial hopefully... // foreach (string badAsmbly in UnsupportedAssemblies) { // csc: one ref per /reference: option argsString = argsString.Replace("/reference:" + badAsmbly, null); // vbc: list of refs in one /reference: option argsString = argsString.Replace("," + badAsmbly + ",", ","); } // return argsString; } static string RemoveUnsupportedOptions(string[] badOptionsNames, String argsString) { // Remove unsupported options // First calculate what to do and finally do it. if (badOptionsNames != null) { foreach (string badOptName in badOptionsNames) { // Remove from /optionName to terminating space int idxOption = argsString.IndexOf("/" + badOptName); if (idxOption == -1) { continue; } int idxEnd = argsString.IndexOf(" ", idxOption); if (idxEnd == -1) { // ?Either error, or its the last option, so chop to the end. Error("Strip option but no end space."); } string cut = argsString.Substring(idxOption, idxEnd - idxOption); string replace = null; // // Special case: /define for VB if (badOptName == "define") { // Convert /define:"a=\"a\",b=\"b\"" to /define:a=a,b=b // Needs to be in that simple form... // First the inner backslash-quoted quotes. int _countOfValueQuoting = CountInstances(cut, "\\\""); // a=\"aaa\" if ((_countOfValueQuoting & 1) == 1) { Warning("/define, odd number of value quotings."); continue; } replace = cut.Replace("\\\"", null); // Now the other quotes. int countOfQuotes = CountInstances(cut, "\""); // /define:"a=\"aaa\"" if ((countOfQuotes & 1) == 1) { Warning("/define, odd number of option quotes."); continue; } replace = replace.Replace("\"", null); } else if (cut.Contains("\"")) { // More work to do here? Do we need to support general // options with spaces and quoting etc. // /doc doesn't generally, so we're ok so far... // Warning("Skipping removing option as its value is string delimited"); continue; } // // Now actually do the cut/replace. Info("gonna cut: <<" + cut + ">>" + (replace == null ? null : " ** and put: <<" + replace + ">>")); string join = argsString.Substring(0, idxOption) + replace + argsString.Substring(idxEnd); argsString = join; } } return argsString; } /// <summary> /// Count the instances of a string in a given string value -- this is not otherwise /// supported AFAIK. /// </summary> static int CountInstances(/*this*/ string thisParam, string value) { int count = 0; int pos = 0; while (true) { int idx = thisParam.IndexOf(value, pos); if (idx == -1) { break; } ++count; pos = idx + value.Length; if (pos >= thisParam.Length) { break; } }//while return count; } static void TestCountInstances() { string[][] values = { new string[] { "/define:\"a=\\\"A\\\",b=\\\"B\\\",c=\\\"C\\\"", "\\\"", 6.ToString() }, new string[] { "/define:\"a=A,b=\\\"B\\\",c=C", "\\\"", 2.ToString() }, new string[] { "/define:\"aaa\\\"", "\\\"", 1.ToString() }, new string[] { "/define:\"a=A,b=B,c=C\"", "\\\"", 0.ToString() }, }; foreach (string[] cur in values) { int count = CountInstances(cur[0], cur[1]); Console.WriteLine("{0} {1}: {2} {3}", count == int.Parse(cur[2]), count, cur[0], cur[1] ); }//for } static void RegexPlaying(string rxStr) { Regex rx = new Regex(rxStr); Console.WriteLine("rxStr: " + rxStr); string tst = "\"C:\\Temp\\Csc.exe\" /noconfig @\"C:\\Temp\\tmpEE.tmp\""; Console.WriteLine("tst: " + tst); Match m = rx.Match(tst); Console.WriteLine("m: " + m); DiagPrintMatch(m); Console.WriteLine(); tst = "\"C:\\Temp\\XXXX YYY\\Csc.exe\" /noconfig @\"C:\\Temp\\tmpEE.tmp\""; Console.WriteLine("tst: " + tst); m = rx.Match(tst); Console.WriteLine("m: " + m); DiagPrintMatch(m); Console.WriteLine(); tst = "Csc.exe /noconfig @\"C:\\Temp\\tmpEE.tmp\""; Console.WriteLine("tst: " + tst); m = rx.Match(tst); Console.WriteLine("m: " + m); DiagPrintMatch(m); Console.WriteLine(); tst = "Csc.exe /noconfig @\"C:\\Temp\\tmpEE.tmp\""; Console.WriteLine("tst: " + tst); m = rx.Match(tst); Console.WriteLine("m: " + m); DiagPrintMatch(m); } static void DiagPrintMatch(Match m) { Console.WriteLine("success: " + m.Success); GroupCollection grps = m.Groups; for (int i = 0; i < m.Groups.Count; i++) { Group g = m.Groups[i]; Console.WriteLine("Group" + i + "='" + g + "'"); CaptureCollection cc = g.Captures; for (int j = 0; j < cc.Count; j++) { Capture c = cc[j]; System.Console.WriteLine("Capture" + j + "='" + c + "', Position=" + c.Index); } } } }//class ]] _______________________________________________ Mono-list maillist - Mono-list@lists.ximian.com http://lists.ximian.com/mailman/listinfo/mono-list