Dealing with csproj files was Re: [Mono-list] Compiling C# code with Mono.

Jaroslaw Kowalski jarek@atm.com.pl
Fri, 3 Jan 2003 21:03:27 +0100


This is a multi-part message in MIME format.

------=_NextPart_000_0023_01C2B36B.8FE5CD70
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

I have created a tool (attached) that converts VS.NET solutions to
Makefiles. It's quick and dirty, definitely not finished but works for me by
generating Windows makefiles from SLN (solution) files. Unix makefiles
should work too. Windows makefiles are for nmake and unix makefiles are for
GNU make.

You feed it with the name of solution file (.sln) and some options and it
prints out the makefile to stdout.

The options are:

-u    generate Unix version of the makefile (slashes instead of backslashes)
-w    generate Windows version
-c    don't generate "all" and "clean" targets
-t    don't generate project targets (i.e. targets named after project
names)
-f    don't generate default values for CSC and CSCFLAGS.

For each project in the solution it generates the following (so you need to
specify TARGET) when making:

PROJECT_NAME_EXE=$(TARGET)/ProjectName.exe
PROJECT_NAME_PDB=$(TARGET)/ProjectName.pdb
PROJECT_NAME_SRC=... source files that make up the project

plus the rule to compile it using $(CSC) as the compiler with $(CSCFLAGS) as
options to it. The rule contains dependencies on other DLLs in the solution.
Dependencies on DLLs not found in the solution are not written out. They are
instead just referenced.

The whole idea is to have the generated makefile included from some other
makefile, so you can just emit the rules to build projects, but nothing
else.
Or you can just invoke make like this:

"make -f generated_makefile.mak CSC=mcs CSCFLAGS=--optimize TARGET=/tmp"

Feel free to work on this tool, make it a full-blown application. You may
also include it in mono/mcs if you like.
The license is kind of BSD one.

Jarek

----- Original Message -----
From: "Rafael Teixeira" <rafaelteixeirabr@hotmail.com>
To: <mono-list@ximian.com>
Sent: Friday, January 03, 2003 11:38 AM
Subject: Dealing with csproj files was Re: [Mono-list] Compiling C# code
with Mono.


> Do we have a tool to convert csproj files to make/nant files? In truth a
> xslt transform would do, because a csproj is a xml file, do we have one?
>
> If not I'll do it.
>
> And about a tool to drive mcs from a csproj, do we have it? Would it be
> usefull?
>
> It's interesting but less so, because only people working in
Windows/VS.NET,
> that would like to recompile with mcs would need it, but as the whole
thing
> is to make the binaries fully compatible, why recompile? Maybe for testing
> mcs, but then the first solution, static conversion, would do, I think...
>
> Rafael Teixeira
> Brazilian Polymath
> Mono, MonoQLE Hacker
>
>
>
> _______________________________________________
> Mono-list maillist  -  Mono-list@ximian.com
> http://lists.ximian.com/mailman/listinfo/mono-list
>

------=_NextPart_000_0023_01C2B36B.8FE5CD70
Content-Type: text/plain;
	name="Maker.cs"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="Maker.cs"

