[System.Xml] Add support for msxsl format functions. Fixes #50242
[mono-project.git] / msvc / scripts / genproj.cs
blobbf450539688c65b76e790b6beae92a2971d2f91d
1 //
2 // Consumes the order.xml file that contains a list of all the assemblies to build
3 // and produces a solution and the csproj files for it
4 //
5 // Currently this hardcodes a set of assemblies to build, the net-4.x series, but
6 // it can be extended to handle the command line tools.
7 //
8 // KNOWN ISSUES:
9 // * This fails to find matches for "System" and "System.xml" when processing the
10 // RabbitMQ executable, likely, because we do not process executables yet
12 // * Has not been tested in a while with the command line tools
14 using System;
15 using System.IO;
16 using System.Collections.Generic;
17 using System.Text;
18 using System.Globalization;
19 using System.Xml.Linq;
20 using System.Xml.XPath;
21 using System.Linq;
22 using System.Xml;
24 public enum Target {
25 Library, Exe, Module, WinExe
28 public enum LanguageVersion {
29 ISO_1 = 1,
30 Default_MCS = 2,
31 ISO_2 = 3,
32 LINQ = 4,
33 Future = 5,
34 Default = LINQ
37 class SlnGenerator {
38 public static readonly string NewLine = "\r\n"; //Environment.NewLine; // "\n";
39 public SlnGenerator (string formatVersion = "2012")
41 switch (formatVersion) {
42 case "2008":
43 this.header = MakeHeader ("10.00", "2008");
44 break;
45 default:
46 this.header = MakeHeader ("12.00", "2012");
47 break;
51 const string project_start = "Project(\"{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}\") = \"{0}\", \"{1}\", \"{2}\""; // Note: No need to double up on {} around {2}
52 const string project_end = "EndProject";
54 public List<MsbuildGenerator.VsCsproj> libraries = new List<MsbuildGenerator.VsCsproj> ();
55 string header;
57 string MakeHeader (string formatVersion, string yearTag)
59 return string.Format ("Microsoft Visual Studio Solution File, Format Version {0}" + NewLine + "# Visual Studio {1}", formatVersion, yearTag);
62 public void Add (MsbuildGenerator.VsCsproj vsproj)
64 try {
65 libraries.Add (vsproj);
66 } catch (Exception ex) {
67 Console.WriteLine (ex);
71 public void Write (string filename)
73 var fullPath = Path.GetDirectoryName (filename) + "/";
75 using (var sln = new StreamWriter (filename)) {
76 sln.WriteLine ();
77 sln.WriteLine (header);
78 foreach (var proj in libraries) {
79 var unixProjFile = proj.csProjFilename.Replace ("\\", "/");
80 var fullProjPath = Path.GetFullPath (unixProjFile);
81 sln.WriteLine (project_start, proj.library, MsbuildGenerator.GetRelativePath (fullPath, fullProjPath), proj.projectGuid);
82 sln.WriteLine (project_end);
84 sln.WriteLine ("Global");
86 sln.WriteLine ("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
87 sln.WriteLine ("\t\tDebug|Any CPU = Debug|Any CPU");
88 sln.WriteLine ("\t\tRelease|Any CPU = Release|Any CPU");
89 sln.WriteLine ("\tEndGlobalSection");
91 sln.WriteLine ("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
92 foreach (var proj in libraries) {
93 var guid = proj.projectGuid;
94 sln.WriteLine ("\t\t{0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", guid);
95 sln.WriteLine ("\t\t{0}.Debug|Any CPU.Build.0 = Debug|Any CPU", guid);
96 sln.WriteLine ("\t\t{0}.Release|Any CPU.ActiveCfg = Release|Any CPU", guid);
97 sln.WriteLine ("\t\t{0}.Release|Any CPU.Build.0 = Release|Any CPU", guid);
99 sln.WriteLine ("\tEndGlobalSection");
101 sln.WriteLine ("\tGlobalSection(SolutionProperties) = preSolution");
102 sln.WriteLine ("\t\tHideSolutionNode = FALSE");
103 sln.WriteLine ("\tEndGlobalSection");
105 sln.WriteLine ("EndGlobal");
109 internal bool ContainsProjectIdentifier (string projId)
111 return libraries.FindIndex (x => (x.library == projId)) >= 0;
114 public int Count { get { return libraries.Count; } }
117 class MsbuildGenerator {
118 static readonly string NewLine = SlnGenerator.NewLine;
119 static XmlNamespaceManager xmlns;
121 public const string profile_2_0 = "_2_0";
122 public const string profile_3_5 = "_3_5";
123 public const string profile_4_0 = "_4_0";
124 public const string profile_4_x = "_4_x";
126 static void Usage ()
128 Console.WriteLine ("Invalid argument");
131 static string template;
132 static MsbuildGenerator ()
134 using (var input = new StreamReader ("csproj.tmpl")) {
135 template = input.ReadToEnd ();
138 xmlns = new XmlNamespaceManager (new NameTable ());
139 xmlns.AddNamespace ("x", "http://schemas.microsoft.com/developer/msbuild/2003");
142 // The directory as specified in order.xml
143 public string dir;
144 string library;
145 string projectGuid;
146 string fx_version;
148 XElement xproject;
149 public string CsprojFilename;
152 // Our base directory, this is relative to our exectution point mono/msvc/scripts
153 string base_dir;
154 string mcs_topdir;
156 public string LibraryOutput, AbsoluteLibraryOutput;
158 public MsbuildGenerator (XElement xproject)
160 this.xproject = xproject;
161 dir = xproject.Attribute ("dir").Value;
162 library = xproject.Attribute ("library").Value;
163 CsprojFilename = "..\\..\\mcs\\" + dir + "\\" + library + ".csproj";
164 LibraryOutput = xproject.Element ("library_output").Value;
166 projectGuid = LookupOrGenerateGuid ();
167 fx_version = xproject.Element ("fx_version").Value;
168 Csproj = new VsCsproj () {
169 csProjFilename = this.CsprojFilename,
170 projectGuid = this.projectGuid,
171 library_output = this.LibraryOutput,
172 fx_version = double.Parse (fx_version),
173 library = this.library,
174 MsbuildGenerator = this
177 if (dir == "mcs") {
178 mcs_topdir = "../";
179 class_dir = "../class/";
180 base_dir = "../../mcs/mcs";
181 } else {
182 mcs_topdir = "../";
184 foreach (char c in dir) {
185 if (c == '/')
186 mcs_topdir = "..//" + mcs_topdir;
188 class_dir = mcs_topdir.Substring (3);
190 base_dir = Path.Combine ("..", "..", "mcs", dir);
192 AbsoluteLibraryOutput = Path.GetFullPath (Path.Combine (base_dir, LibraryOutput));
195 string LookupOrGenerateGuid ()
197 var projectFile = NativeName (CsprojFilename);
198 if (File.Exists (projectFile)){
199 var doc = XDocument.Load (projectFile);
200 return doc.XPathSelectElement ("x:Project/x:PropertyGroup/x:ProjectGuid", xmlns).Value;
202 return "{" + Guid.NewGuid ().ToString ().ToUpper () + "}";
205 // Currently used
206 bool Unsafe = false;
207 StringBuilder defines = new StringBuilder ();
208 bool Optimize = true;
209 bool want_debugging_support = false;
210 string main = null;
211 Dictionary<string, string> embedded_resources = new Dictionary<string, string> ();
212 List<string> warning_as_error = new List<string> ();
213 List<int> ignore_warning = new List<int> ();
214 bool load_default_config = true;
215 bool StdLib = true;
216 List<string> references = new List<string> ();
217 List<string> libs = new List<string> ();
218 List<string> reference_aliases = new List<string> ();
219 bool showWarnings = true;
221 // Currently unused
222 #pragma warning disable 0219, 0414
223 int WarningLevel = 4;
225 bool Checked = false;
226 bool WarningsAreErrors;
227 bool VerifyClsCompliance = true;
228 string win32IconFile;
229 string StrongNameKeyFile;
230 bool copyLocal = true;
231 Target Target = Target.Library;
232 string TargetExt = ".exe";
233 string OutputFile;
234 string StrongNameKeyContainer;
235 bool StrongNameDelaySign = false;
236 LanguageVersion Version = LanguageVersion.Default;
237 string CodePage;
239 // Class directory, relative to
240 string class_dir;
241 #pragma warning restore 0219,414
243 readonly char [] argument_value_separator = new char [] { ';', ',' };
246 // This parses the -arg and /arg options to the compiler, even if the strings
247 // in the following text use "/arg" on the strings.
249 bool CSCParseOption (string option, ref string [] args)
251 int idx = option.IndexOf (':');
252 string arg, value;
254 if (idx == -1) {
255 arg = option;
256 value = "";
257 } else {
258 arg = option.Substring (0, idx);
260 value = option.Substring (idx + 1);
263 switch (arg.ToLower (CultureInfo.InvariantCulture)) {
264 case "/nologo":
265 return true;
267 case "/t":
268 case "/target":
269 switch (value) {
270 case "exe":
271 Target = Target.Exe;
272 break;
274 case "winexe":
275 Target = Target.WinExe;
276 break;
278 case "library":
279 Target = Target.Library;
280 TargetExt = ".dll";
281 break;
283 case "module":
284 Target = Target.Module;
285 TargetExt = ".netmodule";
286 break;
288 default:
289 return false;
291 return true;
293 case "/out":
294 if (value.Length == 0) {
295 Usage ();
296 Environment.Exit (1);
298 OutputFile = value;
299 return true;
301 case "/o":
302 case "/o+":
303 case "/optimize":
304 case "/optimize+":
305 Optimize = true;
306 return true;
308 case "/o-":
309 case "/optimize-":
310 Optimize = false;
311 return true;
313 case "/incremental":
314 case "/incremental+":
315 case "/incremental-":
316 // nothing.
317 return true;
319 case "/d":
320 case "/define": {
321 if (value.Length == 0) {
322 Usage ();
323 Environment.Exit (1);
326 foreach (string d in value.Split (argument_value_separator)) {
327 if (defines.Length != 0)
328 defines.Append (";");
329 defines.Append (d);
332 return true;
335 case "/bugreport":
337 // We should collect data, runtime, etc and store in the file specified
339 return true;
340 case "/linkres":
341 case "/linkresource":
342 case "/res":
343 case "/resource":
344 bool embeded = arg [1] == 'r' || arg [1] == 'R';
345 string [] s = value.Split (argument_value_separator);
346 switch (s.Length) {
347 case 1:
348 if (s [0].Length == 0)
349 goto default;
350 embedded_resources [s [0]] = Path.GetFileName (s [0]);
351 break;
352 case 2:
353 embedded_resources [s [0]] = s [1];
354 break;
355 case 3:
356 Console.WriteLine ("Does not support this method yet: {0}", arg);
357 Environment.Exit (1);
358 break;
359 default:
360 Console.WriteLine ("Wrong number of arguments for option `{0}'", option);
361 Environment.Exit (1);
362 break;
365 return true;
367 case "/recurse":
368 Console.WriteLine ("/recurse not supported");
369 Environment.Exit (1);
370 return true;
372 case "/r":
373 case "/reference": {
374 if (value.Length == 0) {
375 Console.WriteLine ("-reference requires an argument");
376 Environment.Exit (1);
379 string [] refs = value.Split (argument_value_separator);
380 foreach (string r in refs) {
381 string val = r;
382 int index = val.IndexOf ('=');
383 if (index > -1) {
384 reference_aliases.Add (r);
385 continue;
388 if (val.Length != 0)
389 references.Add (val);
391 return true;
393 case "/main":
394 main = value;
395 return true;
397 case "/m":
398 case "/addmodule":
399 case "/win32res":
400 case "/doc":
401 if (showWarnings)
402 Console.WriteLine ("{0} = not supported", arg);
403 return true;
405 case "/lib": {
406 libs.Add (value);
407 return true;
409 case "/win32icon": {
410 win32IconFile = value;
411 return true;
413 case "/debug-":
414 want_debugging_support = false;
415 return true;
417 case "/debug":
418 case "/debug+":
419 want_debugging_support = true;
420 return true;
422 case "/checked":
423 case "/checked+":
424 Checked = true;
425 return true;
427 case "/checked-":
428 Checked = false;
429 return true;
431 case "/clscheck":
432 case "/clscheck+":
433 return true;
435 case "/clscheck-":
436 VerifyClsCompliance = false;
437 return true;
439 case "/unsafe":
440 case "/unsafe+":
441 Unsafe = true;
442 return true;
444 case "/unsafe-":
445 Unsafe = false;
446 return true;
448 case "/warnaserror":
449 case "/warnaserror+":
450 if (value.Length == 0) {
451 WarningsAreErrors = true;
452 } else {
453 foreach (string wid in value.Split (argument_value_separator))
454 warning_as_error.Add (wid);
456 return true;
458 case "/-runtime":
459 // Console.WriteLine ("Warning ignoring /runtime:v4");
460 return true;
462 case "/warnaserror-":
463 if (value.Length == 0) {
464 WarningsAreErrors = false;
465 } else {
466 foreach (string wid in value.Split (argument_value_separator))
467 warning_as_error.Remove (wid);
469 return true;
471 case "/warn":
472 WarningLevel = Int32.Parse (value);
473 return true;
475 case "/nowarn": {
476 string [] warns;
478 if (value.Length == 0) {
479 Console.WriteLine ("/nowarn requires an argument");
480 Environment.Exit (1);
483 warns = value.Split (argument_value_separator);
484 foreach (string wc in warns) {
485 try {
486 if (wc.Trim ().Length == 0)
487 continue;
489 int warn = Int32.Parse (wc);
490 if (warn < 1) {
491 throw new ArgumentOutOfRangeException ("warn");
493 ignore_warning.Add (warn);
494 } catch {
495 Console.WriteLine (String.Format ("`{0}' is not a valid warning number", wc));
496 Environment.Exit (1);
499 return true;
502 case "/noconfig":
503 load_default_config = false;
504 return true;
506 case "/nostdlib":
507 case "/nostdlib+":
508 StdLib = false;
509 return true;
511 case "/nostdlib-":
512 StdLib = true;
513 return true;
515 case "/fullpaths":
516 return true;
518 case "/keyfile":
519 if (value == String.Empty) {
520 Console.WriteLine ("{0} requires an argument", arg);
521 Environment.Exit (1);
523 StrongNameKeyFile = value;
524 return true;
525 case "/keycontainer":
526 if (value == String.Empty) {
527 Console.WriteLine ("{0} requires an argument", arg);
528 Environment.Exit (1);
530 StrongNameKeyContainer = value;
531 return true;
532 case "/delaysign+":
533 case "/delaysign":
534 StrongNameDelaySign = true;
535 return true;
536 case "/delaysign-":
537 StrongNameDelaySign = false;
538 return true;
540 case "/langversion":
541 switch (value.ToLower (CultureInfo.InvariantCulture)) {
542 case "iso-1":
543 Version = LanguageVersion.ISO_1;
544 return true;
546 case "default":
547 Version = LanguageVersion.Default;
548 return true;
549 case "iso-2":
550 Version = LanguageVersion.ISO_2;
551 return true;
552 case "future":
553 Version = LanguageVersion.Future;
554 return true;
556 Console.WriteLine ("Invalid option `{0}' for /langversion. It must be either `ISO-1', `ISO-2' or `Default'", value);
557 Environment.Exit (1);
558 return true;
560 case "/codepage":
561 CodePage = value;
562 return true;
564 case "/publicsign":
565 return true;
567 case "/deterministic":
568 return true;
570 case "/runtimemetadataversion":
571 return true;
573 case "/-getresourcestrings":
574 return true;
577 Console.WriteLine ("Failing with : {0}", arg);
578 return false;
581 static string [] LoadArgs (string file)
583 StreamReader f;
584 var args = new List<string> ();
585 string line;
586 try {
587 f = new StreamReader (file);
588 } catch {
589 return null;
592 StringBuilder sb = new StringBuilder ();
594 while ((line = f.ReadLine ()) != null) {
595 int t = line.Length;
597 for (int i = 0; i < t; i++) {
598 char c = line [i];
600 if (c == '"' || c == '\'') {
601 char end = c;
603 for (i++; i < t; i++) {
604 c = line [i];
606 if (c == end)
607 break;
608 sb.Append (c);
610 } else if (c == ' ') {
611 if (sb.Length > 0) {
612 args.Add (sb.ToString ());
613 sb.Length = 0;
615 } else
616 sb.Append (c);
618 if (sb.Length > 0) {
619 args.Add (sb.ToString ());
620 sb.Length = 0;
624 string [] ret_value = new string [args.Count];
625 args.CopyTo (ret_value, 0);
627 return ret_value;
630 static string Load (string f)
632 var native = NativeName (f);
634 if (File.Exists (native)) {
635 using (var sr = new StreamReader (native)) {
636 return sr.ReadToEnd ();
638 } else
639 return "";
642 public static string NativeName (string path)
644 if (System.IO.Path.DirectorySeparatorChar == '/')
645 return path.Replace ("\\", "/");
646 else
647 return path.Replace ("/", "\\");
650 public class VsCsproj {
651 public string projectGuid;
652 public string output;
653 public string library_output;
654 public string csProjFilename;
655 public double fx_version;
656 public List<VsCsproj> projReferences = new List<VsCsproj> ();
657 public string library;
658 public MsbuildGenerator MsbuildGenerator;
661 public VsCsproj Csproj;
663 void AppendResource (StringBuilder resources, string source, string logical)
665 resources.AppendFormat (" <EmbeddedResource Include=\"{0}\">" + NewLine, source);
666 resources.AppendFormat (" <LogicalName>{0}</LogicalName>" + NewLine, logical);
667 resources.AppendFormat (" </EmbeddedResource>" + NewLine);
670 public VsCsproj Generate (string library_output, Dictionary<string,MsbuildGenerator> projects, bool showWarnings = false)
672 var generatedProjFile = NativeName (Csproj.csProjFilename);
673 //Console.WriteLine ("Generating: {0}", generatedProjFile);
675 string boot, flags, output_name, built_sources, response, profile, reskey;
677 boot = xproject.Element ("boot").Value;
678 flags = xproject.Element ("flags").Value;
679 output_name = xproject.Element ("output").Value;
680 if (output_name.EndsWith (".exe"))
681 Target = Target.Exe;
682 built_sources = xproject.Element ("built_sources").Value;
683 response = xproject.Element ("response").Value;
684 reskey = xproject.Element ("resources").Value;
686 profile = xproject.Element ("profile").Value;
687 if (string.IsNullOrEmpty (response)) {
688 // Address the issue where entries are missing the fx_version
689 // Should be fixed in the Makefile or elsewhere; this is a workaround
690 //<fx_version>basic</fx_version>
691 //<profile>./../build/deps/mcs.exe.sources.response</profile>
692 //<response></response>
693 response = profile;
694 profile = fx_version;
695 if (response.Contains ("build") || response.Contains ("basic") || response.Contains (profile_2_0)) {
696 fx_version = "2.0";
697 if (response.Contains (profile_2_0)) profile = "net_2_0";
698 } if (response.Contains ("build") || response.Contains ("basic") || response.Contains (profile_2_0)) {
699 fx_version = "2.0";
700 } else if (response.Contains (profile_3_5)) {
701 fx_version = "3.5";
702 profile = "net_3_5";
703 } else if (response.Contains (profile_4_0)) {
704 fx_version = "4.0";
705 profile = "net_4_0";
706 } else if (response.Contains (profile_4_x)) {
707 fx_version = "4.5";
708 profile = "net_4_x";
712 // Prebuild code, might be in inputs, check:
713 // inputs/LIBRARY.pre
715 string prebuild = GenerateStep (library, ".pre", "PreBuildEvent");
716 string postbuild = GenerateStep (library, ".post", "PostBuildEvent");
718 var all_args = new Queue<string []> ();
719 all_args.Enqueue (flags.Split ());
720 while (all_args.Count > 0) {
721 string [] f = all_args.Dequeue ();
723 for (int i = 0; i < f.Length; i++) {
724 if (f [i].Length > 0 && f [i][0] == '-')
725 f [i] = "/" + f [i].Substring (1);
727 if (f [i] [0] == '@') {
728 string [] extra_args;
729 string response_file = f [i].Substring (1);
731 var resp_file_full = Path.Combine (base_dir, response_file);
732 extra_args = LoadArgs (resp_file_full);
733 if (extra_args == null) {
734 Console.WriteLine ($"{library_output}: Unable to open response file: {resp_file_full}");
735 Environment.Exit (1);
738 all_args.Enqueue (extra_args);
739 continue;
742 if (CSCParseOption (f [i], ref f))
743 continue;
744 Console.WriteLine ("{library_output}: Failure with {0}", f [i]);
745 Environment.Exit (1);
749 string [] source_files;
750 using (var reader = new StreamReader (NativeName (base_dir + "\\" + response))) {
751 source_files = reader.ReadToEnd ().Split ();
754 Array.Sort (source_files);
756 StringBuilder sources = new StringBuilder ();
757 foreach (string s in source_files) {
758 if (s.Length == 0)
759 continue;
761 string src = s.Replace ("/", "\\");
762 if (src.StartsWith (@"Test\..\"))
763 src = src.Substring (8, src.Length - 8);
765 sources.AppendFormat (" <Compile Include=\"{0}\" />" + NewLine, src);
768 source_files = built_sources.Split ();
769 Array.Sort (source_files);
771 foreach (string s in source_files) {
772 if (s.Length == 0)
773 continue;
775 string src = s.Replace ("/", "\\");
776 if (src.StartsWith (@"Test\..\"))
777 src = src.Substring (8, src.Length - 8);
779 sources.AppendFormat (" <Compile Include=\"{0}\" />" + NewLine, src);
781 sources.Remove (sources.Length - 1, 1);
783 //if (library == "corlib-build") // otherwise, does not compile on fx_version == 4.0
785 // references.Add("System.dll");
786 // references.Add("System.Xml.dll");
789 //if (library == "System.Core-build") // otherwise, slow compile. May be a transient need.
791 // this.ignore_warning.Add(1685);
792 // this.ignore_warning.Add(0436);
795 var refs = new StringBuilder ();
797 bool is_test = response.Contains ("_test_");
798 if (is_test) {
799 // F:\src\mono\mcs\class\lib\net_2_0\nunit.framework.dll
800 // F:\src\mono\mcs\class\SomeProject\SomeProject_test_-net_2_0.csproj
801 var nunitLibPath = string.Format (@"..\lib\{0}\nunit.framework.dll", profile);
802 refs.Append (string.Format (" <Reference Include=\"{0}\" />" + NewLine, nunitLibPath));
806 // Generate resource referenced from the command line
808 var resources = new StringBuilder ();
809 if (embedded_resources.Count > 0) {
810 foreach (var dk in embedded_resources) {
811 var source = dk.Key;
812 if (source.EndsWith (".resources"))
813 source = source.Replace (".resources", ".resx");
815 // try to find a pre-built resource, and use that instead of trying to build it
816 if (source.EndsWith (".resx")) {
817 var probe_prebuilt = Path.Combine (base_dir, source.Replace (".resx", ".resources.prebuilt"));
818 if (File.Exists (probe_prebuilt)) {
820 source = GetRelativePath (base_dir + "/", probe_prebuilt);
823 AppendResource (resources, source, dk.Value);
827 // Generate resources that were part of the explicit <resource> node
829 if (reskey != null && reskey != ""){
830 var pairs = reskey.Split (' ', '\n', '\t');
831 foreach (var pair in pairs){
832 var p = pair.IndexOf (",");
833 if (p == -1){
834 Console.Error.WriteLine ($"Found a resource without a filename: {pairs} for {Csproj.csProjFilename}");
835 Environment.Exit (1);
837 AppendResource (resources, pair.Substring (p+1), pair.Substring (0, p) + ".resources");
840 if (resources.Length > 0){
841 resources.Insert (0, " <ItemGroup>" + NewLine);
842 resources.AppendFormat (" </ItemGroup>" + NewLine);
845 if (references.Count > 0 || reference_aliases.Count > 0) {
846 // -r:mscorlib.dll -r:System.dll
847 //<ProjectReference Include="..\corlib\corlib-basic.csproj">
848 // <Project>{155aef28-c81f-405d-9072-9d52780e3e70}</Project>
849 // <Name>corlib-basic</Name>
850 //</ProjectReference>
851 //<ProjectReference Include="..\System\System-basic.csproj">
852 // <Project>{2094e859-db2f-481f-9630-f89d31d9ed48}</Project>
853 // <Name>System-basic</Name>
854 //</ProjectReference>
855 var refdistinct = references.Distinct ();
856 foreach (string reference in refdistinct) {
858 var match = GetMatchingCsproj (library_output, reference, projects);
859 if (match != null) {
860 AddProjectReference (refs, Csproj, match, reference, null);
861 } else {
862 if (showWarnings){
863 Console.WriteLine ("{0}: Could not find a matching project reference for {1}", library, Path.GetFileName (reference));
864 Console.WriteLine (" --> Adding reference with hintpath instead");
866 refs.Append (" <Reference Include=\"" + reference + "\">" + NewLine);
867 refs.Append (" <SpecificVersion>False</SpecificVersion>" + NewLine);
868 refs.Append (" <HintPath>" + reference + "</HintPath>" + NewLine);
869 refs.Append (" <Private>False</Private>" + NewLine);
870 refs.Append (" </Reference>" + NewLine);
874 foreach (string r in reference_aliases) {
875 int index = r.IndexOf ('=');
876 string alias = r.Substring (0, index);
877 string assembly = r.Substring (index + 1);
878 var match = GetMatchingCsproj (library_output, assembly, projects, explicitPath: true);
879 if (match != null) {
880 AddProjectReference (refs, Csproj, match, r, alias);
881 } else {
882 throw new NotSupportedException (string.Format ("From {0}, could not find a matching project reference for {1}", library, r));
883 refs.Append (" <Reference Include=\"" + assembly + "\">" + NewLine);
884 refs.Append (" <SpecificVersion>False</SpecificVersion>" + NewLine);
885 refs.Append (" <HintPath>" + r + "</HintPath>" + NewLine);
886 refs.Append (" <Aliases>" + alias + "</Aliases>" + NewLine);
887 refs.Append (" </Reference>" + NewLine);
893 // Possible inputs:
894 // ../class/lib/build/tmp/System.Xml.dll [No longer possible, we should be removing this from order.xml]
895 // /class/lib/basic/System.Core.dll
896 // <library_output>mcs.exe</library_output>
897 string build_output_dir;
898 if (LibraryOutput.Contains ("/"))
899 build_output_dir = Path.GetDirectoryName (LibraryOutput);
900 else
901 build_output_dir = "bin\\Debug\\" + library;
903 bool basic_or_build = (library.Contains ("-basic") || library.Contains ("-build"));
906 // Replace the template values
909 string strongNameSection = "";
910 if (StrongNameKeyFile != null){
911 strongNameSection = String.Format (
912 " <PropertyGroup>" + NewLine +
913 " <SignAssembly>true</SignAssembly>" + NewLine +
914 "{1}" +
915 " </PropertyGroup>" + NewLine +
916 " <PropertyGroup>" + NewLine +
917 " <AssemblyOriginatorKeyFile>{0}</AssemblyOriginatorKeyFile>" + NewLine +
918 " </PropertyGroup>", StrongNameKeyFile, StrongNameDelaySign ? " <DelaySign>true</DelaySign>" + NewLine : "");
920 Csproj.output = template.
921 Replace ("@OUTPUTTYPE@", Target == Target.Library ? "Library" : "Exe").
922 Replace ("@SIGNATURE@", strongNameSection).
923 Replace ("@PROJECTGUID@", Csproj.projectGuid).
924 Replace ("@DEFINES@", defines.ToString ()).
925 Replace ("@DISABLEDWARNINGS@", string.Join (",", (from i in ignore_warning select i.ToString ()).ToArray ())).
926 //Replace("@NOSTDLIB@", (basic_or_build || (!StdLib)) ? "<NoStdLib>true</NoStdLib>" : string.Empty).
927 Replace ("@NOSTDLIB@", "<NoStdLib>" + (!StdLib).ToString () + "</NoStdLib>").
928 Replace ("@NOCONFIG@", "<NoConfig>" + (!load_default_config).ToString () + "</NoConfig>").
929 Replace ("@ALLOWUNSAFE@", Unsafe ? "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>" : "").
930 Replace ("@FX_VERSION", fx_version).
931 Replace ("@ASSEMBLYNAME@", Path.GetFileNameWithoutExtension (output_name)).
932 Replace ("@OUTPUTDIR@", build_output_dir).
933 Replace ("@OUTPUTSUFFIX@", Path.GetFileName (build_output_dir)).
934 Replace ("@DEFINECONSTANTS@", defines.ToString ()).
935 Replace ("@DEBUG@", want_debugging_support ? "true" : "false").
936 Replace ("@DEBUGTYPE@", want_debugging_support ? "full" : "pdbonly").
937 Replace ("@REFERENCES@", refs.ToString ()).
938 Replace ("@PREBUILD@", prebuild).
939 Replace ("@POSTBUILD@", postbuild).
940 Replace ("@STARTUPOBJECT@", main == null ? "" : $"<StartupObject>{main}</StartupObject>").
941 //Replace ("@ADDITIONALLIBPATHS@", String.Format ("<AdditionalLibPaths>{0}</AdditionalLibPaths>", string.Join (",", libs.ToArray ()))).
942 Replace ("@ADDITIONALLIBPATHS@", String.Empty).
943 Replace ("@RESOURCES@", resources.ToString ()).
944 Replace ("@OPTIMIZE@", Optimize ? "true" : "false").
945 Replace ("@SOURCES@", sources.ToString ());
947 //Console.WriteLine ("Generated {0}", ofile.Replace ("\\", "/"));
948 using (var o = new StreamWriter (generatedProjFile)) {
949 o.WriteLine (Csproj.output);
952 return Csproj;
955 string GenerateStep (string library, string suffix, string eventKey)
957 string target = Load (library + suffix);
958 string target_windows, target_unix;
960 int q = library.IndexOf ("-");
961 if (q != -1)
962 target = target + Load (library.Substring (0, q) + suffix);
964 if (target.IndexOf ("@MONO@") != -1){
965 target_unix = target.Replace ("@MONO@", "mono").Replace ("@CAT@", "cat");
966 target_windows = target.Replace ("@MONO@", "").Replace ("@CAT@", "type");
967 } else {
968 target_unix = target.Replace ("jay.exe", "jay");
969 target_windows = target;
971 target_unix = target_unix.Replace ("@COPY@", "cp");
972 target_windows = target_unix.Replace ("@COPY@", "copy");
974 target_unix = target_unix.Replace ("\r", "");
975 const string condition_unix = "Condition=\" '$(OS)' != 'Windows_NT' \"";
976 const string condition_windows = "Condition=\" '$(OS)' == 'Windows_NT' \"";
977 var result =
978 $" <{eventKey} {condition_unix}>\n{target_unix}\n </{eventKey}>{NewLine}" +
979 $" <{eventKey} {condition_windows}>{NewLine}{target_windows}{NewLine} </{eventKey}>";
980 return result;
983 void AddProjectReference (StringBuilder refs, VsCsproj result, MsbuildGenerator match, string r, string alias)
985 refs.AppendFormat (" <ProjectReference Include=\"{0}\">{1}", GetRelativePath (result.csProjFilename, match.CsprojFilename), NewLine);
986 refs.Append (" <Project>" + match.projectGuid + "</Project>" + NewLine);
987 refs.Append (" <Name>" + Path.GetFileNameWithoutExtension (match.CsprojFilename.Replace ('\\', Path.DirectorySeparatorChar)) + "</Name>" + NewLine);
988 if (alias != null)
989 refs.Append (" <Aliases>" + alias + "</Aliases>");
990 refs.Append (" </ProjectReference>" + NewLine);
991 if (!result.projReferences.Contains (match.Csproj))
992 result.projReferences.Add (match.Csproj);
995 public static string GetRelativePath (string from, string to)
997 from = from.Replace ("\\", "/");
998 to = to.Replace ("\\", "/");
999 var fromUri = new Uri (Path.GetFullPath (from));
1000 var toUri = new Uri (Path.GetFullPath (to));
1002 var ret = fromUri.MakeRelativeUri (toUri).ToString ().Replace ("%5C", "\x5c");
1003 return ret;
1006 MsbuildGenerator GetMatchingCsproj (string library_output, string dllReferenceName, Dictionary<string,MsbuildGenerator> projects, bool explicitPath = false)
1008 // libDir would be "./../../class/lib/net_4_x for example
1009 // project
1010 if (!dllReferenceName.EndsWith (".dll") && !dllReferenceName.EndsWith (".exe"))
1011 dllReferenceName += ".dll";
1013 var probe = Path.GetFullPath (Path.Combine (base_dir, dllReferenceName));
1014 foreach (var project in projects){
1015 if (probe == project.Value.AbsoluteLibraryOutput)
1016 return project.Value;
1019 // not explicit, search for the library in the lib path order specified
1021 foreach (var libDir in libs) {
1022 var abs = Path.GetFullPath (Path.Combine (base_dir, libDir));
1023 foreach (var project in projects){
1024 probe = Path.Combine (abs, dllReferenceName);
1026 if (probe == project.Value.AbsoluteLibraryOutput)
1027 return project.Value;
1031 // Last attempt, try to find the library in all the projects
1032 foreach (var project in projects) {
1033 if (project.Value.AbsoluteLibraryOutput.EndsWith (dllReferenceName))
1034 return project.Value;
1037 var ljoined = String.Join (", ", libs);
1038 Console.WriteLine ($"{library_output}: did not find referenced {dllReferenceName} with libs={ljoined}");
1039 foreach (var p in projects) {
1040 Console.WriteLine ("{0}", p.Value.AbsoluteLibraryOutput);
1042 return null;
1047 public class Driver {
1049 static IEnumerable<XElement> GetProjects (bool full = false)
1051 XDocument doc = XDocument.Load ("order.xml");
1052 foreach (XElement project in doc.Root.Elements ()) {
1053 string dir = project.Attribute ("dir").Value;
1054 string library = project.Attribute ("library").Value;
1055 var profile = project.Element ("profile").Value;
1057 #if false
1058 // Skip facades for now, the tool doesn't know how to deal with them yet.
1059 if (dir.Contains ("Facades"))
1060 continue;
1062 // These are currently broken, skip until they're fixed.
1063 if (dir.StartsWith ("mcs") || dir.Contains ("apigen"))
1064 continue;
1067 // Do only class libraries for now
1069 if (!(dir.StartsWith ("class") || dir.StartsWith ("mcs") || dir.StartsWith ("basic")))
1070 continue;
1072 if (full){
1073 if (!library.Contains ("tests"))
1074 yield return project;
1075 continue;
1077 #endif
1079 // Do not do 2.1, it is not working yet
1080 // Do not do basic, as there is no point (requires a system mcs to be installed).
1082 if (library.Contains ("moonlight") || library.Contains ("-basic") || library.EndsWith ("bootstrap") || library.Contains ("build"))
1083 continue;
1085 // The next ones are to make debugging easier for now
1086 if (profile == "basic")
1087 continue;
1089 // For now -- problem is, our resolver currently only considers the assembly name, and we ahve
1090 // conflicing 2.0 and 2.4 versions so for now, we just skip the nunit20 versions
1091 if (dir.Contains ("nunit20"))
1092 continue;
1094 #if true
1095 if (profile != "net_4_x" || library.Contains ("tests"))
1096 continue;
1097 #endif
1098 //Console.WriteLine ("Going to handle {0}", library);
1099 yield return project;
1103 static void Main (string [] args)
1105 if (!File.Exists ("genproj.cs")) {
1106 Console.WriteLine ("This command must be executed from mono/msvc/scripts");
1107 Environment.Exit (1);
1110 if (args.Length == 1 && args [0].ToLower ().Contains ("-h")) {
1111 Console.WriteLine ("Usage:");
1112 Console.WriteLine ("genproj.exe [visual_studio_release] [output_full_solutions]");
1113 Console.WriteLine ("If output_full_solutions is false, only the main System*.dll");
1114 Console.WriteLine (" assemblies (and dependencies) is included in the solution.");
1115 Console.WriteLine ("Example:");
1116 Console.WriteLine ("genproj.exe 2012 false");
1117 Console.WriteLine ("genproj.exe with no arguments is equivalent to 'genproj.exe 2012 true'\n\n");
1118 Console.WriteLine ("genproj.exe deps");
1119 Console.WriteLine ("Generates a Makefile dependency file from the projects input");
1120 Environment.Exit (0);
1123 var slnVersion = (args.Length > 0) ? args [0] : "2012";
1124 bool fullSolutions = (args.Length > 1) ? bool.Parse (args [1]) : true;
1126 // To generate makefile depenedencies
1127 var makefileDeps = (args.Length > 0 && args [0] == "deps");
1129 var sln_gen = new SlnGenerator (slnVersion);
1130 var four_five_sln_gen = new SlnGenerator (slnVersion);
1131 var projects = new Dictionary<string,MsbuildGenerator> ();
1133 var duplicates = new List<string> ();
1134 foreach (var project in GetProjects (makefileDeps)) {
1135 var library_output = project.Element ("library_output").Value;
1136 projects [library_output] = new MsbuildGenerator (project);
1138 foreach (var project in GetProjects (makefileDeps)){
1139 var library_output = project.Element ("library_output").Value;
1140 //Console.WriteLine ("=== {0} ===", library_output);
1141 var gen = projects [library_output];
1142 try {
1143 var csproj = gen.Generate (library_output, projects);
1144 var csprojFilename = csproj.csProjFilename;
1145 if (!sln_gen.ContainsProjectIdentifier (csproj.library)) {
1146 sln_gen.Add (csproj);
1147 } else {
1148 duplicates.Add (csprojFilename);
1151 } catch (Exception e) {
1152 Console.WriteLine ("Error in {0}\n{1}", project, e);
1156 Func<MsbuildGenerator.VsCsproj, bool> additionalFilter;
1157 additionalFilter = fullSolutions ? (Func<MsbuildGenerator.VsCsproj, bool>)null : IsCommonLibrary;
1159 FillSolution (four_five_sln_gen, MsbuildGenerator.profile_4_x, projects.Values, additionalFilter);
1161 if (duplicates.Count () > 0) {
1162 var sb = new StringBuilder ();
1163 sb.AppendLine ("WARNING: Skipped some project references, apparent duplicates in order.xml:");
1164 foreach (var item in duplicates) {
1165 sb.AppendLine (item);
1167 Console.WriteLine (sb.ToString ());
1170 WriteSolution (four_five_sln_gen, Path.Combine ("..", "..", MakeSolutionName (MsbuildGenerator.profile_4_x)));
1172 if (makefileDeps){
1173 const string classDirPrefix = "./../../";
1174 Console.WriteLine ("here {0}", sln_gen.libraries.Count);
1175 foreach (var p in sln_gen.libraries){
1176 string rebasedOutput = RebaseToClassDirectory (MsbuildGenerator.GetRelativePath ("../../mcs/class", p.library_output));
1178 Console.Write ("{0}: ", rebasedOutput);
1179 foreach (var r in p.projReferences){
1180 var lo = r.library_output;
1181 if (lo.StartsWith (classDirPrefix))
1182 lo = lo.Substring (classDirPrefix.Length);
1183 else
1184 lo = "<<ERROR-dependency is not a class library>>";
1185 Console.Write ("{0} ", lo);
1187 Console.Write ("\n\t(cd {0}; make {1})", p.MsbuildGenerator.dir, p.library_output);
1188 Console.WriteLine ("\n");
1192 // A few other optional solutions
1193 // Solutions with 'everything' and the most common libraries used in development may be of interest
1194 //WriteSolution (sln_gen, "./mcs_full.sln");
1195 //WriteSolution (small_full_sln_gen, "small_full.sln");
1196 // The following may be useful if lacking visual studio or MonoDevelop, to bootstrap mono compiler self-hosting
1197 //WriteSolution (basic_sln_gen, "mcs_basic.sln");
1198 //WriteSolution (build_sln_gen, "mcs_build.sln");
1201 // Rebases a path, assuming that execution is taking place in the "class" subdirectory,
1202 // so it strips ../class/ from a path, which is a no-op
1203 static string RebaseToClassDirectory (string path)
1205 const string prefix = "../class/";
1206 int p = path.IndexOf (prefix);
1207 if (p == -1)
1208 return path;
1209 return path.Substring (0, p) + path.Substring (p+prefix.Length);
1210 return path;
1213 static string MakeSolutionName (string profileTag)
1215 return "net" + profileTag + ".sln";
1218 static void FillSolution (SlnGenerator solution, string profileString, IEnumerable<MsbuildGenerator> projects, Func<MsbuildGenerator.VsCsproj, bool> additionalFilter = null)
1220 foreach (var generator in projects) {
1221 var vsCsproj = generator.Csproj;
1222 if (!vsCsproj.library.Contains (profileString))
1223 continue;
1224 if (additionalFilter != null && !additionalFilter (vsCsproj))
1225 continue;
1226 var csprojFilename = vsCsproj.csProjFilename;
1227 if (!solution.ContainsProjectIdentifier (vsCsproj.library)) {
1228 solution.Add (vsCsproj);
1229 RecursiveAddProj (solution, vsCsproj);
1234 static void RecursiveAddProj (SlnGenerator solution, MsbuildGenerator.VsCsproj vsCsproj, int recursiveDepth = 1)
1236 const int max_recursive = 16;
1237 if (recursiveDepth > max_recursive) throw new Exception (string.Format ("Reached {0} levels of project dependency", max_recursive));
1238 foreach (var projRef in vsCsproj.projReferences) {
1239 if (!solution.ContainsProjectIdentifier (projRef.library)) {
1240 solution.Add (projRef);
1241 RecursiveAddProj (solution, projRef, recursiveDepth + 1);
1246 static void WriteSolution (SlnGenerator sln_gen, string slnfilename)
1248 Console.WriteLine (String.Format ("Writing solution {1}, with {0} projects", sln_gen.Count, slnfilename));
1249 sln_gen.Write (slnfilename);
1252 static bool IsCommonLibrary (MsbuildGenerator.VsCsproj proj)
1254 var library = proj.library;
1255 //if (library.Contains ("-basic"))
1256 // return true;
1257 //if (library.Contains ("-build"))
1258 // return true;
1259 //if (library.StartsWith ("corlib"))
1260 // return true;
1261 if (library.StartsWith ("System-"))
1262 return true;
1263 if (library.StartsWith ("System.Xml"))
1264 return true;
1265 if (library.StartsWith ("System.Secu"))
1266 return true;
1267 if (library.StartsWith ("System.Configuration"))
1268 return true;
1269 if (library.StartsWith ("System.Core"))
1270 return true;
1271 //if (library.StartsWith ("Mono."))
1272 // return true;
1274 return false;