[genproj] Simplify strong name key handling
[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                 var fullPath = Path.GetDirectoryName (filename) + "/";
74                 
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);
83                         }
84                         sln.WriteLine ("Global");
85
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");
90
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);
98                         }
99                         sln.WriteLine ("\tEndGlobalSection");
100
101                         sln.WriteLine ("\tGlobalSection(SolutionProperties) = preSolution");
102                         sln.WriteLine ("\t\tHideSolutionNode = FALSE");
103                         sln.WriteLine ("\tEndGlobalSection");
104
105                         sln.WriteLine ("EndGlobal");
106                 }
107         }
108
109         internal bool ContainsProjectIdentifier (string projId)
110         {
111                 return libraries.FindIndex (x => (x.library == projId)) >= 0;
112         }
113
114         public int Count { get { return libraries.Count; } }
115 }
116
117 class MsbuildGenerator {
118         static readonly string NewLine = SlnGenerator.NewLine;
119         static XmlNamespaceManager xmlns;
120
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";
125
126         static void Usage ()
127         {
128                 Console.WriteLine ("Invalid argument");
129         }
130
131         static string template;
132         static MsbuildGenerator ()
133         {
134                 using (var input = new StreamReader ("csproj.tmpl")) {
135                         template = input.ReadToEnd ();
136                 }
137
138                 xmlns = new XmlNamespaceManager (new NameTable ());
139                 xmlns.AddNamespace ("x", "http://schemas.microsoft.com/developer/msbuild/2003");
140         }
141
142         // The directory as specified in order.xml
143         public string dir;
144         string library;
145         string projectGuid;
146         string fx_version;
147
148         XElement xproject;
149         public string CsprojFilename;
150
151         //
152         // Our base directory, this is relative to our exectution point mono/msvc/scripts
153         string base_dir;
154         string mcs_topdir;
155
156         public string LibraryOutput, AbsoluteLibraryOutput;
157
158         public MsbuildGenerator (XElement xproject)
159         {
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;
165
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
175                 };
176
177                 if (dir == "mcs") {
178                         mcs_topdir = "../";
179                         class_dir = "../class/";
180                         base_dir = "../../mcs/mcs";
181                 } else {
182                         mcs_topdir = "../";
183
184                         foreach (char c in dir) {
185                                 if (c == '/')
186                                         mcs_topdir = "..//" + mcs_topdir;
187                         }
188                         class_dir = mcs_topdir.Substring (3);
189
190                         base_dir = Path.Combine ("..", "..", "mcs", dir);
191                 }
192                 AbsoluteLibraryOutput = Path.GetFullPath (Path.Combine (base_dir, LibraryOutput));
193         }
194
195         string LookupOrGenerateGuid ()
196         {
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;
201                 }
202                 return "{" + Guid.NewGuid ().ToString ().ToUpper () + "}";
203         }
204
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;
220
221         // Currently unused
222 #pragma warning disable 0219, 0414
223         int WarningLevel = 4;
224
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;
238
239         // Class directory, relative to 
240         string class_dir;
241 #pragma warning restore 0219,414
242
243         readonly char [] argument_value_separator = new char [] { ';', ',' };
244
245         //
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.
248         //
249         bool CSCParseOption (string option, ref string [] args)
250         {
251                 int idx = option.IndexOf (':');
252                 string arg, value;
253
254                 if (idx == -1) {
255                         arg = option;
256                         value = "";
257                 } else {
258                         arg = option.Substring (0, idx);
259
260                         value = option.Substring (idx + 1);
261                 }
262
263                 switch (arg.ToLower (CultureInfo.InvariantCulture)) {
264                 case "/nologo":
265                         return true;
266
267                 case "/t":
268                 case "/target":
269                         switch (value) {
270                         case "exe":
271                                 Target = Target.Exe;
272                                 break;
273
274                         case "winexe":
275                                 Target = Target.WinExe;
276                                 break;
277
278                         case "library":
279                                 Target = Target.Library;
280                                 TargetExt = ".dll";
281                                 break;
282
283                         case "module":
284                                 Target = Target.Module;
285                                 TargetExt = ".netmodule";
286                                 break;
287
288                         default:
289                                 return false;
290                         }
291                         return true;
292
293                 case "/out":
294                         if (value.Length == 0) {
295                                 Usage ();
296                                 Environment.Exit (1);
297                         }
298                         OutputFile = value;
299                         return true;
300
301                 case "/o":
302                 case "/o+":
303                 case "/optimize":
304                 case "/optimize+":
305                         Optimize = true;
306                         return true;
307
308                 case "/o-":
309                 case "/optimize-":
310                         Optimize = false;
311                         return true;
312
313                 case "/incremental":
314                 case "/incremental+":
315                 case "/incremental-":
316                         // nothing.
317                         return true;
318
319                 case "/d":
320                 case "/define": {
321                                 if (value.Length == 0) {
322                                         Usage ();
323                                         Environment.Exit (1);
324                                 }
325
326                                 foreach (string d in value.Split (argument_value_separator)) {
327                                         if (defines.Length != 0)
328                                                 defines.Append (";");
329                                         defines.Append (d);
330                                 }
331
332                                 return true;
333                         }
334
335                 case "/bugreport":
336                         //
337                         // We should collect data, runtime, etc and store in the file specified
338                         //
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;
363                         }
364
365                         return true;
366
367                 case "/recurse":
368                         Console.WriteLine ("/recurse not supported");
369                         Environment.Exit (1);
370                         return true;
371
372                 case "/r":
373                 case "/reference": {
374                                 if (value.Length == 0) {
375                                         Console.WriteLine ("-reference requires an argument");
376                                         Environment.Exit (1);
377                                 }
378
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;
386                                         }
387
388                                         if (val.Length != 0)
389                                                 references.Add (val);
390                                 }
391                                 return true;
392                         }
393                 case "/main":
394                         main = value;
395                         return true;
396
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;
404                         
405                 case "/lib": {
406                                 libs.Add (value);
407                                 return true;
408                         }
409                 case "/win32icon": {
410                                 win32IconFile = value;
411                                 return true;
412                         }
413                 case "/debug-":
414                         want_debugging_support = false;
415                         return true;
416
417                 case "/debug":
418                 case "/debug+":
419                         want_debugging_support = true;
420                         return true;
421
422                 case "/checked":
423                 case "/checked+":
424                         Checked = true;
425                         return true;
426
427                 case "/checked-":
428                         Checked = false;
429                         return true;
430
431                 case "/clscheck":
432                 case "/clscheck+":
433                         return true;
434
435                 case "/clscheck-":
436                         VerifyClsCompliance = false;
437                         return true;
438
439                 case "/unsafe":
440                 case "/unsafe+":
441                         Unsafe = true;
442                         return true;
443
444                 case "/unsafe-":
445                         Unsafe = false;
446                         return true;
447
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);
455                         }
456                         return true;
457
458                 case "/-runtime":
459                         // Console.WriteLine ("Warning ignoring /runtime:v4");
460                         return true;
461
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);
468                         }
469                         return true;
470
471                 case "/warn":
472                         WarningLevel = Int32.Parse (value);
473                         return true;
474
475                 case "/nowarn": {
476                                 string [] warns;
477
478                                 if (value.Length == 0) {
479                                         Console.WriteLine ("/nowarn requires an argument");
480                                         Environment.Exit (1);
481                                 }
482
483                                 warns = value.Split (argument_value_separator);
484                                 foreach (string wc in warns) {
485                                         try {
486                                                 if (wc.Trim ().Length == 0)
487                                                         continue;
488
489                                                 int warn = Int32.Parse (wc);
490                                                 if (warn < 1) {
491                                                         throw new ArgumentOutOfRangeException ("warn");
492                                                 }
493                                                 ignore_warning.Add (warn);
494                                         } catch {
495                                                 Console.WriteLine (String.Format ("`{0}' is not a valid warning number", wc));
496                                                 Environment.Exit (1);
497                                         }
498                                 }
499                                 return true;
500                         }
501
502                 case "/noconfig":
503                         load_default_config = false;
504                         return true;
505
506                 case "/nostdlib":
507                 case "/nostdlib+":
508                         StdLib = false;
509                         return true;
510
511                 case "/nostdlib-":
512                         StdLib = true;
513                         return true;
514
515                 case "/fullpaths":
516                         return true;
517
518                 case "/keyfile":
519                         if (value == String.Empty) {
520                                 Console.WriteLine ("{0} requires an argument", arg);
521                                 Environment.Exit (1);
522                         }
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);
529                         }
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;
539
540                 case "/langversion":
541                         switch (value.ToLower (CultureInfo.InvariantCulture)) {
542                         case "iso-1":
543                                 Version = LanguageVersion.ISO_1;
544                                 return true;
545
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;
555                         }
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;
559
560                 case "/codepage":
561                         CodePage = value;
562                         return true;
563
564                 case "/publicsign":
565                         return true;
566
567                 case "/deterministic":
568                         return true;
569
570                 case "/runtimemetadataversion":
571                         return true;
572
573                 case "/-getresourcestrings":
574                         return true;
575                 }
576
577                 Console.WriteLine ("Failing with : {0}", arg);
578                 return false;
579         }
580
581         static string [] LoadArgs (string file)
582         {
583                 StreamReader f;
584                 var args = new List<string> ();
585                 string line;
586                 try {
587                         f = new StreamReader (file);
588                 } catch {
589                         return null;
590                 }
591
592                 StringBuilder sb = new StringBuilder ();
593
594                 while ((line = f.ReadLine ()) != null) {
595                         int t = line.Length;
596
597                         for (int i = 0; i < t; i++) {
598                                 char c = line [i];
599
600                                 if (c == '"' || c == '\'') {
601                                         char end = c;
602
603                                         for (i++; i < t; i++) {
604                                                 c = line [i];
605
606                                                 if (c == end)
607                                                         break;
608                                                 sb.Append (c);
609                                         }
610                                 } else if (c == ' ') {
611                                         if (sb.Length > 0) {
612                                                 args.Add (sb.ToString ());
613                                                 sb.Length = 0;
614                                         }
615                                 } else
616                                         sb.Append (c);
617                         }
618                         if (sb.Length > 0) {
619                                 args.Add (sb.ToString ());
620                                 sb.Length = 0;
621                         }
622                 }
623
624                 string [] ret_value = new string [args.Count];
625                 args.CopyTo (ret_value, 0);
626
627                 return ret_value;
628         }
629
630         static string Load (string f)
631         {
632                 var native = NativeName (f);
633
634                 if (File.Exists (native)) {
635                         using (var sr = new StreamReader (native)) {
636                                 return sr.ReadToEnd ();
637                         }
638                 } else
639                         return "";
640         }
641
642         public static string NativeName (string path)
643         {
644                 if (System.IO.Path.DirectorySeparatorChar == '/')
645                         return path.Replace ("\\", "/");
646                 else
647                         return path.Replace ("/", "\\");
648         }
649
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;
659         }
660
661         public VsCsproj Csproj;
662
663         void AppendResource (StringBuilder resources, string source, string logical)
664         {
665                 resources.AppendFormat ("    <EmbeddedResource Include=\"{0}\">" + NewLine, source);
666                 resources.AppendFormat ("      <LogicalName>{0}</LogicalName>" + NewLine, logical);
667                 resources.AppendFormat ("    </EmbeddedResource>" + NewLine);
668         }
669         
670         public VsCsproj Generate (string library_output, Dictionary<string,MsbuildGenerator> projects, bool showWarnings = false)
671         {
672                 var generatedProjFile = NativeName (Csproj.csProjFilename);
673                 //Console.WriteLine ("Generating: {0}", generatedProjFile);
674
675                 string boot, flags, output_name, built_sources, response, profile, reskey;
676
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;
685
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";
709                         }
710                 }
711                 //
712                 // Prebuild code, might be in inputs, check:
713                 //  inputs/LIBRARY.pre
714                 //
715                 string prebuild = GenerateStep (library, ".pre", "PreBuildEvent");
716                 string postbuild = GenerateStep (library, ".post", "PostBuildEvent");
717
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 ();
722
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);
726                                 
727                                 if (f [i] [0] == '@') {
728                                         string [] extra_args;
729                                         string response_file = f [i].Substring (1);
730
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);
736                                         }
737
738                                         all_args.Enqueue (extra_args);
739                                         continue;
740                                 }
741
742                                 if (CSCParseOption (f [i], ref f))
743                                         continue;
744                                 Console.WriteLine ("{library_output}: Failure with {0}", f [i]);
745                                 Environment.Exit (1);
746                         }
747                 }
748
749                 string [] source_files;
750                 using (var reader = new StreamReader (NativeName (base_dir + "\\" + response))) {
751                         source_files = reader.ReadToEnd ().Split ();
752                 }
753
754                 Array.Sort (source_files);
755
756                 StringBuilder sources = new StringBuilder ();
757                 foreach (string s in source_files) {
758                         if (s.Length == 0)
759                                 continue;
760
761                         string src = s.Replace ("/", "\\");
762                         if (src.StartsWith (@"Test\..\"))
763                                 src = src.Substring (8, src.Length - 8);
764
765                         sources.AppendFormat ("    <Compile Include=\"{0}\" />" + NewLine, src);
766                 }
767
768                 source_files = built_sources.Split ();
769                 Array.Sort (source_files);
770
771                 foreach (string s in source_files) {
772                         if (s.Length == 0)
773                                 continue;
774
775                         string src = s.Replace ("/", "\\");
776                         if (src.StartsWith (@"Test\..\"))
777                                 src = src.Substring (8, src.Length - 8);
778
779                         sources.AppendFormat ("    <Compile Include=\"{0}\" />" + NewLine, src);
780                 }
781                 sources.Remove (sources.Length - 1, 1);
782
783                 //if (library == "corlib-build") // otherwise, does not compile on fx_version == 4.0
784                 //{
785                 //    references.Add("System.dll");
786                 //    references.Add("System.Xml.dll");
787                 //}
788
789                 //if (library == "System.Core-build") // otherwise, slow compile. May be a transient need.
790                 //{
791                 //    this.ignore_warning.Add(1685);
792                 //    this.ignore_warning.Add(0436);
793                 //}
794
795                 var refs = new StringBuilder ();
796
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));
803                 }
804
805                 //
806                 // Generate resource referenced from the command line
807                 //
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");
814                                 
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)) {
819                                                 
820                                                 source = GetRelativePath (base_dir + "/", probe_prebuilt);
821                                         }
822                                 }
823                                 AppendResource (resources, source, dk.Value);
824                         }
825                 }
826                 //
827                 // Generate resources that were part of the explicit <resource> node
828                 //
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);
836                                 }
837                                 AppendResource (resources, pair.Substring (p+1), pair.Substring (0, p) + ".resources");
838                         }
839                 }
840                 if (resources.Length > 0){
841                         resources.Insert (0, "  <ItemGroup>" + NewLine);
842                         resources.AppendFormat ("  </ItemGroup>" + NewLine);
843                 }
844
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) {
857                                 
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");
865                                         }
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);
871                                 }
872                         }
873
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);
888
889                                 }
890                         }
891                 }
892
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;
902
903                 if (build_output_dir.Contains ("-linux") || build_output_dir.Contains ("-darwin") || build_output_dir.Contains ("-win32"))
904                         build_output_dir = build_output_dir
905                                 .Replace ("-linux", "-$(HostPlatform)")
906                                 .Replace ("-darwin", "-$(HostPlatform)")
907                                 .Replace ("-win32", "-$(HostPlatform)");
908
909                 bool basic_or_build = (library.Contains ("-basic") || library.Contains ("-build"));
910
911                 //
912                 // Replace the template values
913                 //
914
915                 string strongNameSection = "";
916                 if (StrongNameKeyFile != null){
917                         strongNameSection = String.Format (
918                                 "    <SignAssembly>true</SignAssembly>" + NewLine +
919                                 "{1}" +
920                                 "    <AssemblyOriginatorKeyFile>{0}</AssemblyOriginatorKeyFile>",
921                                 StrongNameKeyFile, StrongNameDelaySign ? "    <DelaySign>true</DelaySign>" + NewLine : "");
922                 }
923                 Csproj.output = template.
924                         Replace ("@OUTPUTTYPE@", Target == Target.Library ? "Library" : "Exe").
925                         Replace ("@SIGNATURE@", strongNameSection).
926                         Replace ("@PROJECTGUID@", Csproj.projectGuid).
927                         Replace ("@DEFINES@", defines.ToString ()).
928                         Replace ("@DISABLEDWARNINGS@", string.Join (",", (from i in ignore_warning select i.ToString ()).ToArray ())).
929                         //Replace("@NOSTDLIB@", (basic_or_build || (!StdLib)) ? "<NoStdLib>true</NoStdLib>" : string.Empty).
930                         Replace ("@NOSTDLIB@", "<NoStdLib>" + (!StdLib).ToString () + "</NoStdLib>").
931                         Replace ("@NOCONFIG@", "<NoConfig>" + (!load_default_config).ToString () + "</NoConfig>").
932                         Replace ("@ALLOWUNSAFE@", Unsafe ? "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>" : "").
933                         Replace ("@FX_VERSION", fx_version).
934                         Replace ("@ASSEMBLYNAME@", Path.GetFileNameWithoutExtension (output_name)).
935                         Replace ("@OUTPUTDIR@", build_output_dir).
936                         Replace ("@OUTPUTSUFFIX@", Path.GetFileName (build_output_dir)).
937                         Replace ("@DEFINECONSTANTS@", defines.ToString ()).
938                         Replace ("@DEBUG@", want_debugging_support ? "true" : "false").
939                         Replace ("@DEBUGTYPE@", want_debugging_support ? "full" : "pdbonly").
940                         Replace ("@REFERENCES@", refs.ToString ()).
941                         Replace ("@PREBUILD@", prebuild).
942                         Replace ("@POSTBUILD@", postbuild).
943                         Replace ("@STARTUPOBJECT@", main == null ? "" : $"<StartupObject>{main}</StartupObject>").
944                         //Replace ("@ADDITIONALLIBPATHS@", String.Format ("<AdditionalLibPaths>{0}</AdditionalLibPaths>", string.Join (",", libs.ToArray ()))).
945                         Replace ("@ADDITIONALLIBPATHS@", String.Empty).
946                         Replace ("@RESOURCES@", resources.ToString ()).
947                         Replace ("@OPTIMIZE@", Optimize ? "true" : "false").
948                         Replace ("@SOURCES@", sources.ToString ());
949
950                 //Console.WriteLine ("Generated {0}", ofile.Replace ("\\", "/"));
951                 using (var o = new StreamWriter (generatedProjFile)) {
952                         o.WriteLine (Csproj.output);
953                 }
954
955                 return Csproj;
956         }
957
958         string GenerateStep (string library, string suffix, string eventKey)
959         {
960                 string target = Load (library + suffix);
961                 string target_windows, target_unix;
962
963                 int q = library.IndexOf ("-");
964                 if (q != -1)
965                         target = target + Load (library.Substring (0, q) + suffix);
966
967                 if (target.IndexOf ("@MONO@") != -1){
968                         target_unix = target.Replace ("@MONO@", "mono").Replace ("@CAT@", "cat");
969                         target_windows = target.Replace ("@MONO@", "").Replace ("@CAT@", "type");
970                 } else {
971                         target_unix = target.Replace ("jay.exe", "jay");
972                         target_windows = target;
973                 }
974                 target_unix = target_unix.Replace ("@COPY@", "cp");
975                 target_windows = target_unix.Replace ("@COPY@", "copy");
976
977                 target_unix = target_unix.Replace ("\r", "");
978                 const string condition_unix    = "Condition=\" '$(OS)' != 'Windows_NT' \"";
979                 const string condition_windows = "Condition=\" '$(OS)' == 'Windows_NT' \"";
980                 var result =
981                         $"    <{eventKey} {condition_unix}>\n{target_unix}\n    </{eventKey}>{NewLine}" +
982                         $"    <{eventKey} {condition_windows}>{NewLine}{target_windows}{NewLine}    </{eventKey}>";
983                 return result;
984         }
985         
986         void AddProjectReference (StringBuilder refs, VsCsproj result, MsbuildGenerator match, string r, string alias)
987         {
988                 refs.AppendFormat ("    <ProjectReference Include=\"{0}\">{1}", GetRelativePath (result.csProjFilename, match.CsprojFilename), NewLine);
989                 refs.Append ("      <Project>" + match.projectGuid + "</Project>" + NewLine);
990                 refs.Append ("      <Name>" + Path.GetFileNameWithoutExtension (match.CsprojFilename.Replace ('\\', Path.DirectorySeparatorChar)) + "</Name>" + NewLine);
991                 if (alias != null)
992                         refs.Append ("      <Aliases>" + alias + "</Aliases>");
993                 refs.Append ("    </ProjectReference>" + NewLine);
994                 if (!result.projReferences.Contains (match.Csproj))
995                         result.projReferences.Add (match.Csproj);
996         }
997
998         public static string GetRelativePath (string from, string to)
999         {
1000                 from = from.Replace ("\\", "/");
1001                 to = to.Replace ("\\", "/");
1002                 var fromUri = new Uri (Path.GetFullPath (from));
1003                 var toUri = new Uri (Path.GetFullPath (to));
1004
1005                 var ret =  fromUri.MakeRelativeUri (toUri).ToString ().Replace ("%5C", "\x5c");
1006                 return ret;
1007         }
1008
1009         MsbuildGenerator GetMatchingCsproj (string library_output, string dllReferenceName, Dictionary<string,MsbuildGenerator> projects, bool explicitPath = false)
1010         {
1011                 // libDir would be "./../../class/lib/net_4_x for example
1012                 // project 
1013                 if (!dllReferenceName.EndsWith (".dll") && !dllReferenceName.EndsWith (".exe"))
1014                         dllReferenceName += ".dll";
1015
1016                 var probe = Path.GetFullPath (Path.Combine (base_dir, dllReferenceName));
1017                 foreach (var project in projects){
1018                         if (probe == project.Value.AbsoluteLibraryOutput)
1019                                 return project.Value;
1020                 }
1021
1022                 // not explicit, search for the library in the lib path order specified
1023
1024                 foreach (var libDir in libs) {
1025                         var abs = Path.GetFullPath (Path.Combine (base_dir, libDir));
1026                         foreach (var project in projects){
1027                                 probe = Path.Combine (abs, dllReferenceName);
1028
1029                                 if (probe == project.Value.AbsoluteLibraryOutput)
1030                                         return project.Value;
1031                         }
1032                 }
1033
1034                 // Last attempt, try to find the library in all the projects
1035                 foreach (var project in projects) {
1036                         if (project.Value.AbsoluteLibraryOutput.EndsWith (dllReferenceName))
1037                                 return project.Value;
1038
1039                 }
1040                 var ljoined = String.Join (", ", libs);
1041                 Console.WriteLine ($"{library_output}: did not find referenced {dllReferenceName} with libs={ljoined}");
1042                 foreach (var p in projects) {
1043                         Console.WriteLine ("{0}", p.Value.AbsoluteLibraryOutput);
1044                 }
1045                 return null;
1046         }
1047
1048 }
1049
1050 public class Driver {
1051
1052         static IEnumerable<XElement> GetProjects (bool full = false)
1053         {
1054                 XDocument doc = XDocument.Load ("order.xml");
1055                 foreach (XElement project in doc.Root.Elements ()) {
1056                         string dir = project.Attribute ("dir").Value;
1057                         string library = project.Attribute ("library").Value;
1058                         var profile = project.Element ("profile").Value;
1059
1060 #if false
1061                         // Skip facades for now, the tool doesn't know how to deal with them yet.
1062                         if (dir.Contains ("Facades"))
1063                                 continue;
1064
1065                         // These are currently broken, skip until they're fixed.
1066                         if (dir.StartsWith ("mcs") || dir.Contains ("apigen"))
1067                                 continue;
1068
1069                         //
1070                         // Do only class libraries for now
1071                         //
1072                         if (!(dir.StartsWith ("class") || dir.StartsWith ("mcs") || dir.StartsWith ("basic")))
1073                                 continue;
1074
1075                         if (full){
1076                                 if (!library.Contains ("tests"))
1077                                         yield return project;
1078                                 continue;
1079                         }
1080 #endif
1081                         //
1082                         // Do not do 2.1, it is not working yet
1083                         // Do not do basic, as there is no point (requires a system mcs to be installed).
1084                         //
1085                         if (library.Contains ("moonlight") || library.Contains ("-basic") || library.EndsWith ("bootstrap")  || library.Contains ("build"))
1086                                 continue;
1087
1088                         // The next ones are to make debugging easier for now
1089                         if (profile == "basic")
1090                                 continue;
1091
1092                         // For now -- problem is, our resolver currently only considers the assembly name, and we ahve
1093                         // conflicing 2.0 and 2.4 versions so for now, we just skip the nunit20 versions
1094                         if (dir.Contains ("nunit20"))
1095                                 continue;
1096                         
1097 #if true
1098                         if (profile != "net_4_x" || library.Contains ("tests")) 
1099                                 continue;
1100 #endif
1101                         //Console.WriteLine ("Going to handle {0}", library);
1102                         yield return project;
1103                 }
1104         }
1105
1106         static void Main (string [] args)
1107         {
1108                 if (!File.Exists ("genproj.cs")) {
1109                         Console.WriteLine ("This command must be executed from mono/msvc/scripts");
1110                         Environment.Exit (1);
1111                 }
1112
1113                 if (args.Length == 1 && args [0].ToLower ().Contains ("-h")) {
1114                         Console.WriteLine ("Usage:");
1115                         Console.WriteLine ("genproj.exe [visual_studio_release] [output_full_solutions]");
1116                         Console.WriteLine ("If output_full_solutions is false, only the main System*.dll");
1117                         Console.WriteLine (" assemblies (and dependencies) is included in the solution.");
1118                         Console.WriteLine ("Example:");
1119                         Console.WriteLine ("genproj.exe 2012 false");
1120                         Console.WriteLine ("genproj.exe with no arguments is equivalent to 'genproj.exe 2012 true'\n\n");
1121                         Console.WriteLine ("genproj.exe deps");
1122                         Console.WriteLine ("Generates a Makefile dependency file from the projects input");
1123                         Environment.Exit (0);
1124                 }
1125
1126                 var slnVersion = (args.Length > 0) ? args [0] : "2012";
1127                 bool fullSolutions = (args.Length > 1) ? bool.Parse (args [1]) : true;
1128
1129                 // To generate makefile depenedencies
1130                 var makefileDeps =  (args.Length > 0 && args [0] == "deps");
1131
1132                 var sln_gen = new SlnGenerator (slnVersion);
1133                 var four_five_sln_gen = new SlnGenerator (slnVersion);
1134                 var projects = new Dictionary<string,MsbuildGenerator> ();
1135
1136                 var duplicates = new List<string> ();
1137                 foreach (var project in GetProjects (makefileDeps)) {
1138                         var library_output = project.Element ("library_output").Value;
1139                         projects [library_output] = new MsbuildGenerator (project);
1140                 }
1141                 foreach (var project in GetProjects (makefileDeps)){
1142                         var library_output = project.Element ("library_output").Value;
1143                         //Console.WriteLine ("=== {0} ===", library_output);
1144                         var gen = projects [library_output];
1145                         try {
1146                                 var csproj = gen.Generate (library_output, projects);
1147                                 var csprojFilename = csproj.csProjFilename;
1148                                 if (!sln_gen.ContainsProjectIdentifier (csproj.library)) {
1149                                         sln_gen.Add (csproj);
1150                                 } else {
1151                                         duplicates.Add (csprojFilename);
1152                                 }
1153                                 
1154                         } catch (Exception e) {
1155                                 Console.WriteLine ("Error in {0}\n{1}", project, e);
1156                         }
1157                 }
1158
1159                 Func<MsbuildGenerator.VsCsproj, bool> additionalFilter;
1160                 additionalFilter = fullSolutions ? (Func<MsbuildGenerator.VsCsproj, bool>)null : IsCommonLibrary;
1161
1162                 FillSolution (four_five_sln_gen, MsbuildGenerator.profile_4_x, projects.Values, additionalFilter);
1163
1164                 if (duplicates.Count () > 0) {
1165                         var sb = new StringBuilder ();
1166                         sb.AppendLine ("WARNING: Skipped some project references, apparent duplicates in order.xml:");
1167                         foreach (var item in duplicates) {
1168                                 sb.AppendLine (item);
1169                         }
1170                         Console.WriteLine (sb.ToString ());
1171                 }
1172
1173                 WriteSolution (four_five_sln_gen, Path.Combine ("..", "..", MakeSolutionName (MsbuildGenerator.profile_4_x)));
1174
1175                 if (makefileDeps){
1176                         const string classDirPrefix = "./../../";
1177                         Console.WriteLine ("here {0}", sln_gen.libraries.Count);
1178                         foreach (var p in sln_gen.libraries){
1179                                 string rebasedOutput = RebaseToClassDirectory (MsbuildGenerator.GetRelativePath ("../../mcs/class", p.library_output));
1180                                 
1181                                 Console.Write ("{0}: ", rebasedOutput);
1182                                 foreach (var r in p.projReferences){
1183                                         var lo = r.library_output;
1184                                         if (lo.StartsWith (classDirPrefix))
1185                                                 lo = lo.Substring (classDirPrefix.Length);
1186                                         else
1187                                                 lo = "<<ERROR-dependency is not a class library>>";
1188                                         Console.Write ("{0} ", lo);
1189                                 }
1190                                 Console.Write ("\n\t(cd {0}; make {1})", p.MsbuildGenerator.dir, p.library_output);
1191                                 Console.WriteLine ("\n");
1192                         }
1193                 }
1194                 
1195                 // A few other optional solutions
1196                 // Solutions with 'everything' and the most common libraries used in development may be of interest
1197                 //WriteSolution (sln_gen, "./mcs_full.sln");
1198                 //WriteSolution (small_full_sln_gen, "small_full.sln");
1199                 // The following may be useful if lacking visual studio or MonoDevelop, to bootstrap mono compiler self-hosting
1200                 //WriteSolution (basic_sln_gen, "mcs_basic.sln");
1201                 //WriteSolution (build_sln_gen, "mcs_build.sln");
1202         }
1203
1204         // Rebases a path, assuming that execution is taking place in the "class" subdirectory,
1205         // so it strips ../class/ from a path, which is a no-op
1206         static string RebaseToClassDirectory (string path)
1207         {
1208                 const string prefix = "../class/";
1209                 int p = path.IndexOf (prefix);
1210                 if (p == -1)
1211                         return path;
1212                 return path.Substring (0, p) + path.Substring (p+prefix.Length);
1213                 return path;
1214         }
1215         
1216         static string MakeSolutionName (string profileTag)
1217         {
1218                 return "net" + profileTag + ".sln";
1219         }
1220
1221         static void FillSolution (SlnGenerator solution, string profileString, IEnumerable<MsbuildGenerator> projects, Func<MsbuildGenerator.VsCsproj, bool> additionalFilter = null)
1222         {
1223                 foreach (var generator in projects) {
1224                         var vsCsproj = generator.Csproj;
1225                         if (!vsCsproj.library.Contains (profileString))
1226                                 continue;
1227                         if (additionalFilter != null && !additionalFilter (vsCsproj))
1228                                 continue;
1229                         var csprojFilename = vsCsproj.csProjFilename;
1230                         if (!solution.ContainsProjectIdentifier (vsCsproj.library)) {
1231                                 solution.Add (vsCsproj);
1232                                 RecursiveAddProj (solution, vsCsproj);
1233                         }
1234                 }
1235         }
1236
1237         static void RecursiveAddProj (SlnGenerator solution, MsbuildGenerator.VsCsproj vsCsproj, int recursiveDepth = 1)
1238         {
1239                 const int max_recursive = 16;
1240                 if (recursiveDepth > max_recursive) throw new Exception (string.Format ("Reached {0} levels of project dependency", max_recursive));
1241                 foreach (var projRef in vsCsproj.projReferences) {
1242                         if (!solution.ContainsProjectIdentifier (projRef.library)) {
1243                                 solution.Add (projRef);
1244                                 RecursiveAddProj (solution, projRef, recursiveDepth + 1);
1245                         }
1246                 }
1247         }
1248
1249         static void WriteSolution (SlnGenerator sln_gen, string slnfilename)
1250         {
1251                 Console.WriteLine (String.Format ("Writing solution {1}, with {0} projects", sln_gen.Count, slnfilename));
1252                 sln_gen.Write (slnfilename);
1253         }
1254
1255         static bool IsCommonLibrary (MsbuildGenerator.VsCsproj proj)
1256         {
1257                 var library = proj.library;
1258                 //if (library.Contains ("-basic"))
1259                 //      return true;
1260                 //if (library.Contains ("-build"))
1261                 //      return true;
1262                 //if (library.StartsWith ("corlib"))
1263                 //      return true;
1264                 if (library.StartsWith ("System-"))
1265                         return true;
1266                 if (library.StartsWith ("System.Xml"))
1267                         return true;
1268                 if (library.StartsWith ("System.Secu"))
1269                         return true;
1270                 if (library.StartsWith ("System.Configuration"))
1271                         return true;
1272                 if (library.StartsWith ("System.Core"))
1273                         return true;
1274                 //if (library.StartsWith ("Mono."))
1275                 //      return true;
1276
1277                 return false;
1278         }
1279 }