4e2713f586d9d6e3901a306b5b1c31e2d30833c7
[mono.git] / msvc / scripts / genproj.cs
1 using System;
2 using System.IO;
3 using System.Collections.Generic;
4 using System.Text;
5 using System.Globalization;
6 using System.Xml.Linq;
7
8 public enum Target {
9         Library, Exe, Module, WinExe
10 }
11
12 public enum LanguageVersion
13 {
14         ISO_1           = 1,
15         Default_MCS     = 2,
16         ISO_2           = 3,
17         LINQ            = 4,
18         Future          = 5,
19         Default         = LINQ
20 }
21
22 class MsbuildGenerator {
23         static void Usage ()
24         {
25                 Console.WriteLine ("Invalid argument");
26         }
27
28         static string template;
29         static MsbuildGenerator ()
30         {
31                 using (var input = new StreamReader ("csproj.tmpl")){
32                         template = input.ReadToEnd ();
33                 }
34         }
35
36         // The directory as specified in order.xml
37         string dir;
38
39         //
40         // Our base directory, this is relative to our exectution point mono/msvc/scripts
41         string base_dir;
42
43         string mcs_topdir;
44
45         // Class directory, relative to 
46         string class_dir;
47         
48         public MsbuildGenerator (string dir)
49         {
50                 this.dir = dir;
51                 
52                 if (dir == "mcs"){
53                         mcs_topdir = "..\\";
54                         class_dir = "..\\class\\";
55                         base_dir = "..\\..\\..\\mcs\\mcs";
56                 } else {
57                         mcs_topdir = "..\\";
58                         
59                         foreach (char c in dir){
60                                 if (c == '/')
61                                         mcs_topdir = "..\\" + mcs_topdir;
62                         }
63                         class_dir = mcs_topdir.Substring (3);
64                         
65                         base_dir = "..\\..\\..\\mcs\\" + dir;
66                 }
67         }
68         
69         // Currently used
70         bool Unsafe = false;
71         StringBuilder defines = new StringBuilder ();
72         bool StdLib = true;
73
74         // Currently unused
75         Target Target = Target.Exe;
76         string TargetExt = ".exe";
77         string OutputFile;
78         bool Optimize = true;
79         bool VerifyClsCompliance = true;
80
81         string win32IconFile;
82         bool want_debugging_support = false;
83         bool Checked = false;
84         bool WarningsAreErrors;
85         Dictionary<string,string> embedded_resources = new Dictionary<string,string> ();
86         List<string> references = new List<string> ();
87         List<string> reference_aliases = new List<string> ();
88         List<string> warning_as_error = new List<string> ();
89         int WarningLevel = 4;
90         List<int> ignore_warning = new List<int> ();
91         bool load_default_config = true;
92         string StrongNameKeyFile;
93         string StrongNameKeyContainer;
94         bool StrongNameDelaySign = false;
95         LanguageVersion Version = LanguageVersion.Default;
96         string CodePage;
97
98         readonly char[] argument_value_separator = new char [] { ';', ',' };
99
100         //
101         // This parses the -arg and /arg options to the compiler, even if the strings
102         // in the following text use "/arg" on the strings.
103         //
104         bool CSCParseOption (string option, ref string [] args)
105         {
106                 int idx = option.IndexOf (':');
107                 string arg, value;
108
109                 if (idx == -1){
110                         arg = option;
111                         value = "";
112                 } else {
113                         arg = option.Substring (0, idx);
114
115                         value = option.Substring (idx + 1);
116                 }
117
118                 switch (arg.ToLower (CultureInfo.InvariantCulture)){
119                 case "/nologo":
120                         return true;
121
122                 case "/t":
123                 case "/target":
124                         switch (value){
125                         case "exe":
126                                 Target = Target.Exe;
127                                 break;
128
129                         case "winexe":
130                                 Target = Target.WinExe;
131                                 break;
132
133                         case "library":
134                                 Target = Target.Library;
135                                 TargetExt = ".dll";
136                                 break;
137
138                         case "module":
139                                 Target = Target.Module;
140                                 TargetExt = ".netmodule";
141                                 break;
142
143                         default:
144                                 return false;
145                         }
146                         return true;
147
148                 case "/out":
149                         if (value.Length == 0){
150                                 Usage ();
151                                 Environment.Exit (1);
152                         }
153                         OutputFile = value;
154                         return true;
155
156                 case "/o":
157                 case "/o+":
158                 case "/optimize":
159                 case "/optimize+":
160                         Optimize = true;
161                         return true;
162
163                 case "/o-":
164                 case "/optimize-":
165                         Optimize = false;
166                         return true;
167
168                 case "/incremental":
169                 case "/incremental+":
170                 case "/incremental-":
171                         // nothing.
172                         return true;
173
174                 case "/d":
175                 case "/define": {
176                         if (value.Length == 0){
177                                 Usage ();
178                                 Environment.Exit (1);
179                         }
180
181                         foreach (string d in value.Split (argument_value_separator)){
182                                 if (defines.Length != 0)
183                                         defines.Append (";");
184                                 defines.Append (d);
185                         }
186
187                         return true;
188                 }
189
190                 case "/bugreport":
191                         //
192                         // We should collect data, runtime, etc and store in the file specified
193                         //
194                         return true;
195                 case "/linkres":
196                 case "/linkresource":
197                 case "/res":
198                 case "/resource":
199                         bool embeded = arg [1] == 'r' || arg [1] == 'R';
200                         string[] s = value.Split (argument_value_separator);
201                         switch (s.Length) {
202                         case 1:
203                                 if (s[0].Length == 0)
204                                         goto default;
205                                 embedded_resources [s[0]] = Path.GetFileName (s[0]);
206                                 break;
207                         case 2:
208                                 embedded_resources [s [0]] = s [1];
209                                 break;
210                         case 3:
211                                 Console.WriteLine ("Does not support this method yet: {0}", arg);
212                                 Environment.Exit (1);
213                                 break;
214                         default:
215                                 Console.WriteLine ("Wrong number of arguments for option `{0}'", option);
216                                 Environment.Exit (1);
217                                 break;
218                                 
219                         }
220
221                         return true;
222                                 
223                 case "/recurse":
224                         Console.WriteLine ("/recurse not supported");
225                         Environment.Exit (1);
226                         return true;
227
228                 case "/r":
229                 case "/reference": {
230                         if (value.Length == 0){
231                                 Console.WriteLine ("-reference requires an argument");
232                                 Environment.Exit (1);
233                         }
234
235                         string[] refs = value.Split (argument_value_separator);
236                         foreach (string r in refs){
237                                 string val = r;
238                                 int index = val.IndexOf ('=');
239                                 if (index > -1) {
240                                         reference_aliases.Add (r);
241                                         continue;
242                                 }
243
244                                 if (val.Length != 0)
245                                         references.Add (val);
246                         }
247                         return true;
248                 }
249                 case "/main":
250                 case "/m":
251                 case "/addmodule": 
252                 case "/win32res":
253                 case "/doc": 
254                 case "/lib": 
255                 {
256                         Console.WriteLine ("{0} = not supported", arg);
257                         throw new Exception ();
258                 }
259                 case "/win32icon": {
260                         win32IconFile = value;
261                         return true;
262                 }
263                 case "/debug-":
264                         want_debugging_support = false;
265                         return true;
266                                 
267                 case "/debug":
268                 case "/debug+":
269                         want_debugging_support = true;
270                         return true;
271
272                 case "/checked":
273                 case "/checked+":
274                         Checked = true;
275                         return true;
276
277                 case "/checked-":
278                         Checked = false;
279                         return true;
280
281                 case "/clscheck":
282                 case "/clscheck+":
283                         return true;
284
285                 case "/clscheck-":
286                         VerifyClsCompliance = false;
287                         return true;
288
289                 case "/unsafe":
290                 case "/unsafe+":
291                         Unsafe = true;
292                         return true;
293
294                 case "/unsafe-":
295                         Unsafe = false;
296                         return true;
297
298                 case "/warnaserror":
299                 case "/warnaserror+":
300                         if (value.Length == 0) {
301                                 WarningsAreErrors = true;
302                         } else {
303                                 foreach (string wid in value.Split (argument_value_separator))
304                                         warning_as_error.Add (wid);
305                         }
306                         return true;
307
308                 case "/warnaserror-":
309                         if (value.Length == 0) {
310                                 WarningsAreErrors = false;
311                         } else {
312                                 foreach (string wid in value.Split (argument_value_separator))
313                                         warning_as_error.Remove (wid);
314                         }
315                         return true;
316
317                 case "/warn":
318                         WarningLevel = Int32.Parse (value);
319                         return true;
320
321                 case "/nowarn": {
322                         string [] warns;
323
324                         if (value.Length == 0){
325                                 Console.WriteLine ("/nowarn requires an argument");
326                                 Environment.Exit (1);
327                         }
328
329                         warns = value.Split (argument_value_separator);
330                         foreach (string wc in warns){
331                                 try {
332                                         if (wc.Trim ().Length == 0)
333                                                 continue;
334
335                                         int warn = Int32.Parse (wc);
336                                         if (warn < 1) {
337                                                 throw new ArgumentOutOfRangeException("warn");
338                                         }
339                                         ignore_warning.Add (warn);
340                                 } catch {
341                                         Console.WriteLine (String.Format("`{0}' is not a valid warning number", wc));
342                                         Environment.Exit (1);
343                                 }
344                         }
345                         return true;
346                 }
347
348                 case "/noconfig":
349                         load_default_config = false;
350                         return true;
351
352                 case "/nostdlib":
353                 case "/nostdlib+":
354                         StdLib = false;
355                         return true;
356
357                 case "/nostdlib-":
358                         StdLib = true;
359                         return true;
360
361                 case "/fullpaths":
362                         return true;
363
364                 case "/keyfile":
365                         if (value == String.Empty) {
366                                 Console.WriteLine ("{0} requires an argument", arg);
367                                 Environment.Exit (1);
368                         }
369                         StrongNameKeyFile = value;
370                         return true;
371                 case "/keycontainer":
372                         if (value == String.Empty) {
373                                 Console.WriteLine ("{0} requires an argument", arg);
374                                 Environment.Exit (1);
375                         }
376                         StrongNameKeyContainer = value;
377                         return true;
378                 case "/delaysign+":
379                         StrongNameDelaySign = true;
380                         return true;
381                 case "/delaysign-":
382                         StrongNameDelaySign = false;
383                         return true;
384
385                 case "/langversion":
386                         switch (value.ToLower (CultureInfo.InvariantCulture)) {
387                         case "iso-1":
388                                 Version = LanguageVersion.ISO_1;
389                                 return true;
390                                         
391                         case "default":
392                                 Version = LanguageVersion.Default;
393                                 return true;
394                         case "iso-2":
395                                 Version = LanguageVersion.ISO_2;
396                                 return true;
397                         case "future":
398                                 Version = LanguageVersion.Future;
399                                 return true;
400                         }
401                         Console.WriteLine ("Invalid option `{0}' for /langversion. It must be either `ISO-1', `ISO-2' or `Default'", value);
402                         Environment.Exit (1);
403                         return true;
404                         
405                 case "/codepage":
406                         CodePage = value;
407                         return true;
408                 }
409
410                 return false;
411         }
412
413         static string [] LoadArgs (string file)
414         {
415                 StreamReader f;
416                 var args = new List<string> ();
417                 string line;
418                 try {
419                         f = new StreamReader (file);
420                 } catch {
421                         return null;
422                 }
423                 
424                 StringBuilder sb = new StringBuilder ();
425                 
426                 while ((line = f.ReadLine ()) != null){
427                         int t = line.Length;
428                         
429                         for (int i = 0; i < t; i++){
430                                 char c = line [i];
431                                 
432                                 if (c == '"' || c == '\''){
433                                         char end = c;
434                                         
435                                         for (i++; i < t; i++){
436                                                 c = line [i];
437                                                 
438                                                 if (c == end)
439                                                         break;
440                                                 sb.Append (c);
441                                         }
442                                 } else if (c == ' '){
443                                         if (sb.Length > 0){
444                                                 args.Add (sb.ToString ());
445                                                 sb.Length = 0;
446                                         }
447                                 } else
448                                         sb.Append (c);
449                         }
450                         if (sb.Length > 0){
451                                 args.Add (sb.ToString ());
452                                 sb.Length = 0;
453                         }
454                 }
455                 
456                 string [] ret_value = new string [args.Count];
457                 args.CopyTo (ret_value, 0);
458                 
459                 return ret_value;
460         }
461
462         static string Load (string f)
463         {
464                 if (File.Exists (f)){
465                         using (var sr = new StreamReader (f)){
466                                 return sr.ReadToEnd ();
467                         }
468                 } else
469                         return "";
470         }
471         
472         public void Generate (XElement xproject)
473         {
474                 string library = xproject.Attribute ("library").Value;
475                 string boot, mcs, flags, output_name, built_sources, library_output, response;
476
477                 boot  = xproject.Element ("boot").Value;
478                 mcs   = xproject.Element ("mcs").Value;
479                 flags = xproject.Element ("flags").Value;
480                 output_name =xproject.Element ("output").Value;
481                 built_sources = xproject.Element ("built_sources").Value;
482                 library_output = xproject.Element ("library_output").Value;
483                 response = xproject.Element ("response").Value;
484
485                 //
486                 // Prebuild code, might be in inputs, check:
487                 //  inputs/LIBRARY-PROFILE.pre
488                 //  inputs/LIBRARY.pre
489                 //
490                 string prebuild = Load (library + ".pre");
491
492                 int q = library.IndexOf ("-");
493                 if (q != -1)
494                         prebuild = prebuild + Load (library.Substring (0, q) + ".pre");
495                         
496                 var all_args = new Queue<string []> ();
497                 all_args.Enqueue (flags.Split ());
498                 while (all_args.Count > 0){
499                         string [] f = all_args.Dequeue ();
500                         
501                         for (int i = 0; i < f.Length; i++){
502                                 if (f [i][0] == '-')
503                                         f [i] = "/" + f [i].Substring (1);
504                                 
505                                 if (f [i][0] == '@') {
506                                         string [] extra_args;
507                                         string response_file = f [i].Substring (1);
508                                         
509                                         extra_args = LoadArgs (base_dir + "\\" + response_file);
510                                         if (extra_args == null) {
511                                                 Console.WriteLine ("Unable to open response file: " + response_file);
512                                                 Environment.Exit (1);
513                                         }
514
515                                         all_args.Enqueue (extra_args);
516                                         continue;
517                                 }
518                                 
519                                 if (CSCParseOption (f [i], ref f))
520                                         continue;
521                                 Console.WriteLine ("Failure with {0}", f [i]);
522                                 Environment.Exit (1);
523                         }
524                 }
525                 
526                 string [] source_files;
527                 using (var reader = new StreamReader (base_dir + "\\" + response)){
528                         source_files  = reader.ReadToEnd ().Split ();
529                 }
530                 StringBuilder sources = new StringBuilder ();
531                 foreach (string s in source_files){
532                         if (s.Length == 0)
533                                 continue;
534                         sources.Append (String.Format ("   <Compile Include=\"{0}\" />\n", s.Replace ("/", "\\")));
535                 }
536                 foreach (string s in built_sources.Split ()){
537                         if (s.Length == 0)
538                                 continue;
539                         
540                         sources.Append (String.Format ("   <Compile Include=\"{0}\" />\n", s.Replace ("/", "\\")));
541                 }
542                 
543                 //
544                 // Compute the csc command that we need to use
545                 //
546                 // The mcs string is formatted like this:
547                 // MONO_PATH=./../../class/lib/basic: /cvs/mono/runtime/mono-wrapper ./../../class/lib/basic/mcs.exe
548                 //
549                 // The first block is a set of MONO_PATHs, the last part is the compiler
550                 //
551                 if (mcs.StartsWith ("MONO_PATH="))
552                         mcs = mcs.Substring (10);
553                 
554                 var compiler = mcs.Substring (mcs.LastIndexOf (' ') + 1);
555                 if (compiler.EndsWith ("class/lib/basic/mcs.exe"))
556                         compiler = "basic";
557                 else if (compiler.EndsWith ("class/lib/net_1_1_bootstrap/mcs.exe"))
558                         compiler = "net_1_1_bootstrap";
559                 else if (compiler.EndsWith ("class/lib/net_1_1/mcs.exe"))
560                         compiler = "net_1_1";
561                 else if (compiler.EndsWith ("class/lib/net_2_0_bootstrap/gmcs.exe"))
562                         compiler = "net_2_0_bootstrap";
563                 else if (compiler.EndsWith ("mcs/gmcs.exe"))
564                         compiler = "gmcs";
565                 else if (compiler.EndsWith ("class/lib/net_2_1_bootstrap/smcs.exe"))
566                         compiler = "net_2_1_bootstrap";
567                 else if (compiler.EndsWith ("class/lib/net_2_1_raw/smcs.exe"))
568                         compiler = "net_2_1_raw";
569                 else {
570                         Console.WriteLine ("Can not determine compiler from {0}", compiler);
571                         Environment.Exit (1);
572                 }
573
574                 var mono_paths = mcs.Substring (0, mcs.IndexOf (' ')).Split (new char [] {':'});
575                 for (int i = 0; i < mono_paths.Length; i++){
576                         int p = mono_paths [i].LastIndexOf ('/');
577                         if (p != -1)
578                                 mono_paths [i] = mono_paths [i].Substring (p + 1);
579                 }
580                 
581                 var encoded_mono_paths = string.Join ("-", mono_paths).Replace ("--", "-");
582                 var encoded_mp_compiler = (encoded_mono_paths + "-" + compiler).Replace ("--", "-");
583                 
584                 string csc_tool_path = mcs_topdir + "..\\mono\\msvc\\scripts\\" + encoded_mp_compiler;
585                 if (!Directory.Exists (encoded_mp_compiler)){
586                         Console.WriteLine ("Created {0}", encoded_mp_compiler);
587                         Directory.CreateDirectory (encoded_mp_compiler);
588                 }
589                 if (!File.Exists (Path.Combine (encoded_mp_compiler, "csc.exe"))){
590                         File.Copy ("monowrap.exe", Path.Combine (encoded_mp_compiler, "csc.exe"));
591                         File.Copy ("monowrap.pdb", Path.Combine (encoded_mp_compiler, "csc.pdb"));
592                 }
593                 
594                 var refs = new StringBuilder ();
595                 //
596                 // mcs is different that csc in this regard, somehow with -noconfig we still import System and System.XML
597                 //
598                 if (dir == "mcs" && !load_default_config){
599                         references.Add ("System.dll");
600                         references.Add ("System.Xml.dll");
601                 }
602                 
603                 if (references.Count > 0 || reference_aliases.Count > 0){
604                         refs.Append ("<ItemGroup>\n");
605                         string last = mono_paths [0].Substring (mono_paths [0].LastIndexOf ('/') + 1);
606                         
607                         string hint_path = class_dir + "\\lib\\" + last;
608
609                         foreach (string r in references){
610                                 refs.Append ("    <Reference Include=\"" + r + "\">\n");
611                                 refs.Append ("      <SpecificVersion>False</SpecificVersion>\n");
612                                 refs.Append ("      <HintPath>" + hint_path + "\\" + r + "</HintPath>\n");
613                                 refs.Append ("    </Reference>\n");
614                         }
615
616                         foreach (string r in reference_aliases){
617                                 int index = r.IndexOf ('=');
618                                 string alias = r.Substring (0, index);
619                                 string assembly = r.Substring (index + 1);
620
621                                 refs.Append ("    <Reference Include=\"" + assembly + "\">\n");
622                                 refs.Append ("      <SpecificVersion>False</SpecificVersion>\n");
623                                 refs.Append ("      <HintPath>" + hint_path + "\\" + r + "</HintPath>\n");
624                                 refs.Append ("      <Aliases>" + alias + "</Aliases>\n");
625                                 refs.Append ("    </Reference>\n");
626                         }
627                         
628                         refs.Append ("  </ItemGroup>\n");
629                 }
630
631                 try {
632                         Path.GetDirectoryName (library_output);
633                 } catch {
634                         Console.WriteLine ("Error in path: {0} while processing {1}", library_output, library);
635                 }
636                 
637                 //
638                 // Replace the template values
639                 //
640                 string output = template.
641                         Replace ("@DEFINES@", defines.ToString ()).
642                         Replace ("@NOSTDLIB@", StdLib ? "" : "<NoStdLib>true</NoStdLib>").
643                         Replace ("@ALLOWUNSAFE@", Unsafe ? "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>" : "").
644                         Replace ("@ASSEMBLYNAME@", Path.GetFileNameWithoutExtension (output_name)).
645                         Replace ("@OUTPUTDIR@", Path.GetDirectoryName (library_output)).
646                         Replace ("@DEFINECONSTANTS@", defines.ToString ()).
647                         Replace ("@CSCTOOLPATH@", csc_tool_path).
648                         Replace ("@DEBUG@", want_debugging_support ? "true" : "false").
649                         Replace ("@DEBUGTYPE@", want_debugging_support ? "full" : "pdbonly").
650                         Replace ("@REFERENCES@", refs.ToString ()).
651                         Replace ("@PREBUILD@", prebuild).
652                         Replace ("@SOURCES@", sources.ToString ());
653
654
655                 string ofile = "..\\..\\..\\mcs\\" + dir + "\\" + library + ".csproj";
656                 //Console.WriteLine ("Generated {0}", ofile.Replace ("\\", "/"));
657                 using (var o = new StreamWriter (ofile)){
658                         o.WriteLine (output);
659                 }
660         }
661         
662 }
663
664 public class Driver {
665         
666         static void Main (string [] args)
667         {
668                 if (!File.Exists ("genproj.cs") || !File.Exists ("monowrap.cs")){
669                         Console.WriteLine ("This command should be ran from mono/msvc/scripts");
670                         Environment.Exit (1);
671                 }
672
673                 XDocument doc = XDocument.Load ("order.xml");
674                 foreach (XElement project in doc.Root.Elements ()){
675                         string dir = project.Attribute ("dir").Value;
676                         string library = project.Attribute ("library").Value;
677
678                         //
679                         // Do only class libraries for now
680                         //
681                         if (!(dir.StartsWith ("class") || dir.StartsWith ("mcs")))
682                                 continue;
683
684                         //
685                         // Do not do 2.1, it is not working yet
686                         // Do not do basic, as there is no point (requires a system mcs to be installed).
687                         //
688                         if (library.Contains ("net_2_1") || library.Contains ("-basic"))
689                                 continue;
690                         
691                         var gen = new MsbuildGenerator (dir);
692                         try {
693                                 gen.Generate (project);
694                         } catch (Exception e) {
695                                 Console.WriteLine ("Error in {0}\n{1}", dir, e);
696                         }
697                 }
698         }
699
700 }