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/0
8/31/458658.aspx
-->
<!-- Use with:
msbuild
/p:CustomAfterMicrosoftCommonTargets="D:TempMonoMsbui
ldMono.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:TempMonoMsbuild</CscToolPath>
<VbcToolPath>D:TempMonoMsbuild</VbcToolPath>
<UseHostCompilerIfAvailable>false</UseHostCompilerI
fAvailable>
</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:TempMono.Comp
ilers.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:
//
// "absolutepathcompiler.exe" /noconfig "absolutepathrspfile.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 "absolutepathrspfile.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/defaul
t.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
FilesMono-1.2.5bingmcs.bat";
//const string VbncPath = "D:Program
FilesMono-1.2.5binvbnc.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&q
uot;" >>
// 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:WINDOWSMicrosoft.NETFrameworkv2.0.50727
Csc.exe" /noconfig "C:Documents and
SettingsandyLocal SettingsTemptmpEE.tmp">>
//
<<"C:WINDOWSMicrosoft.NETFrameworkv2.0.50727
Vbc.exe" /noconfig "C:Documents and
SettingsandyLocal SettingsTemptmp76.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(errorC
ode));
System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(errorC
ode.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(originCategoryCodeTextExpres
sion.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: ", 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: ,
clX:", 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.NETFrameworkv2.0.50727");
// Remove this assert if your windows directory is
not C:WINDOWS.
System.Diagnostics.Trace.Assert(fclPath
== "C:WINDOWSMicrosoft.NETFrameworkv2.0.50727&
quot;);
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\&q
uot;,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(" : ",
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-vb mailing list
Mono-vb lists.ximian.com
http
://lists.ximian.com/mailman/listinfo/mono-vb
|