b8f01a62291de9f5599f94f9edc7da2de19622cf
[mono.git] / msvc / scripts / genproj.cs
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
11 //
12 //    * Has not been tested in a while with the command line tools
13 //
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;
23
24 public enum Target {
25         Library, Exe, Module, WinExe
26 }
27
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
35 }
36
37 class SlnGenerator {
38         public static readonly string NewLine = "\r\n"; //Environment.NewLine; // "\n"; 
39         public SlnGenerator (string formatVersion = "2012")
40         {
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;
48                 }
49         }
50
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";
53
54         public List<MsbuildGenerator.VsCsproj> libraries = new List<MsbuildGenerator.VsCsproj> ();
55         string header;
56
57         string MakeHeader (string formatVersion, string yearTag)
58         {
59                 return string.Format ("Microsoft Visual Studio Solution File, Format Version {0}" + NewLine + "# Visual Studio {1}", formatVersion, yearTag);
60         }
61
62         public void Add (MsbuildGenerator.VsCsproj vsproj)
63         {
64                 try {
65                         libraries.Add (vsproj);
66                 } catch (Exception ex) {
67                         Console.WriteLine (ex);
68                 }
69         }
70
71         public void Write (string filename)
72         {
73                 using (var sln = new StreamWriter (filename)) {
74                         sln.WriteLine ();
75                         sln.WriteLine (header);
76                         foreach (var proj in libraries) {
77                                 sln.WriteLine (project_start, proj.library, proj.csProjFilename, proj.projectGuid);
78                                 sln.WriteLine (project_end);
79                         }
80                         sln.WriteLine ("Global");
81
82                         sln.WriteLine ("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
83                         sln.WriteLine ("\t\tDebug|Any CPU = Debug|Any CPU");
84                         sln.WriteLine ("\t\tRelease|Any CPU = Release|Any CPU");
85                         sln.WriteLine ("\tEndGlobalSection");
86
87                         sln.WriteLine ("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
88                         foreach (var proj in libraries) {
89                                 var guid = proj.projectGuid;
90                                 sln.WriteLine ("\t\t{0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", guid);
91                                 sln.WriteLine ("\t\t{0}.Debug|Any CPU.Build.0 = Debug|Any CPU", guid);
92                                 sln.WriteLine ("\t\t{0}.Release|Any CPU.ActiveCfg = Release|Any CPU", guid);
93                                 sln.WriteLine ("\t\t{0}.Release|Any CPU.Build.0 = Release|Any CPU", guid);
94                         }
95                         sln.WriteLine ("\tEndGlobalSection");
96
97                         sln.WriteLine ("\tGlobalSection(SolutionProperties) = preSolution");
98                         sln.WriteLine ("\t\tHideSolutionNode = FALSE");
99                         sln.WriteLine ("\tEndGlobalSection");
100
101                         sln.WriteLine ("EndGlobal");
102                 }
103         }
104
105         internal bool ContainsProjectIdentifier (string projId)
106         {
107                 return libraries.FindIndex (x => (x.library == projId)) >= 0;
108         }
109
110         public int Count { get { return libraries.Count; } }
111 }
112
113 class MsbuildGenerator {
114         static readonly string NewLine = SlnGenerator.NewLine;
115         static XmlNamespaceManager xmlns;
116
117         public const string profile_2_0 = "_2_0";
118         public const string profile_3_5 = "_3_5";
119         public const string profile_4_0 = "_4_0";
120         public const string profile_4_x = "_4_x";
121
122         static void Usage ()
123         {
124                 Console.WriteLine ("Invalid argument");
125         }
126
127         static string template;
128         static MsbuildGenerator ()
129         {
130                 using (var input = new StreamReader ("csproj.tmpl")) {
131                         template = input.ReadToEnd ();
132                 }
133
134                 xmlns = new XmlNamespaceManager (new NameTable ());
135                 xmlns.AddNamespace ("x", "http://schemas.microsoft.com/developer/msbuild/2003");
136         }
137
138         // The directory as specified in order.xml
139         public string dir;
140         string library;
141         string projectGuid;
142         string fx_version;
143
144         XElement xproject;
145         public string CsprojFilename;
146
147         //
148         // Our base directory, this is relative to our exectution point mono/msvc/scripts
149         string base_dir;
150         string mcs_topdir;
151
152         public string LibraryOutput, AbsoluteLibraryOutput;
153
154         public MsbuildGenerator (XElement xproject)
155         {
156                 this.xproject = xproject;
157                 dir = xproject.Attribute ("dir").Value;
158                 library = xproject.Attribute ("library").Value;
159                 CsprojFilename = "..\\..\\mcs\\" + dir + "\\" + library + ".csproj";
160                 LibraryOutput = xproject.Element ("library_output").Value;
161
162                 projectGuid = LookupOrGenerateGuid ();
163                 fx_version = xproject.Element ("fx_version").Value;
164                 Csproj = new VsCsproj () {
165                         csProjFilename = this.CsprojFilename,
166                         projectGuid = this.projectGuid,
167                         library_output = this.LibraryOutput,
168                         fx_version = double.Parse (fx_version),
169                         library = this.library,
170                         MsbuildGenerator = this
171                 };
172
173                 if (dir == "mcs") {
174                         mcs_topdir = "../";
175                         class_dir = "../class/";
176                         base_dir = "../../mcs/mcs";
177                 } else {
178                         mcs_topdir = "../";
179
180                         foreach (char c in dir) {
181                                 if (c == '/')
182                                         mcs_topdir = "..//" + mcs_topdir;
183                         }
184                         class_dir = mcs_topdir.Substring (3);
185
186                         base_dir = Path.Combine ("..", "..", "mcs", dir);
187                 }
188                 AbsoluteLibraryOutput = Path.GetFullPath (Path.Combine (base_dir, LibraryOutput));
189         }
190
191         string LookupOrGenerateGuid ()
192         {
193                 var projectFile = NativeName (CsprojFilename);
194                 if (File.Exists (projectFile)){
195                         var doc = XDocument.Load (projectFile);
196                         return doc.XPathSelectElement ("x:Project/x:PropertyGroup/x:ProjectGuid", xmlns).Value;
197                 }
198                 return "{" + Guid.NewGuid ().ToString ().ToUpper () + "}";
199         }
200
201         // Currently used
202         bool Unsafe = false;
203         StringBuilder defines = new StringBuilder ();
204         bool Optimize = true;
205         bool want_debugging_support = false;
206         string main = null;
207         Dictionary<string, string> embedded_resources = new Dictionary<string, string> ();
208         List<string> warning_as_error = new List<string> ();
209         List<int> ignore_warning = new List<int> ();
210         bool load_default_config = true;
211         bool StdLib = true;
212         List<string> references = new List<string> ();
213         List<string> libs = new List<string> ();
214         List<string> reference_aliases = new List<string> ();
215         bool showWarnings = true;
216
217         // Currently unused
218 #pragma warning disable 0219, 0414
219         int WarningLevel = 4;
220
221         bool Checked = false;
222         bool WarningsAreErrors;
223         bool VerifyClsCompliance = true;
224         string win32IconFile;
225         string StrongNameKeyFile;
226         bool copyLocal = true;
227         Target Target = Target.Library;
228         string TargetExt = ".exe";
229         string OutputFile;
230         string StrongNameKeyContainer;
231         bool StrongNameDelaySign = false;
232         LanguageVersion Version = LanguageVersion.Default;
233         string CodePage;
234
235         // Class directory, relative to 
236         string class_dir;
237 #pragma warning restore 0219,414
238
239         readonly char [] argument_value_separator = new char [] { ';', ',' };
240
241         //
242         // This parses the -arg and /arg options to the compiler, even if the strings
243         // in the following text use "/arg" on the strings.
244         //
245         bool CSCParseOption (string option, ref string [] args)
246         {
247                 int idx = option.IndexOf (':');
248                 string arg, value;
249
250                 if (idx == -1) {
251                         arg = option;
252                         value = "";
253                 } else {
254                         arg = option.Substring (0, idx);
255
256                         value = option.Substring (idx + 1);
257                 }
258
259                 switch (arg.ToLower (CultureInfo.InvariantCulture)) {
260                 case "/nologo":
261                         return true;
262
263                 case "/t":
264                 case "/target":
265                         switch (value) {
266                         case "exe":
267                                 Target = Target.Exe;
268                                 break;
269
270                         case "winexe":
271                                 Target = Target.WinExe;
272                                 break;
273
274                         case "library":
275                                 Target = Target.Library;
276                                 TargetExt = ".dll";
277                                 break;
278
279                         case "module":
280                                 Target = Target.Module;
281                                 TargetExt = ".netmodule";
282                                 break;
283
284                         default:
285                                 return false;
286                         }
287                         return true;
288
289                 case "/out":
290                         if (value.Length == 0) {
291                                 Usage ();
292                                 Environment.Exit (1);
293                         }
294                         OutputFile = value;
295                         return true;
296
297                 case "/o":
298                 case "/o+":
299                 case "/optimize":
300                 case "/optimize+":
301                         Optimize = true;
302                         return true;
303
304                 case "/o-":
305                 case "/optimize-":
306                         Optimize = false;
307                         return true;
308
309                 case "/incremental":
310                 case "/incremental+":
311                 case "/incremental-":
312                         // nothing.
313                         return true;
314
315                 case "/d":
316                 case "/define": {
317                                 if (value.Length == 0) {
318                                         Usage ();
319                                         Environment.Exit (1);
320                                 }
321
322                                 foreach (string d in value.Split (argument_value_separator)) {
323                                         if (defines.Length != 0)
324                                                 defines.Append (";");
325                                         defines.Append (d);
326                                 }
327
328                                 return true;
329                         }
330
331                 case "/bugreport":
332                         //
333                         // We should collect data, runtime, etc and store in the file specified
334                         //
335                         return true;
336                 case "/linkres":
337                 case "/linkresource":
338                 case "/res":
339                 case "/resource":
340                         bool embeded = arg [1] == 'r' || arg [1] == 'R';
341                         string [] s = value.Split (argument_value_separator);
342                         switch (s.Length) {
343                         case 1:
344                                 if (s [0].Length == 0)
345                                         goto default;
346                                 embedded_resources [s [0]] = Path.GetFileName (s [0]);
347                                 break;
348                         case 2:
349                                 embedded_resources [s [0]] = s [1];
350                                 break;
351                         case 3:
352                                 Console.WriteLine ("Does not support this method yet: {0}", arg);
353                                 Environment.Exit (1);
354                                 break;
355                         default:
356                                 Console.WriteLine ("Wrong number of arguments for option `{0}'", option);
357                                 Environment.Exit (1);
358                                 break;
359                         }
360
361                         return true;
362
363                 case "/recurse":
364                         Console.WriteLine ("/recurse not supported");
365                         Environment.Exit (1);
366                         return true;
367
368                 case "/r":
369                 case "/reference": {
370                                 if (value.Length == 0) {
371                                         Console.WriteLine ("-reference requires an argument");
372                                         Environment.Exit (1);
373                                 }
374
375                                 string [] refs = value.Split (argument_value_separator);
376                                 foreach (string r in refs) {
377                                         string val = r;
378                                         int index = val.IndexOf ('=');
379                                         if (index > -1) {
380                                                 reference_aliases.Add (r);
381                                                 continue;
382                                         }
383
384                                         if (val.Length != 0)
385                                                 references.Add (val);
386                                 }
387                                 return true;
388                         }
389                 case "/main":
390                         main = value;
391                         return true;
392
393                 case "/m":
394                 case "/addmodule":
395                 case "/win32res":
396                 case "/doc": 
397                         if (showWarnings)
398                                 Console.WriteLine ("{0} = not supported", arg);
399                         return true;
400                         
401                 case "/lib": {
402                                 libs.Add (value);
403                                 return true;
404                         }
405                 case "/win32icon": {
406                                 win32IconFile = value;
407                                 return true;
408                         }
409                 case "/debug-":
410                         want_debugging_support = false;
411                         return true;
412
413                 case "/debug":
414                 case "/debug+":
415                         want_debugging_support = true;
416                         return true;
417
418                 case "/checked":
419                 case "/checked+":
420                         Checked = true;
421                         return true;
422
423                 case "/checked-":
424                         Checked = false;
425                         return true;
426
427                 case "/clscheck":
428                 case "/clscheck+":
429                         return true;
430
431                 case "/clscheck-":
432                         VerifyClsCompliance = false;
433                         return true;
434
435                 case "/unsafe":
436                 case "/unsafe+":
437                         Unsafe = true;
438                         return true;
439
440                 case "/unsafe-":
441                         Unsafe = false;
442                         return true;
443
444                 case "/warnaserror":
445                 case "/warnaserror+":
446                         if (value.Length == 0) {
447                                 WarningsAreErrors = true;
448                         } else {
449                                 foreach (string wid in value.Split (argument_value_separator))
450                                         warning_as_error.Add (wid);
451                         }
452                         return true;
453
454                 case "/-runtime":
455                         // Console.WriteLine ("Warning ignoring /runtime:v4");
456                         return true;
457
458                 case "/warnaserror-":
459                         if (value.Length == 0) {
460                                 WarningsAreErrors = false;
461                         } else {
462                                 foreach (string wid in value.Split (argument_value_separator))
463                                         warning_as_error.Remove (wid);
464                         }
465                         return true;
466
467                 case "/warn":
468                         WarningLevel = Int32.Parse (value);
469                         return true;
470
471                 case "/nowarn": {
472                                 string [] warns;
473
474                                 if (value.Length == 0) {
475                                         Console.WriteLine ("/nowarn requires an argument");
476                                         Environment.Exit (1);
477                                 }
478
479                                 warns = value.Split (argument_value_separator);
480                                 foreach (string wc in warns) {
481                                         try {
482                                                 if (wc.Trim ().Length == 0)
483                                                         continue;
484
485                                                 int warn = Int32.Parse (wc);
486                                                 if (warn < 1) {
487                                                         throw new ArgumentOutOfRangeException ("warn");
488                                                 }
489                                                 ignore_warning.Add (warn);
490                                         } catch {
491                                                 Console.WriteLine (String.Format ("`{0}' is not a valid warning number", wc));
492                                                 Environment.Exit (1);
493                                         }
494                                 }
495                                 return true;
496                         }
497
498                 case "/noconfig":
499                         load_default_config = false;
500                         return true;
501
502                 case "/nostdlib":
503                 case "/nostdlib+":
504                         StdLib = false;
505                         return true;
506
507                 case "/nostdlib-":
508                         StdLib = true;
509                         return true;
510
511                 case "/fullpaths":
512                         return true;
513
514                 case "/keyfile":
515                         if (value == String.Empty) {
516                                 Console.WriteLine ("{0} requires an argument", arg);
517                                 Environment.Exit (1);
518                         }
519                         StrongNameKeyFile = value;
520                         return true;
521                 case "/keycontainer":
522                         if (value == String.Empty) {
523                                 Console.WriteLine ("{0} requires an argument", arg);
524                                 Environment.Exit (1);
525                         }
526                         StrongNameKeyContainer = value;
527                         return true;
528                 case "/delaysign+":
529                 case "/delaysign":
530                         StrongNameDelaySign = true;
531                         return true;
532                 case "/delaysign-":
533                         StrongNameDelaySign = false;
534                         return true;
535
536                 case "/langversion":
537                         switch (value.ToLower (CultureInfo.InvariantCulture)) {
538                         case "iso-1":
539                                 Version = LanguageVersion.ISO_1;
540                                 return true;
541
542                         case "default":
543                                 Version = LanguageVersion.Default;
544                                 return true;
545                         case "iso-2":
546                                 Version = LanguageVersion.ISO_2;
547                                 return true;
548                         case "future":
549                                 Version = LanguageVersion.Future;
550                                 return true;
551                         }
552                         Console.WriteLine ("Invalid option `{0}' for /langversion. It must be either `ISO-1', `ISO-2' or `Default'", value);
553                         Environment.Exit (1);
554                         return true;
555
556                 case "/codepage":
557                         CodePage = value;
558                         return true;
559
560                 case "/publicsign":
561                         return true;
562                         
563                 case "/-getresourcestrings":
564                         return true;
565                 }
566
567                 Console.WriteLine ("Failing with : {0}", arg);
568                 return false;
569         }
570
571         static string [] LoadArgs (string file)
572         {
573                 StreamReader f;
574                 var args = new List<string> ();
575                 string line;
576                 try {
577                         f = new StreamReader (file);
578                 } catch {
579                         return null;
580                 }
581
582                 StringBuilder sb = new StringBuilder ();
583
584                 while ((line = f.ReadLine ()) != null) {
585                         int t = line.Length;
586
587                         for (int i = 0; i < t; i++) {
588                                 char c = line [i];
589
590                                 if (c == '"' || c == '\'') {
591                                         char end = c;
592
593                                         for (i++; i < t; i++) {
594                                                 c = line [i];
595
596                                                 if (c == end)
597                                                         break;
598                                                 sb.Append (c);
599                                         }
600                                 } else if (c == ' ') {
601                                         if (sb.Length > 0) {
602                                                 args.Add (sb.ToString ());
603                                                 sb.Length = 0;
604                                         }
605                                 } else
606                                         sb.Append (c);
607                         }
608                         if (sb.Length > 0) {
609                                 args.Add (sb.ToString ());
610                                 sb.Length = 0;
611                         }
612                 }
613
614                 string [] ret_value = new string [args.Count];
615                 args.CopyTo (ret_value, 0);
616
617                 return ret_value;
618         }
619
620         static string Load (string f)
621         {
622                 var native = NativeName (f);
623
624                 if (File.Exists (native)) {
625                         using (var sr = new StreamReader (native)) {
626                                 return sr.ReadToEnd ();
627                         }
628                 } else
629                         return "";
630         }
631
632         public static string NativeName (string path)
633         {
634                 if (System.IO.Path.DirectorySeparatorChar == '/')
635                         return path.Replace ("\\", "/");
636                 else
637                         return path.Replace ("/", "\\");
638         }
639
640         public class VsCsproj {
641                 public string projectGuid;
642                 public string output;
643                 public string library_output;
644                 public string csProjFilename;
645                 public double fx_version;
646                 public List<VsCsproj> projReferences = new List<VsCsproj> ();
647                 public string library;
648                 public MsbuildGenerator MsbuildGenerator;
649         }
650
651         public VsCsproj Csproj;
652
653         public VsCsproj Generate (Dictionary<string,MsbuildGenerator> projects, bool showWarnings = false)
654         {
655                 var generatedProjFile = NativeName (Csproj.csProjFilename);
656                 //Console.WriteLine ("Generating: {0}", generatedProjFile);
657
658                 string boot, flags, output_name, built_sources, response, profile;
659
660                 boot = xproject.Element ("boot").Value;
661                 flags = xproject.Element ("flags").Value;
662                 output_name = xproject.Element ("output").Value;
663                 if (output_name.EndsWith (".exe"))
664                         Target = Target.Exe;
665                 built_sources = xproject.Element ("built_sources").Value;
666                 response = xproject.Element ("response").Value;
667
668                 profile = xproject.Element ("profile").Value;
669                 if (string.IsNullOrEmpty (response)) {
670                         // Address the issue where entries are missing the fx_version
671                         // Should be fixed in the Makefile or elsewhere; this is a workaround
672                         //<fx_version>basic</fx_version>
673                         //<profile>./../build/deps/mcs.exe.sources.response</profile>
674                         //<response></response>
675                         response = profile;
676                         profile = fx_version;
677                         if (response.Contains ("build") || response.Contains ("basic") || response.Contains (profile_2_0)) {
678                                 fx_version = "2.0";
679                                 if (response.Contains (profile_2_0)) profile = "net_2_0";
680                         } if (response.Contains ("build") || response.Contains ("basic") || response.Contains (profile_2_0)) {
681                                 fx_version = "2.0";
682                         } else if (response.Contains (profile_3_5)) {
683                                 fx_version = "3.5";
684                                 profile = "net_3_5";
685                         } else if (response.Contains (profile_4_0)) {
686                                 fx_version = "4.0";
687                                 profile = "net_4_0";
688                         } else if (response.Contains (profile_4_x)) {
689                                 fx_version = "4.5";
690                                 profile = "net_4_x";
691                         }
692                 }
693                 //
694                 // Prebuild code, might be in inputs, check:
695                 //  inputs/LIBRARY-PROFILE.pre
696                 //  inputs/LIBRARY.pre
697                 //
698                 string prebuild = Load (library + ".pre");
699                 string prebuild_windows, prebuild_unix;
700                 
701                 int q = library.IndexOf ("-");
702                 if (q != -1)
703                         prebuild = prebuild + Load (library.Substring (0, q) + ".pre");
704
705                 if (prebuild.IndexOf ("@MONO@") != -1){
706                         prebuild_unix = prebuild.Replace ("@MONO@", "mono").Replace ("@CAT@", "cat");
707                         prebuild_windows = prebuild.Replace ("@MONO@", "").Replace ("@CAT@", "type");
708                 } else {
709                         prebuild_unix = prebuild.Replace ("jay.exe", "jay");
710                         prebuild_windows = prebuild;
711                 }
712                 
713                 const string condition_unix    = "Condition=\" '$(OS)' != 'Windows_NT' \"";
714                 const string condition_windows = "Condition=\" '$(OS)' == 'Windows_NT' \"";
715                 prebuild =
716                         "    <PreBuildEvent " + condition_unix + ">" + NewLine + prebuild_unix + NewLine + "    </PreBuildEvent>" + NewLine +
717                         "    <PreBuildEvent " + condition_windows + ">" + NewLine + prebuild_windows + NewLine + "    </PreBuildEvent>" + NewLine;
718
719                 var all_args = new Queue<string []> ();
720                 all_args.Enqueue (flags.Split ());
721                 while (all_args.Count > 0) {
722                         string [] f = all_args.Dequeue ();
723
724                         for (int i = 0; i < f.Length; i++) {
725                                 if (f [i].Length > 0 && f [i][0] == '-')
726                                         f [i] = "/" + f [i].Substring (1);
727                                 
728                                 if (f [i] [0] == '@') {
729                                         string [] extra_args;
730                                         string response_file = f [i].Substring (1);
731
732                                         var resp_file_full = Path.Combine (base_dir, response_file);
733                                         extra_args = LoadArgs (resp_file_full);
734                                         if (extra_args == null) {
735                                                 Console.WriteLine ("Unable to open response file: " + resp_file_full);
736                                                 Environment.Exit (1);
737                                         }
738
739                                         all_args.Enqueue (extra_args);
740                                         continue;
741                                 }
742
743                                 if (CSCParseOption (f [i], ref f))
744                                         continue;
745                                 Console.WriteLine ("Failure with {0}", f [i]);
746                                 Environment.Exit (1);
747                         }
748                 }
749
750                 string [] source_files;
751                 //Console.WriteLine ("Base: {0} res: {1}", base_dir, response);
752                 using (var reader = new StreamReader (NativeName (base_dir + "\\" + response))) {
753                         source_files = reader.ReadToEnd ().Split ();
754                 }
755
756                 Array.Sort (source_files);
757
758                 StringBuilder sources = new StringBuilder ();
759                 foreach (string s in source_files) {
760                         if (s.Length == 0)
761                                 continue;
762
763                         string src = s.Replace ("/", "\\");
764                         if (src.StartsWith (@"Test\..\"))
765                                 src = src.Substring (8, src.Length - 8);
766
767                         sources.AppendFormat ("    <Compile Include=\"{0}\" />" + NewLine, src);
768                 }
769
770                 source_files = built_sources.Split ();
771                 Array.Sort (source_files);
772
773                 foreach (string s in source_files) {
774                         if (s.Length == 0)
775                                 continue;
776
777                         string src = s.Replace ("/", "\\");
778                         if (src.StartsWith (@"Test\..\"))
779                                 src = src.Substring (8, src.Length - 8);
780
781                         sources.AppendFormat ("    <Compile Include=\"{0}\" />" + NewLine, src);
782                 }
783                 sources.Remove (sources.Length - 1, 1);
784
785                 //if (library == "corlib-build") // otherwise, does not compile on fx_version == 4.0
786                 //{
787                 //    references.Add("System.dll");
788                 //    references.Add("System.Xml.dll");
789                 //}
790
791                 //if (library == "System.Core-build") // otherwise, slow compile. May be a transient need.
792                 //{
793                 //    this.ignore_warning.Add(1685);
794                 //    this.ignore_warning.Add(0436);
795                 //}
796
797                 var refs = new StringBuilder ();
798
799                 bool is_test = response.Contains ("_test_");
800                 if (is_test) {
801                         // F:\src\mono\mcs\class\lib\net_2_0\nunit.framework.dll
802                         // F:\src\mono\mcs\class\SomeProject\SomeProject_test_-net_2_0.csproj
803                         var nunitLibPath = string.Format (@"..\lib\{0}\nunit.framework.dll", profile);
804                         refs.Append (string.Format ("    <Reference Include=\"{0}\" />" + NewLine, nunitLibPath));
805                 }
806
807                 var resources = new StringBuilder ();
808                 if (embedded_resources.Count > 0) {
809                         resources.AppendFormat ("  <ItemGroup>" + NewLine);
810                         foreach (var dk in embedded_resources) {
811                                 resources.AppendFormat ("    <EmbeddedResource Include=\"{0}\">" + NewLine, dk.Key);
812                                 resources.AppendFormat ("      <LogicalName>{0}</LogicalName>" + NewLine, dk.Value);
813                                 resources.AppendFormat ("    </EmbeddedResource>" + NewLine);
814                         }
815                         resources.AppendFormat ("  </ItemGroup>" + NewLine);
816                 }
817         
818
819                 if (references.Count > 0 || reference_aliases.Count > 0) {
820                         // -r:mscorlib.dll -r:System.dll
821                         //<ProjectReference Include="..\corlib\corlib-basic.csproj">
822                         //  <Project>{155aef28-c81f-405d-9072-9d52780e3e70}</Project>
823                         //  <Name>corlib-basic</Name>
824                         //</ProjectReference>
825                         //<ProjectReference Include="..\System\System-basic.csproj">
826                         //  <Project>{2094e859-db2f-481f-9630-f89d31d9ed48}</Project>
827                         //  <Name>System-basic</Name>
828                         //</ProjectReference>
829                         var refdistinct = references.Distinct ();
830                         foreach (string r in refdistinct) {
831                                 
832                                 var match = GetMatchingCsproj (r, projects);
833                                 if (match != null) {
834                                         AddProjectReference (refs, Csproj, match, r, null);
835                                 } else {
836                                         if (showWarnings){
837                                                 Console.WriteLine ("{0}: Could not find a matching project reference for {1}", library, Path.GetFileName (r));
838                                                 Console.WriteLine ("  --> Adding reference with hintpath instead");
839                                         }
840                                         refs.Append ("    <Reference Include=\"" + r + "\">" + NewLine);
841                                         refs.Append ("      <SpecificVersion>False</SpecificVersion>" + NewLine);
842                                         refs.Append ("      <HintPath>" + r + "</HintPath>" + NewLine);
843                                         refs.Append ("      <Private>False</Private>" + NewLine);
844                                         refs.Append ("    </Reference>" + NewLine);
845                                 }
846                         }
847
848                         foreach (string r in reference_aliases) {
849                                 int index = r.IndexOf ('=');
850                                 string alias = r.Substring (0, index);
851                                 string assembly = r.Substring (index + 1);
852                                 var match = GetMatchingCsproj (assembly, projects, explicitPath: true);
853                                 if (match != null) {
854                                         AddProjectReference (refs, Csproj, match, r, alias);
855                                 } else {
856                                         throw new NotSupportedException (string.Format ("From {0}, could not find a matching project reference for {1}", library, r));
857                                         refs.Append ("    <Reference Include=\"" + assembly + "\">" + NewLine);
858                                         refs.Append ("      <SpecificVersion>False</SpecificVersion>" + NewLine);
859                                         refs.Append ("      <HintPath>" + r + "</HintPath>" + NewLine);
860                                         refs.Append ("      <Aliases>" + alias + "</Aliases>" + NewLine);
861                                         refs.Append ("    </Reference>" + NewLine);
862
863                                 }
864                         }
865                 }
866
867                 // Possible inputs:
868                 // ../class/lib/build/tmp/System.Xml.dll  [No longer possible, we should be removing this from order.xml]
869                 //   /class/lib/basic/System.Core.dll
870                 // <library_output>mcs.exe</library_output>
871                 string build_output_dir;
872                 if (LibraryOutput.Contains ("/"))
873                         build_output_dir = Path.GetDirectoryName (LibraryOutput);
874                 else
875                         build_output_dir = "bin\\Debug\\" + library;
876                 
877
878                 string postbuild_unix = string.Empty;
879                 string postbuild_windows = string.Empty;
880
881                 var postbuild =  
882                         "    <PostBuildEvent " + condition_unix + ">" + NewLine + postbuild_unix + NewLine + "    </PostBuildEvent>" + NewLine +
883                         "    <PostBuildEvent " + condition_windows + ">" + NewLine + postbuild_windows + NewLine + "    </PostBuildEvent>";
884                         
885
886                 bool basic_or_build = (library.Contains ("-basic") || library.Contains ("-build"));
887
888                 //
889                 // Replace the template values
890                 //
891
892                 string strongNameSection = "";
893                 if (StrongNameKeyFile != null){
894                         strongNameSection = String.Format (
895                                 "  <PropertyGroup>" + NewLine +
896                                 "    <SignAssembly>true</SignAssembly>" + NewLine +
897                                 "{1}" +
898                                 "  </PropertyGroup>" + NewLine +
899                                 "  <PropertyGroup>" + NewLine +
900                                 "    <AssemblyOriginatorKeyFile>{0}</AssemblyOriginatorKeyFile>" + NewLine +
901                                 "  </PropertyGroup>", StrongNameKeyFile, StrongNameDelaySign ? "    <DelaySign>true</DelaySign>" + NewLine : "");
902                 }
903                 Csproj.output = template.
904                         Replace ("@OUTPUTTYPE@", Target == Target.Library ? "Library" : "Exe").
905                         Replace ("@SIGNATURE@", strongNameSection).
906                         Replace ("@PROJECTGUID@", Csproj.projectGuid).
907                         Replace ("@DEFINES@", defines.ToString ()).
908                         Replace ("@DISABLEDWARNINGS@", string.Join (",", (from i in ignore_warning select i.ToString ()).ToArray ())).
909                         //Replace("@NOSTDLIB@", (basic_or_build || (!StdLib)) ? "<NoStdLib>true</NoStdLib>" : string.Empty).
910                         Replace ("@NOSTDLIB@", "<NoStdLib>" + (!StdLib).ToString () + "</NoStdLib>").
911                         Replace ("@NOCONFIG@", "<NoConfig>" + (!load_default_config).ToString () + "</NoConfig>").
912                         Replace ("@ALLOWUNSAFE@", Unsafe ? "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>" : "").
913                         Replace ("@FX_VERSION", fx_version).
914                         Replace ("@ASSEMBLYNAME@", Path.GetFileNameWithoutExtension (output_name)).
915                         Replace ("@OUTPUTDIR@", build_output_dir).
916                         Replace ("@DEFINECONSTANTS@", defines.ToString ()).
917                         Replace ("@DEBUG@", want_debugging_support ? "true" : "false").
918                         Replace ("@DEBUGTYPE@", want_debugging_support ? "full" : "pdbonly").
919                         Replace ("@REFERENCES@", refs.ToString ()).
920                         Replace ("@PREBUILD@", prebuild).
921                         Replace ("@STARTUPOBJECT@", main == null ? "" : $"<StartupObject>{main}</StartupObject>").
922                         Replace ("@POSTBUILD@", postbuild).
923                         //Replace ("@ADDITIONALLIBPATHS@", String.Format ("<AdditionalLibPaths>{0}</AdditionalLibPaths>", string.Join (",", libs.ToArray ()))).
924                         Replace ("@ADDITIONALLIBPATHS@", String.Empty).
925                         Replace ("@RESOURCES@", resources.ToString ()).
926                         Replace ("@OPTIMIZE@", Optimize ? "true" : "false").
927                         Replace ("@SOURCES@", sources.ToString ());
928
929                 //Console.WriteLine ("Generated {0}", ofile.Replace ("\\", "/"));
930                 using (var o = new StreamWriter (generatedProjFile)) {
931                         o.WriteLine (Csproj.output);
932                 }
933
934                 return Csproj;
935         }
936
937         void AddProjectReference (StringBuilder refs, VsCsproj result, MsbuildGenerator match, string r, string alias)
938         {
939                 refs.AppendFormat ("    <ProjectReference Include=\"{0}\">{1}", GetRelativePath (result.csProjFilename, match.CsprojFilename), NewLine);
940                 refs.Append ("      <Project>" + match.projectGuid + "</Project>" + NewLine);
941                 refs.Append ("      <Name>" + Path.GetFileNameWithoutExtension (match.CsprojFilename.Replace ('\\', Path.DirectorySeparatorChar)) + "</Name>" + NewLine);
942                 if (alias != null)
943                         refs.Append ("      <Aliases>" + alias + "</Aliases>");
944                 refs.Append ("    </ProjectReference>" + NewLine);
945                 if (!result.projReferences.Contains (match.Csproj))
946                         result.projReferences.Add (match.Csproj);
947         }
948
949         public static string GetRelativePath (string from, string to)
950         {
951                 from = from.Replace ("\\", "/");
952                 to = to.Replace ("\\", "/");
953                 var fromUri = new Uri (Path.GetFullPath (from));
954                 var toUri = new Uri (Path.GetFullPath (to));
955
956                 var ret =  fromUri.MakeRelativeUri (toUri).ToString ().Replace ("%5C", "\x5c");
957                 return ret;
958         }
959
960         MsbuildGenerator GetMatchingCsproj (string dllReferenceName, Dictionary<string,MsbuildGenerator> projects, bool explicitPath = false)
961         {
962                 // libDir would be "./../../class/lib/net_4_x for example
963                 // project 
964                 if (!dllReferenceName.EndsWith (".dll"))
965                         dllReferenceName += ".dll";
966
967                 var probe = Path.GetFullPath (Path.Combine (base_dir, dllReferenceName));
968                 foreach (var project in projects){
969                         if (probe == project.Value.AbsoluteLibraryOutput)
970                                 return project.Value;
971                 }
972
973                 // not explicit, search for the library in the lib path order specified
974
975                 foreach (var libDir in libs) {
976                         var abs = Path.GetFullPath (Path.Combine (base_dir, libDir));
977                         foreach (var project in projects){
978                                 probe = Path.Combine (abs, dllReferenceName);
979
980                                 if (probe == project.Value.AbsoluteLibraryOutput)
981                                         return project.Value;
982                         }
983                 }
984                 Console.WriteLine ("Did not find referenced {0} with libs={1}", dllReferenceName, String.Join (", ", libs));
985                 foreach (var p in projects) {
986                 //      Console.WriteLine ("{0}", p.Value.AbsoluteLibraryOutput);
987                 }
988                 return null;
989         }
990
991 }
992
993 public class Driver {
994
995         static IEnumerable<XElement> GetProjects (bool full = false)
996         {
997                 XDocument doc = XDocument.Load ("order.xml");
998                 foreach (XElement project in doc.Root.Elements ()) {
999                         string dir = project.Attribute ("dir").Value;
1000                         string library = project.Attribute ("library").Value;
1001                         var profile = project.Element ("profile").Value;
1002
1003                         // Skip facades for now, the tool doesn't know how to deal with them yet.
1004                         if (dir.Contains ("Facades"))
1005                                 continue;
1006
1007                         // These are currently broken, skip until they're fixed.
1008                         if (dir.StartsWith ("mcs") || dir.Contains ("apigen"))
1009                                 continue;
1010
1011                         //
1012                         // Do only class libraries for now
1013                         //
1014                         if (!(dir.StartsWith ("class") || dir.StartsWith ("mcs") || dir.StartsWith ("basic")))
1015                                 continue;
1016
1017                         if (full){
1018                                 if (!library.Contains ("tests"))
1019                                         yield return project;
1020                                 continue;
1021                         }
1022                         
1023                         //
1024                         // Do not do 2.1, it is not working yet
1025                         // Do not do basic, as there is no point (requires a system mcs to be installed).
1026                         //
1027                         if (library.Contains ("moonlight") || library.Contains ("-basic") || library.EndsWith ("bootstrap")  || library.Contains ("build"))
1028                                 continue;
1029
1030                         // The next ones are to make debugging easier for now
1031                         if (profile == "basic")
1032                                 continue;
1033                         if (profile != "net_4_x" || library.Contains ("tests"))
1034                                 continue;
1035
1036                         yield return project;
1037                 }
1038         }
1039
1040         static void Main (string [] args)
1041         {
1042                 if (!File.Exists ("genproj.cs")) {
1043                         Console.WriteLine ("This command must be executed from mono/msvc/scripts");
1044                         Environment.Exit (1);
1045                 }
1046
1047                 if (args.Length == 1 && args [0].ToLower ().Contains ("-h")) {
1048                         Console.WriteLine ("Usage:");
1049                         Console.WriteLine ("genproj.exe [visual_studio_release] [output_full_solutions]");
1050                         Console.WriteLine ("If output_full_solutions is false, only the main System*.dll");
1051                         Console.WriteLine (" assemblies (and dependencies) is included in the solution.");
1052                         Console.WriteLine ("Example:");
1053                         Console.WriteLine ("genproj.exe 2012 false");
1054                         Console.WriteLine ("genproj.exe with no arguments is equivalent to 'genproj.exe 2012 true'\n\n");
1055                         Console.WriteLine ("genproj.exe deps");
1056                         Console.WriteLine ("Generates a Makefile dependency file from the projects input");
1057                         Environment.Exit (0);
1058                 }
1059
1060                 var slnVersion = (args.Length > 0) ? args [0] : "2012";
1061                 bool fullSolutions = (args.Length > 1) ? bool.Parse (args [1]) : true;
1062
1063                 // To generate makefile depenedencies
1064                 var makefileDeps =  (args.Length > 0 && args [0] == "deps");
1065
1066                 var sln_gen = new SlnGenerator (slnVersion);
1067                 var four_five_sln_gen = new SlnGenerator (slnVersion);
1068                 var projects = new Dictionary<string,MsbuildGenerator> ();
1069
1070                 var duplicates = new List<string> ();
1071                 foreach (var project in GetProjects (makefileDeps)) {
1072                         var library_output = project.Element ("library_output").Value;
1073                         projects [library_output] = new MsbuildGenerator (project);
1074                 }
1075                 foreach (var project in GetProjects (makefileDeps)){
1076                         var library_output = project.Element ("library_output").Value;
1077                         var gen = projects [library_output];
1078                         try {
1079                                 var csproj = gen.Generate (projects);
1080                                 var csprojFilename = csproj.csProjFilename;
1081                                 if (!sln_gen.ContainsProjectIdentifier (csproj.library)) {
1082                                         sln_gen.Add (csproj);
1083                                 } else {
1084                                         duplicates.Add (csprojFilename);
1085                                 }
1086                                 
1087                         } catch (Exception e) {
1088                                 Console.WriteLine ("Error in {0}\n{1}", project, e);
1089                         }
1090                 }
1091
1092                 Func<MsbuildGenerator.VsCsproj, bool> additionalFilter;
1093                 additionalFilter = fullSolutions ? (Func<MsbuildGenerator.VsCsproj, bool>)null : IsCommonLibrary;
1094
1095                 FillSolution (four_five_sln_gen, MsbuildGenerator.profile_4_x, projects.Values, additionalFilter);
1096
1097                 if (duplicates.Count () > 0) {
1098                         var sb = new StringBuilder ();
1099                         sb.AppendLine ("WARNING: Skipped some project references, apparent duplicates in order.xml:");
1100                         foreach (var item in duplicates) {
1101                                 sb.AppendLine (item);
1102                         }
1103                         Console.WriteLine (sb.ToString ());
1104                 }
1105
1106                 WriteSolution (four_five_sln_gen, MakeSolutionName (MsbuildGenerator.profile_4_x));
1107
1108                 if (makefileDeps){
1109                         const string classDirPrefix = "./../../";
1110                         Console.WriteLine ("here {0}", sln_gen.libraries.Count);
1111                         foreach (var p in sln_gen.libraries){
1112                                 string rebasedOutput = RebaseToClassDirectory (MsbuildGenerator.GetRelativePath ("../../mcs/class", p.library_output));
1113                                 
1114                                 Console.Write ("{0}: ", rebasedOutput);
1115                                 foreach (var r in p.projReferences){
1116                                         var lo = r.library_output;
1117                                         if (lo.StartsWith (classDirPrefix))
1118                                                 lo = lo.Substring (classDirPrefix.Length);
1119                                         else
1120                                                 lo = "<<ERROR-dependency is not a class library>>";
1121                                         Console.Write ("{0} ", lo);
1122                                 }
1123                                 Console.Write ("\n\t(cd {0}; make {1})", p.MsbuildGenerator.dir, p.library_output);
1124                                 Console.WriteLine ("\n");
1125                         }
1126                 }
1127                 
1128                 // A few other optional solutions
1129                 // Solutions with 'everything' and the most common libraries used in development may be of interest
1130                 //WriteSolution (sln_gen, "mcs_full.sln");
1131                 //WriteSolution (small_full_sln_gen, "small_full.sln");
1132                 // The following may be useful if lacking visual studio or MonoDevelop, to bootstrap mono compiler self-hosting
1133                 //WriteSolution (basic_sln_gen, "mcs_basic.sln");
1134                 //WriteSolution (build_sln_gen, "mcs_build.sln");
1135         }
1136
1137         // Rebases a path, assuming that execution is taking place in the "class" subdirectory,
1138         // so it strips ../class/ from a path, which is a no-op
1139         static string RebaseToClassDirectory (string path)
1140         {
1141                 const string prefix = "../class/";
1142                 int p = path.IndexOf (prefix);
1143                 if (p == -1)
1144                         return path;
1145                 return path.Substring (0, p) + path.Substring (p+prefix.Length);
1146                 return path;
1147         }
1148         
1149         static string MakeSolutionName (string profileTag)
1150         {
1151                 return "net" + profileTag + ".sln";
1152         }
1153
1154         static void FillSolution (SlnGenerator solution, string profileString, IEnumerable<MsbuildGenerator> projects, Func<MsbuildGenerator.VsCsproj, bool> additionalFilter = null)
1155         {
1156                 foreach (var generator in projects) {
1157                         var vsCsproj = generator.Csproj;
1158                         if (!vsCsproj.library.Contains (profileString))
1159                                 continue;
1160                         if (additionalFilter != null && !additionalFilter (vsCsproj))
1161                                 continue;
1162                         var csprojFilename = vsCsproj.csProjFilename;
1163                         if (!solution.ContainsProjectIdentifier (vsCsproj.library)) {
1164                                 solution.Add (vsCsproj);
1165                                 RecursiveAddProj (solution, vsCsproj);
1166                         }
1167                 }
1168         }
1169
1170         static void RecursiveAddProj (SlnGenerator solution, MsbuildGenerator.VsCsproj vsCsproj, int recursiveDepth = 1)
1171         {
1172                 const int max_recursive = 16;
1173                 if (recursiveDepth > max_recursive) throw new Exception (string.Format ("Reached {0} levels of project dependency", max_recursive));
1174                 foreach (var projRef in vsCsproj.projReferences) {
1175                         if (!solution.ContainsProjectIdentifier (projRef.library)) {
1176                                 solution.Add (projRef);
1177                                 RecursiveAddProj (solution, projRef, recursiveDepth + 1);
1178                         }
1179                 }
1180         }
1181
1182         static void WriteSolution (SlnGenerator sln_gen, string slnfilename)
1183         {
1184                 Console.WriteLine (String.Format ("Writing solution {1}, with {0} projects", sln_gen.Count, slnfilename));
1185                 sln_gen.Write (slnfilename);
1186         }
1187
1188         static bool IsCommonLibrary (MsbuildGenerator.VsCsproj proj)
1189         {
1190                 var library = proj.library;
1191                 //if (library.Contains ("-basic"))
1192                 //      return true;
1193                 //if (library.Contains ("-build"))
1194                 //      return true;
1195                 //if (library.StartsWith ("corlib"))
1196                 //      return true;
1197                 if (library.StartsWith ("System-"))
1198                         return true;
1199                 if (library.StartsWith ("System.Xml"))
1200                         return true;
1201                 if (library.StartsWith ("System.Secu"))
1202                         return true;
1203                 if (library.StartsWith ("System.Configuration"))
1204                         return true;
1205                 if (library.StartsWith ("System.Core"))
1206                         return true;
1207                 //if (library.StartsWith ("Mono."))
1208                 //      return true;
1209
1210                 return false;
1211         }
1212 }