/*

This file is a part of SLNToMake.

Copyright (c) 2002, 2003 Jaroslaw Kowalski <jaak@polbox.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without=20
modification, are permitted provided that the following conditions=20
are met:

* Redistributions of source code must retain the above copyright notice, =

this list of conditions and the following disclaimer.=20

* Redistributions in binary form must reproduce the above copyright =
notice,
this list of conditions and the following disclaimer in the =
documentation
and/or other materials provided with the distribution.=20

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS =
IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, =
THE=20
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR =
PURPOSE=20
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE =

LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR=20
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS =

INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN=20
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)=20
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF=20
THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Xml;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Reflection;

namespace SlnToMake
{
	class ProjectInfo
	{
		public readonly string name;
		public readonly string guid;
		public readonly string csprojpath;
		public string makename;
		public string makename_ext;
		public XmlDocument doc;
		public string assembly_name;
		public string src;

		public string ext_refs =3D "";
		public string switches =3D "";

		public ProjectInfo(string name, string guid, string csprojpath)
		{
			this.name =3D name;
			this.guid =3D guid;
			this.csprojpath =3D csprojpath;

			makename =3D name.Replace('.','_').ToUpper();
			makename_ext =3D makename + "_EXT";

			// convert backslashes to slashes
		=09
			csprojpath =3D csprojpath.Replace("\\", "/");

			doc =3D new XmlDocument();
			doc.Load(csprojpath);

			XmlElement settingsNode =3D =
(XmlElement)doc.SelectSingleNode("VisualStudioProject/CSHARP/Build/Settin=
gs");

			switch (settingsNode.GetAttribute("OutputType"))
			{
				case "Library":
					makename_ext =3D makename + "_DLL";
					assembly_name =3D settingsNode.GetAttribute("AssemblyName") + =
".dll";
					switches +=3D " /target:library";
					break;

				case "Exe":
					makename_ext =3D makename + "_EXE";
					assembly_name =3D settingsNode.GetAttribute("AssemblyName") + =
".exe";
					switches +=3D " /target:exe";
					break;

				case "WinExe":
					makename_ext =3D makename + "_EXE";
					assembly_name =3D settingsNode.GetAttribute("AssemblyName") + =
".exe";
					switches +=3D " /target:winexe";
					break;

				default:
					throw new NotSupportedException("Unsupported OutputType: " + =
settingsNode.GetAttribute("OutputType"));
			=09
			}

			src =3D "";

			string basePath =3D Path.GetDirectoryName(csprojpath);

			foreach (XmlElement el in =
doc.SelectNodes("VisualStudioProject/CSHARP/Files/Include/File[@BuildActi=
on=3D'Compile']"))
			{
				if (src !=3D "")
				{
					src +=3D " \\\n\t";
				};
				string s =3D String.Format(@"{0}{1}{2}", basePath, Maker.slash, =
el.GetAttribute("RelPath"));
				s =3D s.Replace("\\", "/");
				if (Maker.slash !=3D "/")
					s =3D s.Replace("/", Maker.slash);
				src +=3D s;
			=09
			}
		}
	}

	public class Maker
	{
		static Hashtable projNameInfo =3D new Hashtable();
		static Hashtable projGuidInfo =3D new Hashtable();
		public static string slash;
		public static bool unixMode =3D false;

		static void ParseSolution(string fname)
		{
			FileStream fis =3D new FileStream(fname,FileMode.Open, =
FileAccess.Read, FileShare.Read);
			StreamReader reader =3D new StreamReader(fis);
			Regex regex =3D new Regex(@"Project\(""\{(.*)\}""\) =3D ""(.*)"", =
""(.*)"", ""(\{.*\})""");

			while (true)
			{
				string s =3D reader.ReadLine();
				Match match;

				match =3D regex.Match(s);
				if (match.Success)
				{
					string projectName =3D match.Groups[2].Value;
					string csprojPath =3D match.Groups[3].Value;
					string projectGuid =3D match.Groups[4].Value;

					if (csprojPath.EndsWith(".csproj") && =
!csprojPath.StartsWith("http://"))
					{
						ProjectInfo pi =3D new ProjectInfo(projectName, projectGuid, =
csprojPath);

						projNameInfo[projectName] =3D pi;
						projGuidInfo[projectGuid] =3D pi;
					}
				};

				if (s.StartsWith("Global"))
				{
					break;
				};
			}
		}

		static int Usage()
		{
			Console.WriteLine("USAGE: SlnToMake.exe [-u (unix mode)|-w (windows =
mode)] [-t (no project targets)] filename.sln");
			return 1;
		}

		static int Main(string[] args)
		{
			int i =3D 0;
			bool noCommonTargets =3D false;
			bool noProjectTargets =3D false;
			bool noFlags =3D false;

			while (i < args.Length && args[i].StartsWith("-"))
			{
				switch (args[i][1])
				{
					case 'u':
						unixMode =3D true;
						i++;
						break;

					case 'w':
						unixMode =3D false;
						i++;
						break;

					case 'c':
						noCommonTargets =3D true;
						i++;
						break;

					case 't':
						noProjectTargets =3D true;
						i++;
						break;

					case 'f':
						noFlags =3D true;
						i++;
						break;

					default:
						return Usage();
				}
			}

			if (unixMode)
			{
				slash =3D "/";
			}
			else
			{
				slash =3D "\\";
			}

			if (i >=3D args.Length)
				return Usage();

			string sln =3D args[i];
			TextWriter makefile =3D null;

			makefile =3D Console.Out;

			try
			{
				string d =3D Path.GetDirectoryName(sln);
				if (d !=3D "")
					Directory.SetCurrentDirectory(d);
				ParseSolution(sln);

				if (unixMode)
				{
					makefile.WriteLine("ifndef TARGET");
					makefile.WriteLine("\terror You must provide TARGET when making");
					makefile.WriteLine("endif");
				}
				else
				{
					makefile.WriteLine("!if !defined(TARGET)");
					makefile.WriteLine("!error You must provide TARGET when making");
					makefile.WriteLine("!endif");
				}
				makefile.WriteLine();

				if (!noFlags)
				{
					if (unixMode)
					{
					}
					else
					{
						makefile.WriteLine("CSC=3Dcsc");
						makefile.WriteLine("CSCFLAGS=3D/nologo");
						makefile.WriteLine();
						makefile.WriteLine("!if defined(RELEASE)");
						makefile.WriteLine("CSCFLAGS=3D$(CSCFLAGS) /optimize+ /d:TRACE");
						makefile.WriteLine("!else");
						makefile.WriteLine("CSCFLAGS=3D$(CSCFLAGS) /debug+ =
/d:TRACE,DEBUG");
						makefile.WriteLine("!endif");
					}
					makefile.WriteLine();
				}
				else
				{
					makefile.WriteLine("!if !defined(CSC)");
					makefile.WriteLine("!error You must provide CSC when making");
					makefile.WriteLine("!endif");
					makefile.WriteLine();
				}

				foreach (ProjectInfo pi in projNameInfo.Values)
				{
					makefile.WriteLine("{0}=3D$(TARGET){1}{2}", pi.makename_ext, slash, =
pi.assembly_name);
					makefile.WriteLine("{0}_PDB=3D$(TARGET){1}{2}", pi.makename, slash, =
pi.assembly_name.Replace(".dll",".pdb"));
					makefile.WriteLine("{0}_SRC=3D{1}", pi.makename, pi.src);
					makefile.WriteLine();
				}

				foreach (ProjectInfo pi in projNameInfo.Values)
				{
					string refs =3D "";
					string deps =3D "";
					=09
					foreach (XmlElement el in =
pi.doc.SelectNodes("VisualStudioProject/CSHARP/Build/References/Reference=
"))
					{
						if (el.GetAttribute("Package") =3D=3D "")
						{
							if (refs !=3D "")
								refs +=3D " ";

							string assemblyName =3D el.GetAttribute("AssemblyName");

							// HACK - under Unix filenames are case sensitive
							// Under Windows there's no agreement on Xml vs XML ;-)
						=09
							if (0 =3D=3D String.Compare(assemblyName, "System.Xml", true))
							{
								assemblyName =3D "System.Xml";
							}
							refs +=3D "/r:" + assemblyName + ".dll";
						}
						else
						{
							ProjectInfo pi2 =3D =
(ProjectInfo)projGuidInfo[el.GetAttribute("Project")];

							if (refs !=3D "")
								refs +=3D " ";

							if (deps !=3D "")
								deps +=3D " ";

							refs +=3D "/r:$(" + pi2.makename_ext + ")";
							deps +=3D "$(" + pi2.makename_ext + ")";
						}
					}

					makefile.WriteLine("$({0}): $({1}_SRC) {2}", pi.makename_ext, =
pi.makename, deps);
					makefile.WriteLine("\t$(CSC) $(CSCFLAGS) {2}{3} /out:$({0}) =
$({1}_SRC)", pi.makename_ext, pi.makename, refs, pi.switches);
					makefile.WriteLine();
				}

				if (!noCommonTargets)
				{
					makefile.WriteLine();
					makefile.WriteLine("# common targets");
					makefile.WriteLine();
					makefile.Write("all:\t");

					bool first =3D true;

					foreach (ProjectInfo pi in projNameInfo.Values)
					{
						if (!first)
						{
							makefile.Write(" \\\n\t");
						};
						makefile.Write("$({0})", pi.makename_ext);
						first =3D false;
					}
					makefile.WriteLine();
					makefile.WriteLine();

					makefile.WriteLine("clean:");

					foreach (ProjectInfo pi in projNameInfo.Values)
					{
						if (unixMode)
						{
							makefile.WriteLine("\t-rm -f \"$({0})\" 2> /dev/null", =
pi.makename_ext);
							makefile.WriteLine("\t-rm -f \"$({0}_PDB)\" 2> /dev/null", =
pi.makename);
						}
						else
						{
							makefile.WriteLine("\t-del \"$({0})\" 2> nul", pi.makename_ext);
							makefile.WriteLine("\t-del \"$({0}_PDB)\" 2> nul", pi.makename);
						}
					}
					makefile.WriteLine();
				}

				if (!noProjectTargets)
				{
					makefile.WriteLine();
					makefile.WriteLine("# project names as targets");
					makefile.WriteLine();
					foreach (ProjectInfo pi in projNameInfo.Values)
					{
						makefile.WriteLine("{0}: $({1})", pi.name, pi.makename_ext);
					}
				}
				return 0;
			}
			catch (Exception e)
			{
				Console.WriteLine("EXCEPTION: {0}", e);
				return 1;
			};
		}
	}
}

------=_NextPart_000_0023_01C2B36B.8FE5CD70--