Fix forceBuild not working for projects with unknown platform names (i.e. jay) (...
[mono-project.git] / msvc / scripts / genproj.cs
blobb426763697143303f1379572e2141db8ddfa0aa1
1 //
2 // Consumes the order.xml file that contains a list of all the assemblies to build
3 // and produces a solution and the csproj files for it
4 //
5 // Currently this hardcodes a set of assemblies to build, the net-4.x series, but
6 // it can be extended to handle the command line tools.
7 //
8 // KNOWN ISSUES:
9 // * This fails to find matches for "System" and "System.xml" when processing the
10 // RabbitMQ executable, likely, because we do not process executables yet
12 // * Has not been tested in a while with the command line tools
14 using System;
15 using System.IO;
16 using System.Collections.Generic;
17 using System.Text;
18 using System.Globalization;
19 using System.Xml.Linq;
20 using System.Xml.XPath;
21 using System.Linq;
22 using System.Xml;
24 public enum Target {
25 Library, Exe, Module, WinExe
28 public static class KnownProject {
29 public static readonly KnownProjectInfo
30 Genconsts = new KnownProjectInfo {
31 Name = "genconsts",
32 Path = @"$(SolutionDir)\msvc\scripts\genconsts.csproj",
33 Guid = "{702AE2C0-71DD-4112-9A06-E4FABCA59986}"
35 Stringreplacer = new KnownProjectInfo {
36 Name = "cil-stringreplacer",
37 Path = @"$(SolutionDir)\mcs\tools\cil-stringreplacer\cil-stringreplacer.csproj",
38 Guid = "{53c50ffa-8b39-4c70-8ba8-caa70c41a47b}"
40 Jay = new KnownProjectInfo {
41 Name = "jay",
42 Path = @"$(SolutionDir)\mcs\jay\jay.vcxproj",
43 Guid = "{5d485d32-3b9f-4287-ab24-c8da5b89f537}"
45 Culevel = new KnownProjectInfo {
46 Name = "culevel",
47 Path = @"$(SolutionDir)\mcs\tools\culevel\culevel.csproj",
48 Guid = "{E8E246BD-CD0C-4734-A3C2-7F44796EC47B}"
49 };
52 public class KnownProjectInfo {
53 public string Path;
54 public string Guid;
55 public string Name;
58 public class SlnGenerator {
59 public static readonly string NewLine = "\r\n"; //Environment.NewLine; // "\n";
60 public SlnGenerator (string slnVersion)
62 Console.Error.WriteLine("// Requested sln version is {0}", slnVersion);
63 this.header = MakeHeader ("12.00", "15", "15.0.0.0");
66 const string project_start = "Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\""; // Note: No need to double up on {} around {2}
67 const string project_end = "EndProject";
69 public static readonly List<string> profiles = new List<string> {
70 "net_4_x",
71 "monodroid",
72 "monotouch",
73 "monotouch_tv",
74 "monotouch_watch",
75 "orbis",
76 "unreal",
77 "wasm",
78 "winaot",
79 "xammac",
82 public static readonly HashSet<string> observedProfiles = new HashSet<string> {
83 "net_4_x"
86 public const string csproj_type_guid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
87 public const string vcxproj_type_guid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
89 public static Dictionary<string, HashSet<string>> profilesByGuid = new Dictionary<string, HashSet<string>> ();
90 public List<MsbuildGenerator.VsCsproj> libraries = new List<MsbuildGenerator.VsCsproj> ();
91 string header;
93 string MakeHeader (string formatVersion, string yearTag, string minimumVersion)
95 return string.Format (
96 "Microsoft Visual Studio Solution File, Format Version {0}" + NewLine +
97 "# Visual Studio {1}" + NewLine +
98 "MinimumVisualStudioVersion = {2}",
99 formatVersion, yearTag,
100 minimumVersion
104 public void Add (MsbuildGenerator.VsCsproj vsproj)
106 try {
107 libraries.Add (vsproj);
108 } catch (Exception ex) {
109 Console.Error.WriteLine ($"// Error while adding library: {ex.Message}");
113 private void WriteProjectReference (StreamWriter sln, string prefixGuid, string library, string relativePath, string projectGuid, string[] dependencyGuids)
115 // HACK
116 library = library.Replace("-net_4_x", "");
117 sln.WriteLine (project_start, prefixGuid, library, relativePath, projectGuid);
119 if (dependencyGuids != null && dependencyGuids.Length > 0) {
120 sln.WriteLine ("\tProjectSection(ProjectDependencies) = postProject");
121 foreach (var guid in dependencyGuids)
122 sln.WriteLine ("\t\t{0} = {0}", guid);
123 sln.WriteLine ("\tEndProjectSection");
126 sln.WriteLine (project_end);
129 private void WriteProjectReference (StreamWriter sln, string slnFullPath, MsbuildGenerator.VsCsproj proj)
131 var unixProjFile = proj.csProjFilename.Replace ("\\", "/");
132 var fullProjPath = Path.GetFullPath (unixProjFile).Replace ("\\", "/");
133 var relativePath = MsbuildGenerator.GetRelativePath (slnFullPath, fullProjPath);
134 string[] dependencyGuids = null;
136 WriteProjectReference(sln, csproj_type_guid, proj.library, relativePath, proj.projectGuid, dependencyGuids);
139 private void WriteProjectConfigurationPlatforms (StreamWriter sln, string guid, string defaultPlatform, bool forceBuild)
141 var fallbackProfileNames = new List<string> ();
142 var didBuildAnyProfile = false;
144 foreach (var profile in profiles) {
145 if (!observedProfiles.Contains (profile) && !forceBuild)
146 continue;
148 var platformToBuild = profile;
149 var isBuildEnabled = true;
151 HashSet<string> projectProfiles;
152 if (
153 !profilesByGuid.TryGetValue (guid, out projectProfiles) ||
154 !projectProfiles.Contains (platformToBuild)
156 fallbackProfileNames.Add (platformToBuild);
157 platformToBuild = defaultPlatform;
158 isBuildEnabled = forceBuild;
161 if (isBuildEnabled)
162 didBuildAnyProfile = true;
164 sln.WriteLine ("\t\t{0}.Debug|{1}.ActiveCfg = Debug|{2}", guid, profile, platformToBuild);
165 if (isBuildEnabled)
166 sln.WriteLine ("\t\t{0}.Debug|{1}.Build.0 = Debug|{2}", guid, profile, platformToBuild);
167 sln.WriteLine ("\t\t{0}.Release|{1}.ActiveCfg = Release|{2}", guid, profile, platformToBuild);
168 if (isBuildEnabled)
169 sln.WriteLine ("\t\t{0}.Release|{1}.Build.0 = Release|{2}", guid, profile, platformToBuild);
172 if (!didBuildAnyProfile)
173 Console.Error.WriteLine($"// Project {guid} not set to build in any profile");
175 if (fallbackProfileNames.Count > 0)
176 Console.Error.WriteLine ($"// Project {guid} does not have profile(s) {string.Join(", ", fallbackProfileNames)} so using {defaultPlatform}");
179 public void Write (string filename)
181 var fullPath = Path.GetDirectoryName (filename) + "/";
183 using (var sln = new StreamWriter (filename)) {
184 sln.WriteLine ();
185 sln.WriteLine (header);
187 // Manually insert jay's vcxproj. We depend on jay.exe to perform build steps later.
188 WriteProjectReference (sln, vcxproj_type_guid, "jay", "mcs/jay/jay.vcxproj", KnownProject.Jay.Guid, null);
190 // Manually insert genconsts. This is used to generate Consts.cs.
191 WriteProjectReference (sln, csproj_type_guid, "genconsts", "msvc/scripts/genconsts.csproj", KnownProject.Genconsts.Guid, null);
193 // Manually insert cil-stringreplacer. We can't trivially do this through the order.xml flow and it has a custom csproj.
194 WriteProjectReference (sln, csproj_type_guid, "cil-stringreplacer", "mcs/tools/cil-stringreplacer/cil-stringreplacer.csproj", KnownProject.Stringreplacer.Guid, null);
196 foreach (var proj in libraries) {
197 WriteProjectReference (sln, fullPath, proj);
200 sln.WriteLine ("Global");
202 sln.WriteLine ("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
203 foreach (var profile in profiles) {
204 if (!observedProfiles.Contains (profile))
205 continue;
207 sln.WriteLine ("\t\tDebug|{0} = Debug|{0}", profile, profile);
208 sln.WriteLine ("\t\tRelease|{0} = Release|{0}", profile, profile);
210 sln.WriteLine ("\tEndGlobalSection");
212 sln.WriteLine ("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
214 // Manually insert configurations for the special projects that always build
215 WriteProjectConfigurationPlatforms (sln, KnownProject.Jay.Guid, "Win32", true);
216 WriteProjectConfigurationPlatforms (sln, KnownProject.Genconsts.Guid, "x86", true);
217 WriteProjectConfigurationPlatforms (sln, KnownProject.Stringreplacer.Guid, "AnyCPU", true);
219 foreach (var proj in libraries) {
220 WriteProjectConfigurationPlatforms (sln, proj.projectGuid, "net_4_x", false);
223 sln.WriteLine ("\tEndGlobalSection");
225 sln.WriteLine ("\tGlobalSection(SolutionProperties) = preSolution");
226 sln.WriteLine ("\t\tHideSolutionNode = FALSE");
227 sln.WriteLine ("\tEndGlobalSection");
229 sln.WriteLine ("EndGlobal");
233 internal bool ContainsProjectIdentifier (string projId)
235 return libraries.FindIndex (x => (x.library == projId)) >= 0;
238 public int Count { get { return libraries.Count; } }
241 public class MsbuildGenerator {
242 static readonly string NewLine = SlnGenerator.NewLine;
243 static XmlNamespaceManager xmlns;
245 public const string profile_2_0 = "_2_0";
246 public const string profile_3_5 = "_3_5";
247 public const string profile_4_0 = "_4_0";
248 public const string profile_4_x = "_4_x";
250 public static readonly (string, string)[] fixed_guids = new [] {
251 ("tools/culevel/culevel.csproj", KnownProject.Culevel.Guid)
254 static void Usage ()
256 Console.Error.WriteLine ("// Invalid argument");
259 static string template;
260 static MsbuildGenerator ()
262 using (var input = new StreamReader ("csproj.tmpl")) {
263 template = input.ReadToEnd ();
266 xmlns = new XmlNamespaceManager (new NameTable ());
267 xmlns.AddNamespace ("x", "http://schemas.microsoft.com/developer/msbuild/2003");
270 // The directory as specified in order.xml
271 public string dir;
272 string library;
273 string projectGuid;
274 string fx_version;
276 XElement xproject;
277 public string CsprojFilename;
280 // Our base directory, this is relative to our exectution point mono/msvc/scripts
281 string base_dir;
282 string mcs_topdir;
284 static readonly Dictionary<string, string> GuidForCsprojCache = new Dictionary<string, string> ();
286 public string LibraryOutput, AbsoluteLibraryOutput;
288 public MsbuildGenerator (XElement xproject)
290 this.xproject = xproject;
291 dir = xproject.Attribute ("dir").Value;
292 library = xproject.Attribute ("library").Value;
293 // HACK:
294 var profileIndex = library.LastIndexOf("-");
295 var libraryWithoutProfile = library.Substring(0, profileIndex);
296 CsprojFilename = "..\\..\\mcs\\" + dir + "\\" + libraryWithoutProfile + ".csproj";
297 LibraryOutput = xproject.Element ("library_output").Value;
299 projectGuid = LookupOrGenerateGuid ();
300 fx_version = xproject.Element ("fx_version").Value;
301 Csproj = new VsCsproj () {
302 csProjFilename = this.CsprojFilename,
303 projectGuid = this.projectGuid,
304 library_output = this.LibraryOutput,
305 fx_version = double.Parse (fx_version),
306 library = this.library,
307 MsbuildGenerator = this
310 if (dir == "mcs") {
311 mcs_topdir = "../";
312 class_dir = "../class/";
313 base_dir = "../../mcs/mcs";
314 } else {
315 mcs_topdir = "../";
317 foreach (char c in dir) {
318 if (c == '/')
319 mcs_topdir = "..//" + mcs_topdir;
321 class_dir = mcs_topdir.Substring (3);
323 base_dir = Path.Combine ("..", "..", "mcs", dir);
325 AbsoluteLibraryOutput = Path.GetFullPath (Path.Combine (base_dir, LibraryOutput));
328 string LookupOrGenerateGuid ()
330 var projectFile = NativeName (CsprojFilename);
331 string guidKey = Path.GetFullPath (projectFile);
333 foreach (var fg in fixed_guids) {
334 if (guidKey.EndsWith (fg.Item1)) {
335 Console.WriteLine($"Using fixed guid {fg.Item2} for {fg.Item1}");
336 return fg.Item2;
340 string result;
341 GuidForCsprojCache.TryGetValue (projectFile, out result);
343 if (String.IsNullOrEmpty(result) && File.Exists (projectFile)){
344 try {
345 var doc = XDocument.Load (projectFile);
346 result = doc.XPathSelectElement ("x:Project/x:PropertyGroup/x:ProjectGuid", xmlns).Value;
347 } catch (Exception exc) {
348 Console.Error.WriteLine($"// Failed to parse guid from {projectFile}: {exc.Message}");
352 if (String.IsNullOrEmpty(result))
353 result = "{" + Guid.NewGuid ().ToString ().ToUpper () + "}";
355 GuidForCsprojCache[projectFile] = result;
356 return result;
359 // Currently used
360 bool Unsafe = false;
361 StringBuilder defines = new StringBuilder ();
362 bool Optimize = true;
363 bool want_debugging_support = false;
364 string main = null;
365 SortedDictionary<string, string> embedded_resources = new SortedDictionary<string, string> ();
366 List<string> warning_as_error = new List<string> ();
367 List<int> ignore_warning = new List<int> ();
368 bool load_default_config = true;
369 bool StdLib = true;
370 List<string> references = new List<string> ();
371 List<string> libs = new List<string> ();
372 List<string> reference_aliases = new List<string> ();
373 bool showWarnings = true;
375 // Currently unused
376 #pragma warning disable 0219, 0414
377 int WarningLevel = 4;
379 bool Checked = false;
380 bool WarningsAreErrors;
381 bool VerifyClsCompliance = true;
382 string win32IconFile;
383 string StrongNameKeyFile;
384 bool copyLocal = true;
385 Target Target = Target.Library;
386 string TargetExt = ".exe";
387 string OutputFile;
388 string StrongNameKeyContainer;
389 bool StrongNameDelaySign = false;
390 string LangVersion = "default";
391 string CodePage;
393 // Class directory, relative to
394 string class_dir;
395 #pragma warning restore 0219,414
397 readonly char [] argument_value_separator = new char [] { ';', ',' };
400 // This parses the -arg and /arg options to the compiler, even if the strings
401 // in the following text use "/arg" on the strings.
403 bool CSCParseOption (string option, ref string [] args)
405 int idx = option.IndexOf (':');
406 string arg, value;
408 if (idx == -1) {
409 arg = option;
410 value = "";
411 } else {
412 arg = option.Substring (0, idx);
414 value = option.Substring (idx + 1);
417 switch (arg.ToLower (CultureInfo.InvariantCulture)) {
418 case "/nologo":
419 return true;
421 case "/t":
422 case "/target":
423 switch (value) {
424 case "exe":
425 Target = Target.Exe;
426 break;
428 case "winexe":
429 Target = Target.WinExe;
430 break;
432 case "library":
433 Target = Target.Library;
434 TargetExt = ".dll";
435 break;
437 case "module":
438 Target = Target.Module;
439 TargetExt = ".netmodule";
440 break;
442 default:
443 return false;
445 return true;
447 case "/out":
448 if (value.Length == 0) {
449 Usage ();
450 Environment.Exit (1);
452 OutputFile = value;
453 return true;
455 case "/o":
456 case "/o+":
457 case "/optimize":
458 case "/optimize+":
459 Optimize = true;
460 return true;
462 case "/o-":
463 case "/optimize-":
464 Optimize = false;
465 return true;
467 case "/incremental":
468 case "/incremental+":
469 case "/incremental-":
470 // nothing.
471 return true;
473 case "/d":
474 case "/define": {
475 if (value.Length == 0) {
476 Usage ();
477 Environment.Exit (1);
480 foreach (string d in value.Split (argument_value_separator)) {
481 if (defines.Length != 0)
482 defines.Append (";");
483 defines.Append (d);
486 return true;
489 case "/bugreport":
491 // We should collect data, runtime, etc and store in the file specified
493 return true;
494 case "/linkres":
495 case "/linkresource":
496 case "/res":
497 case "/resource":
498 bool embeded = arg [1] == 'r' || arg [1] == 'R';
499 string [] s = value.Split (argument_value_separator);
500 switch (s.Length) {
501 case 1:
502 if (s [0].Length == 0)
503 goto default;
504 embedded_resources [s [0]] = Path.GetFileName (s [0]);
505 break;
506 case 2:
507 embedded_resources [s [0]] = s [1];
508 break;
509 case 3:
510 Console.Error.WriteLine ("// Does not support this method yet: {0}", arg);
511 Environment.Exit (1);
512 break;
513 default:
514 Console.Error.WriteLine ("// Wrong number of arguments for option `{0}'", option);
515 Environment.Exit (1);
516 break;
519 return true;
521 case "/recurse":
522 Console.Error.WriteLine ("// /recurse not supported");
523 Environment.Exit (1);
524 return true;
526 case "/r":
527 case "/reference": {
528 if (value.Length == 0) {
529 Console.Error.WriteLine ("// /reference requires an argument");
530 Environment.Exit (1);
533 string [] refs = value.Split (argument_value_separator);
534 foreach (string r in refs) {
535 string val = r;
536 int index = val.IndexOf ('=');
537 if (index > -1) {
538 reference_aliases.Add (r);
539 continue;
542 if (val.Length != 0)
543 references.Add (val);
545 return true;
547 case "/main":
548 main = value;
549 return true;
551 case "/m":
552 case "/addmodule":
553 case "/win32res":
554 case "/doc":
555 if (showWarnings)
556 Console.Error.WriteLine ("// {0} = not supported", arg);
557 return true;
559 case "/lib": {
560 libs.Add (value);
561 return true;
563 case "/win32icon": {
564 win32IconFile = value;
565 return true;
567 case "/debug-":
568 want_debugging_support = false;
569 return true;
571 case "/debug":
572 case "/debug+":
573 want_debugging_support = true;
574 return true;
576 case "/checked":
577 case "/checked+":
578 Checked = true;
579 return true;
581 case "/checked-":
582 Checked = false;
583 return true;
585 case "/clscheck":
586 case "/clscheck+":
587 return true;
589 case "/clscheck-":
590 VerifyClsCompliance = false;
591 return true;
593 case "/unsafe":
594 case "/unsafe+":
595 Unsafe = true;
596 return true;
598 case "/unsafe-":
599 Unsafe = false;
600 return true;
602 case "/warnaserror":
603 case "/warnaserror+":
604 if (value.Length == 0) {
605 WarningsAreErrors = true;
606 } else {
607 foreach (string wid in value.Split (argument_value_separator))
608 warning_as_error.Add (wid);
610 return true;
612 case "/-runtime":
613 // Console.WriteLine ("Warning ignoring /runtime:v4");
614 return true;
616 case "/warnaserror-":
617 if (value.Length == 0) {
618 WarningsAreErrors = false;
619 } else {
620 foreach (string wid in value.Split (argument_value_separator))
621 warning_as_error.Remove (wid);
623 return true;
625 case "/warn":
626 WarningLevel = Int32.Parse (value);
627 return true;
629 case "/nowarn": {
630 string [] warns;
632 if (value.Length == 0) {
633 Console.Error.WriteLine ("// /nowarn requires an argument");
634 Environment.Exit (1);
637 warns = value.Split (argument_value_separator);
638 foreach (string wc in warns) {
639 try {
640 if (wc.Trim ().Length == 0)
641 continue;
643 int warn = Int32.Parse (wc);
644 if (warn < 1) {
645 throw new ArgumentOutOfRangeException ("warn");
647 ignore_warning.Add (warn);
648 } catch {
649 Console.Error.WriteLine ($"// `{wc}' is not a valid warning number");
650 Environment.Exit (1);
653 return true;
656 case "/noconfig":
657 load_default_config = false;
658 return true;
660 case "/nostdlib":
661 case "/nostdlib+":
662 StdLib = false;
663 return true;
665 case "/nostdlib-":
666 StdLib = true;
667 return true;
669 case "/fullpaths":
670 return true;
672 case "/keyfile":
673 if (value == String.Empty) {
674 Console.Error.WriteLine ($"// {arg} requires an argument");
675 Environment.Exit (1);
677 StrongNameKeyFile = value;
678 return true;
679 case "/keycontainer":
680 if (value == String.Empty) {
681 Console.Error.WriteLine ($"// {arg} requires an argument");
682 Environment.Exit (1);
684 StrongNameKeyContainer = value;
685 return true;
686 case "/delaysign+":
687 case "/delaysign":
688 StrongNameDelaySign = true;
689 return true;
690 case "/delaysign-":
691 StrongNameDelaySign = false;
692 return true;
694 case "/langversion":
695 LangVersion = value;
696 return true;
698 case "/codepage":
699 CodePage = value;
700 return true;
702 case "/publicsign":
703 return true;
705 case "/deterministic":
706 return true;
708 case "/runtimemetadataversion":
709 return true;
711 case "/-getresourcestrings":
712 return true;
714 case "/features":
715 return true;
718 Console.Error.WriteLine ($"// Failing with : {arg}");
719 return false;
722 static string [] LoadArgs (string file)
724 StreamReader f;
725 var args = new List<string> ();
726 string line;
727 try {
728 f = new StreamReader (file);
729 } catch {
730 return null;
733 StringBuilder sb = new StringBuilder ();
735 while ((line = f.ReadLine ()) != null) {
736 int t = line.Length;
738 for (int i = 0; i < t; i++) {
739 char c = line [i];
741 if (c == '"' || c == '\'') {
742 char end = c;
744 for (i++; i < t; i++) {
745 c = line [i];
747 if (c == end)
748 break;
749 sb.Append (c);
751 } else if (c == ' ') {
752 if (sb.Length > 0) {
753 args.Add (sb.ToString ());
754 sb.Length = 0;
756 } else
757 sb.Append (c);
759 if (sb.Length > 0) {
760 args.Add (sb.ToString ());
761 sb.Length = 0;
765 string [] ret_value = new string [args.Count];
766 args.CopyTo (ret_value, 0);
768 return ret_value;
771 static string Load (string f)
773 var native = NativeName (f);
775 if (File.Exists (native)) {
776 using (var sr = new StreamReader (native)) {
777 return sr.ReadToEnd ();
779 } else
780 return "";
783 public static string NativeName (string path)
785 if (System.IO.Path.DirectorySeparatorChar == '/')
786 return path.Replace ("\\", "/");
787 else
788 return path.Replace ("/", "\\");
791 public class VsCsproj {
792 public string projectGuid;
793 public string output;
794 public string library_output;
795 public string csProjFilename;
796 public double fx_version;
797 public List<VsCsproj> projReferences = new List<VsCsproj> ();
798 public string library;
799 public MsbuildGenerator MsbuildGenerator;
800 public string preBuildEvent, postBuildEvent;
803 public VsCsproj Csproj;
805 void AppendResource (StringBuilder resources, string source, string logical)
807 source = NativeName (source);
808 resources.AppendFormat (" <EmbeddedResource Include=\"{0}\">" + NewLine, source);
809 resources.AppendFormat (" <LogicalName>{0}</LogicalName>" + NewLine, logical);
810 resources.AppendFormat (" </EmbeddedResource>" + NewLine);
813 internal string GetProjectFilename ()
815 return NativeName (Csproj.csProjFilename);
818 public void EraseExisting ()
820 var generatedProjFile = GetProjectFilename();
821 if (File.Exists(generatedProjFile))
822 File.Delete(generatedProjFile);
825 SourcesParser _SourcesParser = null;
827 private SourcesParser GetSourcesParser () {
828 if (_SourcesParser != null)
829 return _SourcesParser;
831 var platformsFolder = Path.GetFullPath ("../../mcs/build/platforms");
832 var profilesFolder = Path.GetFullPath ("../../mcs/build/profiles");
834 SourcesParser.TraceLevel = 0;
835 return _SourcesParser = new SourcesParser (platformsFolder, profilesFolder);
838 private ParseResult ReadSources (string sourcesFileName) {
839 var libraryDirectory = Path.GetDirectoryName (GetProjectFilename ());
841 // HACK: Sometimes the sources path contains a relative path like ../../x
842 if (sourcesFileName.Contains ("/") || sourcesFileName.Contains ("\\")) {
843 libraryDirectory = Path.Combine (libraryDirectory, Path.GetDirectoryName (sourcesFileName));
844 sourcesFileName = Path.GetFileName (sourcesFileName);
847 libraryDirectory = Path.GetFullPath (libraryDirectory);
849 // HACK: executable.make generates sources paths containing .sources already
850 var libraryName = sourcesFileName.Replace (".sources", "");
852 var parser = GetSourcesParser ();
853 var result = parser.Parse (libraryDirectory, libraryName);
855 if (result.SourcesFiles.Count == 0)
856 Console.Error.WriteLine ($"// No sources files found for '{sourcesFileName}', looked in '{libraryDirectory}' for {libraryName}");
858 return result;
861 private string FixupSourceName (string s) {
862 string src = s.Replace ("/", "\\");
863 if (src.StartsWith (@"Test\..\"))
864 src = src.Substring (8, src.Length - 8);
866 return src;
869 private bool IsValidProfile (string output_name, string profile) {
870 return SlnGenerator.profiles.Contains (profile);
873 private void GenerateSourceItems (XmlWriter writer, IEnumerable<string> fileNames, HashSet<string> commonFiles) {
874 foreach (var file in fileNames.OrderBy (f => f, StringComparer.Ordinal)) {
875 // FIXME: Is this needed?
876 if ((commonFiles != null) && commonFiles.Contains (file))
877 continue;
879 writer.WriteStartElement ("Compile");
880 writer.WriteAttributeString ("Include", file);
881 writer.WriteEndElement ();
885 private void GenerateProjectDependency (XmlWriter xmlWriter, KnownProjectInfo project) {
886 xmlWriter.WriteStartElement ("ProjectReference");
887 xmlWriter.WriteAttributeString ("Include", project.Path);
888 xmlWriter.WriteElementString ("Name", project.Name);
889 xmlWriter.WriteElementString ("Project", project.Guid);
890 xmlWriter.WriteElementString ("ReferenceOutputAssembly", "false");
891 xmlWriter.WriteElementString ("CopyToOutputDirectory", "Never");
892 xmlWriter.WriteElementString ("Private", "false");
893 xmlWriter.WriteEndElement();
896 private void GenerateProjectDependencies (
897 XmlWriter writer,
898 HashSet<string> commonFiles,
899 string prebuild, string postbuild
901 var prebuild_postbuild = (prebuild + Environment.NewLine + postbuild)
902 .Replace ("\\", "/");
904 if (commonFiles.Any (f => f.EndsWith("build\\common\\Consts.cs")))
905 GenerateProjectDependency (writer, KnownProject.Genconsts);
907 if (prebuild_postbuild.Contains ("jay.exe"))
908 GenerateProjectDependency (writer, KnownProject.Jay);
910 if (prebuild_postbuild.Contains ("culevel.exe"))
911 GenerateProjectDependency (writer, KnownProject.Culevel);
913 if (prebuild_postbuild.Contains ("cil-stringreplacer.exe"))
914 GenerateProjectDependency (writer, KnownProject.Stringreplacer);
917 private StringBuilder GenerateSourceItemGroups (
918 string output_name,
919 string profile,
920 string sources_file_name,
921 string groupConditional,
922 string prebuild, string postbuild
924 var result = new StringBuilder ();
925 var xmlWriterSettings = new XmlWriterSettings () {
926 ConformanceLevel = ConformanceLevel.Fragment,
927 WriteEndDocumentOnClose = true,
928 CheckCharacters = true,
929 Encoding = Encoding.UTF8,
930 Indent = true,
931 IndentChars = " ",
932 NewLineChars = NewLine,
933 NewLineHandling = NewLineHandling.Replace,
934 NewLineOnAttributes = false,
935 OmitXmlDeclaration = true
937 var xmlWriter = XmlWriter.Create (result, xmlWriterSettings);
938 var parseResult = ReadSources (sources_file_name);
940 var hostPlatformNames = GetSourcesParser ().AllHostPlatformNames;
942 var nullExclusions = new SourcesFile ("null", true);
944 if (parseResult.TargetDictionary.Count == 0)
945 return result;
947 var targetFileSets = (from target in parseResult.Targets
948 where (target.Key.profile == null) || IsValidProfile (output_name, target.Key.profile)
949 let matches = parseResult.GetMatches (target)
950 .Select (m => FixupSourceName (m.RelativePath))
951 .OrderBy (s => s, StringComparer.Ordinal)
952 .Distinct ()
953 let fileNames = new HashSet<string> (matches)
954 orderby target.Key.profile descending, target.Key.hostPlatform descending
955 select (key: target.Key, fileNames: fileNames)).ToList ();
957 var commonFiles = targetFileSets.Aggregate (
958 (HashSet<string>)null,
959 (files, targetSet) => {
960 if (files == null)
961 files = new HashSet<string> (targetSet.fileNames, StringComparer.Ordinal);
962 else
963 files.IntersectWith (targetSet.fileNames);
964 return files;
968 xmlWriter.WriteComment ("Common files");
969 xmlWriter.WriteStartElement ("ItemGroup");
970 GenerateSourceItems (xmlWriter, commonFiles, null);
972 GenerateProjectDependencies (xmlWriter, commonFiles, prebuild, postbuild);
974 xmlWriter.WriteEndElement ();
975 xmlWriter.WriteComment ("End of common files");
977 // FIXME: Is this right if the profile/platform pair are not null,null? It probably is
978 if (targetFileSets.Count != 1) {
979 var profileGroups = (from tfs in targetFileSets
980 group tfs by tfs.key.profile into sets
981 select sets).ToList ();
983 xmlWriter.WriteComment ("Per-profile files");
984 if (profileGroups.Count > 1)
985 xmlWriter.WriteStartElement ("Choose");
987 foreach (var profileGroup in profileGroups) {
988 if (profileGroups.Count == 1) {
989 } else if (profileGroup.Key == null) {
990 xmlWriter.WriteStartElement ("Otherwise");
991 } else {
992 xmlWriter.WriteStartElement ("When");
993 xmlWriter.WriteAttributeString ("Condition", $"'$(Platform)' == '{profileGroup.Key}'");
996 var hostPlatforms = profileGroup.ToList ();
997 if (hostPlatforms.Count == 1) {
998 xmlWriter.WriteStartElement ("ItemGroup");
999 GenerateSourceItems (xmlWriter, hostPlatforms[0].fileNames, commonFiles);
1000 xmlWriter.WriteEndElement ();
1001 } else {
1002 xmlWriter.WriteComment ("Per-host-platform files");
1003 xmlWriter.WriteStartElement ("Choose");
1005 foreach (var set in hostPlatforms) {
1006 if (set.key.hostPlatform == null) {
1007 xmlWriter.WriteStartElement ("Otherwise");
1008 } else {
1009 xmlWriter.WriteStartElement ("When");
1010 xmlWriter.WriteAttributeString ("Condition", $"'$(HostPlatform)' == '{set.key.hostPlatform}'");
1013 xmlWriter.WriteStartElement ("ItemGroup");
1014 GenerateSourceItems (xmlWriter, set.fileNames, commonFiles);
1015 xmlWriter.WriteEndElement ();
1017 xmlWriter.WriteEndElement();
1020 xmlWriter.WriteEndElement ();
1021 xmlWriter.WriteComment ("End of per-host-platform files");
1024 if (profileGroups.Count > 1)
1025 xmlWriter.WriteEndElement ();
1028 if (profileGroups.Count > 1)
1029 xmlWriter.WriteEndElement ();
1030 xmlWriter.WriteComment ("End of per-profile files");
1033 xmlWriter.Close ();
1035 return result;
1038 public VsCsproj Generate (string library_output, Dictionary<string,MsbuildGenerator> projects, out string profile, bool showWarnings = false)
1040 var generatedProjFile = GetProjectFilename();
1041 var updatingExistingProject = File.Exists(generatedProjFile);
1043 if (!updatingExistingProject)
1044 Console.WriteLine ($"Generating {generatedProjFile}");
1046 string boot, flags, output_name, built_sources, response, reskey, sources_file_name;
1048 boot = xproject.Element ("boot").Value;
1049 flags = xproject.Element ("flags").Value;
1050 sources_file_name = xproject.Element ("sources").Value;
1051 output_name = xproject.Element ("output").Value;
1052 if (output_name.EndsWith (".exe"))
1053 Target = Target.Exe;
1054 built_sources = xproject.Element ("built_sources").Value.Trim ();
1055 response = xproject.Element ("response").Value;
1056 reskey = xproject.Element ("resources").Value;
1058 profile = xproject.Element ("profile").Value;
1059 if (string.IsNullOrEmpty (response)) {
1060 // Address the issue where entries are missing the fx_version
1061 // Should be fixed in the Makefile or elsewhere; this is a workaround
1062 //<fx_version>basic</fx_version>
1063 //<profile>./../build/deps/mcs.exe.sources.response</profile>
1064 //<response></response>
1065 response = profile;
1066 profile = fx_version;
1067 if (response.Contains ("build") || response.Contains ("basic") || response.Contains (profile_2_0)) {
1068 fx_version = "2.0";
1069 if (response.Contains (profile_2_0)) profile = "net_2_0";
1070 } if (response.Contains ("build") || response.Contains ("basic") || response.Contains (profile_2_0)) {
1071 fx_version = "2.0";
1072 } else if (response.Contains (profile_3_5)) {
1073 fx_version = "3.5";
1074 profile = "net_3_5";
1075 } else if (response.Contains (profile_4_0)) {
1076 fx_version = "4.0";
1077 profile = "net_4_0";
1078 } else if (response.Contains (profile_4_x)) {
1079 fx_version = "4.6.2";
1080 profile = "net_4_x";
1082 Console.WriteLine ($"Using response fallback for {output_name}: {response}");
1085 // Prebuild code, might be in inputs, check:
1086 // inputs/LIBRARY.pre
1088 string prebuild = GenerateStep (library, ".pre", "PreBuildEvent");
1089 string postbuild = GenerateStep (library, ".post", "PostBuildEvent");
1091 var all_args = new Queue<string []> ();
1092 all_args.Enqueue (flags.Split ());
1093 while (all_args.Count > 0) {
1094 string [] f = all_args.Dequeue ();
1096 for (int i = 0; i < f.Length; i++) {
1097 if (f [i].Length > 0 && f [i][0] == '-')
1098 f [i] = "/" + f [i].Substring (1);
1100 if (f [i] [0] == '@') {
1101 string [] extra_args;
1102 string response_file = f [i].Substring (1);
1104 var resp_file_full = Path.Combine (base_dir, response_file);
1105 extra_args = LoadArgs (resp_file_full);
1106 if (extra_args == null) {
1107 Console.Error.WriteLine ($"// {library_output}: Unable to open response file: {resp_file_full}");
1108 Environment.Exit (1);
1111 all_args.Enqueue (extra_args);
1112 continue;
1115 if (CSCParseOption (f [i], ref f))
1116 continue;
1117 Console.Error.WriteLine ($"// {library_output}: Failure with {f [i]}");
1118 Environment.Exit (1);
1122 var groupConditional = $"Condition=\" '$(Platform)' == '{profile}' \"";
1124 var sources =
1125 updatingExistingProject
1126 ? new StringBuilder ()
1127 : GenerateSourceItemGroups (
1128 output_name, profile,
1129 sources_file_name, groupConditional,
1130 prebuild, postbuild
1133 //if (library == "corlib-build") // otherwise, does not compile on fx_version == 4.0
1135 // references.Add("System.dll");
1136 // references.Add("System.Xml.dll");
1139 //if (library == "System.Core-build") // otherwise, slow compile. May be a transient need.
1141 // this.ignore_warning.Add(1685);
1142 // this.ignore_warning.Add(0436);
1145 var refs = new StringBuilder ();
1147 refs.Append ($" <ItemGroup {groupConditional}>{NewLine}");
1149 if (response.Contains ("_test")) {
1150 refs.Append ($@" <Reference Include=""nunitlite"">{NewLine}");
1151 refs.Append ($@" <HintPath>..\lib\{profile}\nunitlite.dll</HintPath>{NewLine}");
1152 refs.Append ($@" <Private>False</Private>{NewLine}");
1153 refs.Append ($@" </Reference>{NewLine}");
1158 // Generate resource referenced from the command line
1160 var resources = new StringBuilder ();
1161 if (embedded_resources.Count > 0) {
1162 foreach (var dk in embedded_resources) {
1163 var source = dk.Key;
1164 if (source.EndsWith (".resources"))
1165 source = source.Replace (".resources", ".resx");
1167 // try to find a pre-built resource, and use that instead of trying to build it
1168 if (source.EndsWith (".resx")) {
1169 var probe_prebuilt = Path.Combine (base_dir, source.Replace (".resx", ".resources.prebuilt"));
1170 if (File.Exists (probe_prebuilt)) {
1172 source = GetRelativePath (base_dir + "/", probe_prebuilt);
1175 AppendResource (resources, source, dk.Value);
1179 // Generate resources that were part of the explicit <resource> node
1181 if (reskey != null && reskey != ""){
1182 var pairs = reskey.Split (' ', '\n', '\t');
1183 foreach (var pair in pairs){
1184 var p = pair.IndexOf (",");
1185 if (p == -1){
1186 Console.Error.WriteLine ($"// Found a resource without a filename: {pairs} for {Csproj.csProjFilename}");
1187 Environment.Exit (1);
1189 AppendResource (resources, pair.Substring (p+1), pair.Substring (0, p) + ".resources");
1192 if (resources.Length > 0){
1193 resources.Insert (0, $" <ItemGroup {groupConditional}>{NewLine}");
1194 resources.Append (" </ItemGroup>" + NewLine);
1197 if (references.Count > 0 || reference_aliases.Count > 0) {
1198 // -r:mscorlib.dll -r:System.dll
1199 //<ProjectReference Include="..\corlib\corlib-basic.csproj">
1200 // <Project>{155aef28-c81f-405d-9072-9d52780e3e70}</Project>
1201 // <Name>corlib-basic</Name>
1202 //</ProjectReference>
1203 //<ProjectReference Include="..\System\System-basic.csproj">
1204 // <Project>{2094e859-db2f-481f-9630-f89d31d9ed48}</Project>
1205 // <Name>System-basic</Name>
1206 //</ProjectReference>
1207 var refdistinct = references.Distinct ();
1208 foreach (string reference in refdistinct) {
1210 var match = GetMatchingCsproj (library_output, reference, projects);
1211 if (match != null) {
1212 AddProjectReference (refs, Csproj, match, reference, null);
1213 } else {
1214 if (showWarnings){
1215 Console.Error.WriteLine ($"{library}: Could not find a matching project reference for {Path.GetFileName (reference)}");
1216 Console.Error.WriteLine (" --> Adding reference with hintpath instead");
1218 var externalDrawing = (reference == Environment.GetEnvironmentVariable ("EXTERNAL_FACADE_DRAWING_REFERENCE"));
1219 refs.Append (" <Reference Include=\"" + (externalDrawing ? "$(EXTERNAL_FACADE_DRAWING_REFERENCE)" : reference) + "\">" + NewLine);
1220 refs.Append (" <SpecificVersion>False</SpecificVersion>" + NewLine);
1221 refs.Append (" <HintPath>" + (externalDrawing ? "$(EXTERNAL_FACADE_DRAWING_REFERENCE)" : reference) + "</HintPath>" + NewLine);
1222 refs.Append (" <Private>False</Private>" + NewLine);
1223 refs.Append (" </Reference>" + NewLine);
1227 foreach (string r in reference_aliases) {
1228 int index = r.IndexOf ('=');
1229 string alias = r.Substring (0, index);
1230 string assembly = r.Substring (index + 1);
1231 var match = GetMatchingCsproj (library_output, assembly, projects, explicitPath: true);
1232 if (match != null) {
1233 AddProjectReference (refs, Csproj, match, r, alias);
1234 } else {
1235 throw new NotSupportedException (string.Format ("From {0}, could not find a matching project reference for {1}", library, r));
1236 refs.Append (" <Reference Include=\"" + assembly + "\">" + NewLine);
1237 refs.Append (" <SpecificVersion>False</SpecificVersion>" + NewLine);
1238 refs.Append (" <HintPath>" + r + "</HintPath>" + NewLine);
1239 refs.Append (" <Aliases>" + alias + "</Aliases>" + NewLine);
1240 refs.Append (" </Reference>" + NewLine);
1246 refs.Append (" </ItemGroup>");
1248 // Possible inputs:
1249 // ../class/lib/build/tmp/System.Xml.dll [No longer possible, we should be removing this from order.xml]
1250 // /class/lib/basic/System.Core.dll
1251 // <library_output>mcs.exe</library_output>
1252 string build_output_dir, intermediate_output_dir;
1253 if (LibraryOutput.Contains ("/")) {
1254 build_output_dir = Path.GetDirectoryName (LibraryOutput);
1255 intermediate_output_dir = build_output_dir.Substring (0, build_output_dir.IndexOf("/class/lib") + 7) + "obj";
1257 else {
1258 build_output_dir = "bin\\Debug\\" + library;
1259 intermediate_output_dir = "obj\\Debug\\" + library;
1262 if (build_output_dir.Contains ("-linux") || build_output_dir.Contains ("-macos") || build_output_dir.Contains ("-win32") || build_output_dir.Contains ("-unix"))
1263 build_output_dir = build_output_dir
1264 .Replace ("-linux", "-$(HostPlatform)")
1265 .Replace ("-macos", "-$(HostPlatform)")
1266 .Replace ("-win32", "-$(HostPlatform)")
1267 .Replace ("-unix", "-$(HostPlatform)");
1269 bool basic_or_build = (library.Contains ("-basic") || library.Contains ("-build"));
1271 // If an EXE is built with nostdlib, it won't work unless run with mono.exe. This stops our build steps
1272 // from working in visual studio (because we already replace @MONO@ with '' on Windows.)
1274 if (Target != Target.Library)
1275 StdLib = true;
1277 // We have our target framework set to 4.5 in many places because broken scripts check for files with 4.5
1278 // in the path, even though we compile code that uses 4.6 features. So we need to manually fix that here.
1280 if (fx_version == "4.5")
1281 fx_version = "4.6.2";
1283 // The VS2017 signing system fails to sign using this key for some reason, so for now,
1284 // just disable code signing for the nunit assemblies. It's not important.
1285 // I'd rather fix this by updating the makefiles but it seems to be impossible to disable
1286 // code signing in our make system...
1288 if (StrongNameKeyFile?.Contains("nunit.snk") ?? false)
1289 StrongNameKeyFile = null;
1292 // Replace the template values
1295 string strongNameSection = "";
1296 if (StrongNameKeyFile != null){
1297 strongNameSection = String.Format (
1298 " <SignAssembly>true</SignAssembly>" + NewLine +
1299 "{1}" +
1300 " <AssemblyOriginatorKeyFile>{0}</AssemblyOriginatorKeyFile>",
1301 StrongNameKeyFile, StrongNameDelaySign ? " <DelaySign>true</DelaySign>" + NewLine : "");
1304 string assemblyName = Path.GetFileNameWithoutExtension (output_name);
1305 var outputSuffix = Path.GetFileName (build_output_dir);
1307 string textToUpdate = updatingExistingProject
1308 ? File.ReadAllText(generatedProjFile)
1309 : template;
1311 var properties = new StringBuilder ();
1312 properties.Append ($" <PropertyGroup {groupConditional}>{NewLine}");
1313 properties.Append ($" <OutputPath>{build_output_dir}</OutputPath>{NewLine}");
1314 properties.Append ($" <IntermediateOutputPath>{intermediate_output_dir}/$(AssemblyName)-{outputSuffix}</IntermediateOutputPath>{NewLine}");
1315 properties.Append ($" <DefineConstants>{defines.ToString ()}</DefineConstants>{NewLine}");
1316 properties.Append ($" </PropertyGroup>{NewLine}");
1318 var prebuild_postbuild = new StringBuilder ();
1319 if (!String.IsNullOrWhiteSpace(prebuild) || !String.IsNullOrWhiteSpace(postbuild)) {
1320 prebuild_postbuild.Append ($" <PropertyGroup>{NewLine}");
1321 prebuild_postbuild.Append (prebuild);
1322 prebuild_postbuild.Append (postbuild);
1323 prebuild_postbuild.Append ($" </PropertyGroup>{NewLine}");
1326 var builtSources = new StringBuilder ();
1327 if (built_sources.Length > 0) {
1328 builtSources.Append ($" <ItemGroup Condition=\" '$(Platform)' == '{profile}' \">{NewLine}");
1329 foreach (var fileName in built_sources.Split ()) {
1330 var fixedFileName = FixupSourceName (fileName);
1331 builtSources.Append ($" <Compile Include=\"{fixedFileName}\" />{NewLine}");
1333 builtSources.Append ($" </ItemGroup>{NewLine}");
1336 Csproj.output = textToUpdate.
1337 Replace ("@OUTPUTTYPE@", Target == Target.Library ? "Library" : "Exe").
1338 Replace ("@SIGNATURE@", strongNameSection).
1339 Replace ("@PROJECTGUID@", Csproj.projectGuid).
1340 Replace ("@DEFINES@", defines.ToString ()).
1341 Replace ("@DISABLEDWARNINGS@", string.Join (",", (from i in ignore_warning select i.ToString ()).ToArray ())).
1342 Replace ("@LANGVERSION@", LangVersion).
1343 //Replace("@NOSTDLIB@", (basic_or_build || (!StdLib)) ? "<NoStdLib>true</NoStdLib>" : string.Empty).
1344 Replace ("@NOSTDLIB@", "<NoStdLib>" + (!StdLib).ToString () + "</NoStdLib>").
1345 Replace ("@NOCONFIG@", "<NoConfig>" + (!load_default_config).ToString () + "</NoConfig>").
1346 Replace ("@ALLOWUNSAFE@", Unsafe ? "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>" : "").
1347 Replace ("@FX_VERSION@", fx_version).
1348 Replace ("@ASSEMBLYNAME@", assemblyName).
1349 Replace ("@DEBUG@", want_debugging_support ? "true" : "false").
1350 Replace ("@DEBUGTYPE@", want_debugging_support ? "full" : "pdbonly").
1351 Replace ("@PREBUILD_POSTBUILD@", prebuild_postbuild.ToString ()).
1352 Replace ("@STARTUPOBJECT@", main == null ? "" : $"<StartupObject>{main}</StartupObject>").
1353 //Replace ("@ADDITIONALLIBPATHS@", String.Format ("<AdditionalLibPaths>{0}</AdditionalLibPaths>", string.Join (",", libs.ToArray ()))).
1354 Replace ("@ADDITIONALLIBPATHS@", String.Empty).
1355 Replace ("@OPTIMIZE@", Optimize ? "true" : "false").
1356 Replace ("@METADATAVERSION@", assemblyName == "mscorlib" ? "<RuntimeMetadataVersion>Mono</RuntimeMetadataVersion>" : "");
1358 var propertiesPlaceholder = "<!-- @ALL_PROFILE_PROPERTIES@ -->";
1359 var refsPlaceholder = "<!-- @ALL_REFERENCES@ -->";
1360 var resourcesPlaceholder = "<!-- @ALL_RESOURCES@ -->";
1361 var sourcesPlaceholder = "<!-- @ALL_SOURCES@ -->";
1362 var builtSourcesPlaceholder = "<!-- @BUILT_SOURCES@ -->";
1364 Csproj.output = Csproj.output.
1365 Replace (propertiesPlaceholder, properties.ToString () + NewLine + propertiesPlaceholder).
1366 Replace (refsPlaceholder, refs.ToString () + NewLine + refsPlaceholder).
1367 Replace (resourcesPlaceholder, resources.ToString () + NewLine + resourcesPlaceholder).
1368 Replace (sourcesPlaceholder, sources.ToString () + NewLine + sourcesPlaceholder).
1369 Replace (builtSourcesPlaceholder, builtSources.ToString () + NewLine + builtSourcesPlaceholder);
1371 Csproj.preBuildEvent = prebuild;
1372 Csproj.postBuildEvent = postbuild;
1374 //Console.WriteLine ("Generated {0}", ofile.Replace ("\\", "/"));
1375 // Console.WriteLine("Writing {0}", generatedProjFile);
1376 using (var o = new StreamWriter (generatedProjFile)) {
1377 o.WriteLine (Csproj.output);
1380 return Csproj;
1383 string GenerateStep (string library, string suffix, string eventKey)
1385 string target = Load (library + suffix);
1386 string target_windows, target_unix;
1388 int q = library.IndexOf ("-");
1389 if (q != -1)
1390 target = target + Load (library.Substring (0, q) + suffix);
1392 target_unix = target.Replace ("@MONO@", "mono").Replace ("@CAT@", "cat");
1393 target_windows = target.Replace ("@MONO@", "").Replace ("@CAT@", "type");
1395 target_unix = target_unix.Replace ("\\jay\\jay.exe", "\\jay\\jay");
1397 target_unix = target_unix.Replace ("@COPY@", "cp");
1398 target_windows = target_windows.Replace ("@COPY@", "copy");
1400 target_unix = target_unix.Replace ("\r", "");
1401 const string condition_unix = "Condition=\" '$(OS)' != 'Windows_NT' \"";
1402 const string condition_windows = "Condition=\" '$(OS)' == 'Windows_NT' \"";
1404 var result = new StringBuilder ();
1405 if (!String.IsNullOrWhiteSpace (target_unix))
1406 result.Append ($" <{eventKey} {condition_unix}>{target_unix.Trim ()}</{eventKey}>{NewLine}");
1407 if (!String.IsNullOrWhiteSpace (target_windows))
1408 result.Append ($" <{eventKey} {condition_windows}>{target_windows.Trim ()}</{eventKey}>{NewLine}");
1409 return result.ToString ();
1412 void AddProjectReference (StringBuilder refs, VsCsproj result, MsbuildGenerator match, string r, string alias)
1414 refs.AppendFormat (" <ProjectReference Include=\"{0}\"", GetRelativePath (result.csProjFilename, match.CsprojFilename));
1415 if (alias != null) {
1416 refs.Append (">" + NewLine);
1417 refs.Append (" <Aliases>" + alias + "</Aliases>" + NewLine);
1418 refs.Append (" </ProjectReference>" + NewLine);
1420 else {
1421 refs.Append (" />" + NewLine);
1423 if (!result.projReferences.Contains (match.Csproj))
1424 result.projReferences.Add (match.Csproj);
1427 public static string GetRelativePath (string from, string to)
1429 from = from.Replace ("\\", "/");
1430 to = to.Replace ("\\", "/");
1431 var fromUri = new Uri (Path.GetFullPath (from));
1432 var toUri = new Uri (Path.GetFullPath (to));
1434 var ret = fromUri.MakeRelativeUri (toUri).ToString ().Replace ("%5C", "\x5c");
1435 return ret;
1438 MsbuildGenerator GetMatchingCsproj (string library_output, string dllReferenceName, Dictionary<string,MsbuildGenerator> projects, bool explicitPath = false)
1440 // libDir would be "./../../class/lib/net_4_x for example
1441 // project
1442 if (!dllReferenceName.EndsWith (".dll") && !dllReferenceName.EndsWith (".exe"))
1443 dllReferenceName += ".dll";
1445 var probe = Path.GetFullPath (Path.Combine (base_dir, dllReferenceName));
1446 foreach (var project in projects){
1447 if (probe == project.Value.AbsoluteLibraryOutput)
1448 return project.Value;
1451 // not explicit, search for the library in the lib path order specified
1453 foreach (var libDir in libs) {
1454 var abs = Path.GetFullPath (Path.Combine (base_dir, libDir));
1455 foreach (var project in projects){
1456 probe = Path.Combine (abs, dllReferenceName);
1458 if (probe == project.Value.AbsoluteLibraryOutput)
1459 return project.Value;
1463 // Last attempt, try to find the library in all the projects
1464 foreach (var project in projects) {
1465 if (project.Value.AbsoluteLibraryOutput.EndsWith (dllReferenceName))
1466 return project.Value;
1469 var ljoined = String.Join (", ", libs);
1470 Console.Error.WriteLine ($"// {library_output}: did not find referenced {dllReferenceName} with libs={ljoined}");
1472 // FIXME: This is incredibly noisy and generates a billion lines of output
1473 if (false)
1474 foreach (var p in projects) {
1475 Console.Error.WriteLine ("{0}", p.Value.AbsoluteLibraryOutput);
1478 return null;
1483 public static class Driver {
1484 static IEnumerable<XElement> GetProjects (bool withTests = false)
1486 XDocument doc = XDocument.Load ("order.xml");
1487 foreach (XElement project in doc.Root.Elements ()) {
1488 string dir = project.Attribute ("dir").Value;
1489 string library = project.Attribute ("library").Value;
1490 var profile = project.Element ("profile").Value;
1493 // Do not do 2.1, it is not working yet
1494 // Do not do basic, as there is no point (requires a system mcs to be installed).
1496 if (library.Contains ("moonlight") || library.Contains ("-basic") || library.EndsWith ("bootstrap") || library.Contains ("build"))
1497 continue;
1499 // The next ones are to make debugging easier for now
1500 if (profile == "basic")
1501 continue;
1503 // For now -- problem is, our resolver currently only considers the assembly name, and we ahve
1504 // conflicing 2.0 and 2.4 versions so for now, we just skip the nunit20 versions
1505 if (dir.Contains ("nunit20"))
1506 continue;
1508 if (library.Contains ("tests") && !withTests)
1509 continue;
1511 yield return project;
1515 public static void Main (string [] args)
1517 if (!File.Exists ("genproj.cs")) {
1518 Console.Error.WriteLine ("This command must be executed from mono/msvc/scripts");
1519 Environment.Exit (1);
1522 if (args.Length == 1) {
1523 switch (args[0].ToLower()) {
1524 case "-h":
1525 case "--help":
1526 case "-?":
1527 Console.Error.WriteLine ("Usage:");
1528 Console.Error.WriteLine ("genproj.exe [visual_studio_release] [output_full_solutions] [with_tests]");
1529 Console.Error.WriteLine ("If output_full_solutions is false, only the main System*.dll");
1530 Console.Error.WriteLine (" assemblies (and dependencies) is included in the solution.");
1531 Console.Error.WriteLine ("Example:");
1532 Console.Error.WriteLine (" genproj.exe 2012 false false");
1533 Console.Error.WriteLine ("genproj.exe with no arguments is equivalent to 'genproj.exe 2012 true false'\n\n");
1534 Console.Error.WriteLine ("genproj.exe deps");
1535 Console.Error.WriteLine ("Generates a Makefile dependency file from the projects input");
1536 Environment.Exit (0);
1537 break;
1541 var slnVersion = (args.Length > 0) ? args [0] : "2012";
1542 bool fullSolutions = (args.Length > 1) ? bool.Parse (args [1]) : true;
1543 bool withTests = (args.Length > 2) ? bool.Parse (args [2]) : false;
1545 // To generate makefile depenedencies
1546 var makefileDeps = (args.Length > 0 && args [0] == "deps");
1548 var sln_gen = new SlnGenerator (slnVersion);
1549 var four_five_sln_gen = new SlnGenerator (slnVersion);
1550 var projects = new Dictionary<string,MsbuildGenerator> ();
1552 var duplicates = new List<string> ();
1553 Console.Error.WriteLine("// Deleting existing project files");
1554 foreach (var project in GetProjects (withTests)) {
1555 var library_output = project.Element ("library_output").Value;
1557 var gen = new MsbuildGenerator (project);
1558 projects [library_output] = gen;
1559 gen.EraseExisting ();
1561 Console.Error.WriteLine("// Generating project files");
1562 foreach (var project in GetProjects (withTests)){
1563 var library_output = project.Element ("library_output").Value;
1564 // Console.WriteLine ("=== {0} ===", library_output);
1565 var gen = projects [library_output];
1566 try {
1567 string profileName;
1568 var csproj = gen.Generate (library_output, projects, out profileName);
1569 var csprojFilename = csproj.csProjFilename;
1570 if (!sln_gen.ContainsProjectIdentifier (csproj.library)) {
1571 sln_gen.Add (csproj);
1572 } else {
1573 duplicates.Add (csprojFilename);
1576 if (profileName == null) {
1577 Console.Error.WriteLine ($"// {library_output} has no profile");
1578 } else {
1579 HashSet<string> profileNames;
1580 if (!SlnGenerator.profilesByGuid.TryGetValue (csproj.projectGuid, out profileNames))
1581 SlnGenerator.profilesByGuid[csproj.projectGuid] = profileNames = new HashSet<string>();
1583 profileNames.Add (profileName);
1584 SlnGenerator.observedProfiles.Add (profileName);
1586 } catch (Exception e) {
1587 Console.Error.WriteLine ("// Error in {0}\n{1}", project, e);
1591 Console.WriteLine ("Deduplicating project references");
1593 foreach (var csprojFile in projects.Values.Select (x => x.GetProjectFilename ()).Distinct ())
1595 // Console.WriteLine ("Deduplicating: " + csprojFile);
1596 DeduplicateProjectReferences (csprojFile);
1599 Func<MsbuildGenerator.VsCsproj, bool> additionalFilter;
1600 additionalFilter = fullSolutions ? (Func<MsbuildGenerator.VsCsproj, bool>)null : IsCommonLibrary;
1602 FillSolution (four_five_sln_gen, MsbuildGenerator.profile_4_x, projects.Values, additionalFilter);
1604 if (duplicates.Count () > 0) {
1605 var sb = new StringBuilder ();
1606 sb.AppendLine ("// WARNING: Skipped some project references, apparent duplicates in order.xml:");
1607 foreach (var item in duplicates) {
1608 sb.AppendLine ($"// {item}");
1610 Console.Error.WriteLine (sb.ToString ());
1613 WriteSolution (four_five_sln_gen, Path.Combine ("..", "..", "bcl.sln"));
1615 if (makefileDeps){
1616 const string classDirPrefix = "./../../";
1617 Console.WriteLine ("here {0}", sln_gen.libraries.Count);
1618 foreach (var p in sln_gen.libraries){
1619 string rebasedOutput = RebaseToClassDirectory (MsbuildGenerator.GetRelativePath ("../../mcs/class", p.library_output));
1621 Console.Write ("{0}: ", rebasedOutput);
1622 foreach (var r in p.projReferences){
1623 var lo = r.library_output;
1624 if (lo.StartsWith (classDirPrefix))
1625 lo = lo.Substring (classDirPrefix.Length);
1626 else
1627 lo = "<<ERROR-dependency is not a class library>>";
1628 Console.Write ("{0} ", lo);
1630 Console.Write ("\n\t(cd {0}; make {1})", p.MsbuildGenerator.dir, p.library_output);
1631 Console.WriteLine ("\n");
1635 // A few other optional solutions
1636 // Solutions with 'everything' and the most common libraries used in development may be of interest
1637 //WriteSolution (sln_gen, "./mcs_full.sln");
1638 //WriteSolution (small_full_sln_gen, "small_full.sln");
1639 // The following may be useful if lacking visual studio or MonoDevelop, to bootstrap mono compiler self-hosting
1640 //WriteSolution (basic_sln_gen, "mcs_basic.sln");
1641 //WriteSolution (build_sln_gen, "mcs_build.sln");
1644 static void DeduplicateProjectReferences (string csprojFilename)
1646 XmlDocument doc = new XmlDocument ();
1647 doc.Load (csprojFilename);
1648 XmlNamespaceManager mgr = new XmlNamespaceManager (doc.NameTable);
1649 mgr.AddNamespace ("x", "http://schemas.microsoft.com/developer/msbuild/2003");
1651 XmlNode root = doc.DocumentElement;
1652 var allProjectReferences = new Dictionary<string, List<string>> ();
1654 ProcessCompileOrProjectReferenceItems (mgr, root,
1655 (source, platform) => {},
1656 // grab all project references across all platforms
1657 (projRef, platform) =>
1659 if (!allProjectReferences.ContainsKey (platform))
1660 allProjectReferences[platform] = new List<string> ();
1661 allProjectReferences[platform].Add (projRef.Attributes["Include"].Value);
1664 if (allProjectReferences.Count > 1)
1666 // find the project references which are common across all platforms
1667 var commonProjectReferences = allProjectReferences.Values.First ();
1668 foreach (var l in allProjectReferences.Values.Skip (1))
1669 commonProjectReferences = commonProjectReferences.Intersect (l).ToList ();
1671 if (commonProjectReferences.Count > 0)
1673 // remove common project references from the individual platforms
1674 ProcessCompileOrProjectReferenceItems (mgr, root, null, (projRef, platform) =>
1676 var parent = projRef.ParentNode;
1677 if (commonProjectReferences.Contains (projRef.Attributes["Include"].Value))
1678 parent.RemoveChild (projRef);
1680 if (!parent.HasChildNodes)
1681 parent.ParentNode.RemoveChild (parent);
1684 // add common project references as ItemGroup
1685 XmlNode commonProjRefsComment = root.SelectSingleNode ("//comment()[. = ' @COMMON_PROJECT_REFERENCES@ ']");
1686 XmlElement commonProjRefsElement = doc.CreateElement ("ItemGroup", root.NamespaceURI);
1688 foreach (var s in commonProjectReferences)
1690 var c = doc.CreateElement ("ProjectReference", root.NamespaceURI);
1691 var v = doc.CreateAttribute ("Include");
1692 v.Value = s;
1693 c.Attributes.Append (v);
1695 commonProjRefsElement.AppendChild (c);
1697 root.ReplaceChild (commonProjRefsElement, commonProjRefsComment);
1701 using (var w = XmlWriter.Create (csprojFilename, new XmlWriterSettings { NewLineChars = SlnGenerator.NewLine, Indent = true }))
1702 doc.Save (w);
1705 static void ProcessCompileOrProjectReferenceItems (XmlNamespaceManager mgr, XmlNode x, Action<XmlNode, string> compileAction, Action<XmlNode, string> projRefAction)
1707 foreach (XmlNode n in x.SelectNodes("//x:ItemGroup[@Condition]", mgr))
1709 if (n.Attributes.Count == 0)
1710 continue;
1712 var platform = n.Attributes["Condition"].Value;
1714 if (!platform.Contains("$(Platform)"))
1715 continue;
1717 var compileItems = n.SelectNodes("./x:Compile[@Include]", mgr);
1719 if (compileAction != null && compileItems.Count != 0) {
1720 foreach (XmlNode source in compileItems)
1721 compileAction(source, platform);
1724 var projRefItems = n.SelectNodes("./x:ProjectReference[@Include]", mgr);
1726 if (projRefAction != null && projRefItems.Count != 0) {
1727 foreach (XmlNode proj in projRefItems) {
1728 // we don't bother to process ProjectReferences with Aliases
1729 if (!proj.HasChildNodes)
1730 projRefAction(proj, platform);
1736 // Rebases a path, assuming that execution is taking place in the "class" subdirectory,
1737 // so it strips ../class/ from a path, which is a no-op
1738 static string RebaseToClassDirectory (string path)
1740 const string prefix = "../class/";
1741 int p = path.IndexOf (prefix);
1742 if (p == -1)
1743 return path;
1744 return path.Substring (0, p) + path.Substring (p+prefix.Length);
1745 return path;
1748 static void FillSolution (SlnGenerator solution, string profileString, IEnumerable<MsbuildGenerator> projects, Func<MsbuildGenerator.VsCsproj, bool> additionalFilter = null)
1750 foreach (var generator in projects) {
1751 var vsCsproj = generator.Csproj;
1752 if (!vsCsproj.library.Contains (profileString))
1753 continue;
1754 if (additionalFilter != null && !additionalFilter (vsCsproj))
1755 continue;
1756 var csprojFilename = vsCsproj.csProjFilename;
1757 if (!solution.ContainsProjectIdentifier (vsCsproj.library)) {
1758 solution.Add (vsCsproj);
1759 RecursiveAddProj (solution, vsCsproj);
1764 static void RecursiveAddProj (SlnGenerator solution, MsbuildGenerator.VsCsproj vsCsproj, int recursiveDepth = 1)
1766 const int max_recursive = 16;
1767 if (recursiveDepth > max_recursive) throw new Exception (string.Format ("Reached {0} levels of project dependency", max_recursive));
1768 foreach (var projRef in vsCsproj.projReferences) {
1769 if (!solution.ContainsProjectIdentifier (projRef.library)) {
1770 solution.Add (projRef);
1771 RecursiveAddProj (solution, projRef, recursiveDepth + 1);
1776 static void WriteSolution (SlnGenerator sln_gen, string slnfilename)
1778 Console.WriteLine (String.Format ("// Writing solution {1}, with {0} projects", sln_gen.Count, slnfilename));
1779 sln_gen.Write (slnfilename);
1782 static bool IsCommonLibrary (MsbuildGenerator.VsCsproj proj)
1784 var library = proj.library;
1785 //if (library.Contains ("-basic"))
1786 // return true;
1787 //if (library.Contains ("-build"))
1788 // return true;
1789 //if (library.StartsWith ("corlib"))
1790 // return true;
1791 if (library.StartsWith ("System-"))
1792 return true;
1793 if (library.StartsWith ("System.Xml"))
1794 return true;
1795 if (library.StartsWith ("System.Secu"))
1796 return true;
1797 if (library.StartsWith ("System.Configuration"))
1798 return true;
1799 if (library.StartsWith ("System.Core"))
1800 return true;
1801 //if (library.StartsWith ("Mono."))
1802 // return true;
1804 return false;