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