diff options
Diffstat (limited to 'mcs/build/gensources.cs')
-rw-r--r-- | mcs/build/gensources.cs | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/mcs/build/gensources.cs b/mcs/build/gensources.cs new file mode 100644 index 00000000000..d55c627d4dc --- /dev/null +++ b/mcs/build/gensources.cs @@ -0,0 +1,411 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +public static class Program { + public static int Main (string[] _args) { + var args = new List<string> (_args); + bool useStdout = false, showHelp = false, strictMode = false; + + for (int i = 0; i < args.Count; i++) { + var arg = args[i]; + if (!arg.StartsWith ("-")) + continue; + + switch (arg) { + case "-?": + case "--help": + case "-h": + showHelp = true; + break; + case "--trace": + case "--trace1": + SourcesParser.TraceLevel = 1; + break; + case "--trace2": + SourcesParser.TraceLevel = 2; + break; + case "--trace3": + SourcesParser.TraceLevel = 3; + break; + case "--trace4": + SourcesParser.TraceLevel = 4; + break; + case "--stdout": + useStdout = true; + break; + case "--strict": + strictMode = true; + break; + default: + Console.Error.WriteLine ("Unrecognized switch " + arg); + break; + } + + args.RemoveAt (i); + i--; + } + + if (args.Count != 4) + showHelp = true; + + if (showHelp) { + Console.Error.WriteLine ("Usage: mcs/build/gensources.exe [options] (outputFileName|--stdout) libraryDirectoryAndName platformName profileName"); + Console.Error.WriteLine ("You can specify * for platformName and profileName to read all sources files"); + Console.Error.WriteLine ("Available options:"); + Console.Error.WriteLine ("--help -h -?"); + Console.Error.WriteLine (" Show command line info"); + Console.Error.WriteLine ("--trace1 --trace2 --trace3 --trace4"); + Console.Error.WriteLine (" Enable diagnostic output"); + Console.Error.WriteLine ("--stdout"); + Console.Error.WriteLine (" Writes results to standard output (omit outputFileName if you use this)"); + Console.Error.WriteLine ("--strict"); + Console.Error.WriteLine (" Produces an error exit code if files or directories are invalid/missing"); + return 1; + } + + var myAssembly = Assembly.GetExecutingAssembly (); + var codeBase = new Uri (myAssembly.CodeBase); + var executablePath = Path.GetFullPath (codeBase.LocalPath); + var executableDirectory = Path.GetDirectoryName (executablePath); + + var outFile = Path.GetFullPath (args[0]); + var libraryFullName = Path.GetFullPath (args[1]); + var platformName = args[2]; + var profileName = args[3]; + var platformsFolder = Path.Combine (executableDirectory, "platforms"); + var profilesFolder = Path.Combine (executableDirectory, "profiles"); + + var libraryDirectory = Path.GetDirectoryName (libraryFullName); + var libraryName = Path.GetFileName (libraryFullName); + + var parser = new SourcesParser (platformsFolder, profilesFolder); + var result = parser.Parse (libraryDirectory, libraryName, platformName, profileName); + + if (SourcesParser.TraceLevel > 0) + Console.Error.WriteLine ($"// Writing sources for platform {platformName} and profile {profileName}, relative to {libraryDirectory}, to {outFile}."); + + TextWriter output; + if (useStdout) + output = Console.Out; + else + output = new StreamWriter (outFile); + + using (output) { + foreach (var fileName in result.GetFileNames ().OrderBy (s => s, StringComparer.Ordinal)) + output.WriteLine (fileName); + } + + if (strictMode) + return result.ErrorCount; + else + return 0; + } +} + +public struct ParseEntry { + public string SourcesFileName; + public string Directory; + public string Pattern; + public string HostPlatform; + public string ProfileName; +} + +public struct Source { + public string FileName; +} + +public class ParseResult { + public readonly string LibraryDirectory, LibraryName; + + public readonly List<ParseEntry> Sources = new List<ParseEntry> (); + public readonly List<ParseEntry> Exclusions = new List<ParseEntry> (); + + // FIXME: This is a bad spot for this value but enumerators don't have outparam support + public int ErrorCount = 0; + + public ParseResult (string libraryDirectory, string libraryName) { + LibraryDirectory = libraryDirectory; + LibraryName = libraryName; + } + + private static string GetRelativePath (string fullPath, string relativeToDirectory) { + fullPath = fullPath.Replace (SourcesParser.DirectorySeparator, "/"); + relativeToDirectory = relativeToDirectory.Replace (SourcesParser.DirectorySeparator, "/"); + + if (!relativeToDirectory.EndsWith (SourcesParser.DirectorySeparator)) + relativeToDirectory += SourcesParser.DirectorySeparator; + var dirUri = new Uri (relativeToDirectory); + var pathUri = new Uri (fullPath); + + var relativeUri = Uri.UnescapeDataString ( + dirUri.MakeRelativeUri (pathUri).OriginalString + ).Replace ("/", SourcesParser.DirectorySeparator); + + if (SourcesParser.TraceLevel >= 4) + Console.Error.WriteLine ($"// {fullPath} -> {relativeUri}"); + + return relativeUri; + } + + private IEnumerable<string> EnumerateMatches ( + IEnumerable<ParseEntry> entries, + string hostPlatformName, string profileName + ) { + foreach (var entry in entries) { + if ( + (hostPlatformName != null) && + (entry.HostPlatform ?? hostPlatformName) != hostPlatformName + ) + continue; + if ( + (profileName != null) && + (entry.ProfileName ?? profileName) != profileName + ) + continue; + + var absolutePath = Path.Combine (entry.Directory, entry.Pattern); + var absoluteDirectory = Path.GetDirectoryName (absolutePath); + var absolutePattern = Path.GetFileName (absolutePath); + + if (SourcesParser.TraceLevel >= 3) { + if ((absolutePattern != entry.Pattern) || (absoluteDirectory != entry.Directory)) + Console.Error.WriteLine ($"// {entry.Directory} / {entry.Pattern} -> {absoluteDirectory} / {absolutePattern}"); + } + + if (!Directory.Exists (absoluteDirectory)) { + Console.Error.WriteLine ($"Directory does not exist: {Path.GetFullPath (absoluteDirectory)}"); + ErrorCount += 1; + continue; + } + + var matchingFiles = Directory.GetFiles (absoluteDirectory, absolutePattern); + foreach (var fileName in matchingFiles) { + var relativePath = GetRelativePath (fileName, LibraryDirectory); + yield return relativePath; + } + } + } + + // If you loaded sources files for multiple profiles, you can use the arguments here + // to filter the results + public IEnumerable<string> GetFileNames ( + string hostPlatformName = null, string profileName = null + ) { + var encounteredFileNames = new HashSet<string> (StringComparer.Ordinal); + + var excludedFiles = new HashSet<string> ( + EnumerateMatches (Exclusions, hostPlatformName, profileName), + StringComparer.Ordinal + ); + + foreach (var fileName in EnumerateMatches (Sources, hostPlatformName, profileName)) { + if (excludedFiles.Contains (fileName)) { + if (SourcesParser.TraceLevel >= 3) + Console.Error.WriteLine ($"// Excluding {fileName}"); + continue; + } + + // Skip duplicates + if (encounteredFileNames.Contains (fileName)) + continue; + + encounteredFileNames.Add (fileName); + yield return fileName; + } + } +} + +public class SourcesParser { + public static readonly string DirectorySeparator = new String (Path.DirectorySeparatorChar, 1); + public static int TraceLevel = 0; + + private class State { + public ParseResult Result; + public string HostPlatform; + public string ProfileName; + + public int SourcesFilesParsed, ExclusionsFilesParsed; + + public List<ParseEntry> ParsedSources { + get { + return Result.Sources; + } + } + + public List<ParseEntry> ParsedExclusions { + get { + return Result.Exclusions; + } + } + } + + public readonly string[] AllHostPlatformNames; + public readonly string[] AllProfileNames; + + private int ParseDepth = 0; + + public SourcesParser ( + string platformsFolder, string profilesFolder + ) { + AllHostPlatformNames = Directory.GetFiles (platformsFolder, "*.make") + .Select (Path.GetFileNameWithoutExtension) + .ToArray (); + AllProfileNames = Directory.GetFiles (profilesFolder, "*.make") + .Select (Path.GetFileNameWithoutExtension) + .ToArray (); + } + + public ParseResult Parse (string libraryDirectory, string libraryName, string hostPlatform, string profile) { + var state = new State { + Result = new ParseResult (libraryDirectory, libraryName), + ProfileName = profile, + HostPlatform = hostPlatform + }; + + var testPath = Path.Combine (libraryDirectory, $"{hostPlatform}_{profile}_{libraryName}"); + var ok = TryParseSingleFile (state, testPath + ".sources", false); + TryParseSingleFile (state, testPath + ".exclude.sources", true); + + if (ok) { + PrintSummary (state); + return state.Result; + } + + state.HostPlatform = null; + + testPath = Path.Combine (libraryDirectory, $"{profile}_{libraryName}"); + ok = TryParseSingleFile (state, testPath + ".sources", false); + TryParseSingleFile (state, testPath + ".exclude.sources", true); + + if (ok) { + PrintSummary (state); + return state.Result; + } + + state.ProfileName = null; + + testPath = Path.Combine (libraryDirectory, libraryName); + TryParseSingleFile (state, testPath + ".sources", false); + TryParseSingleFile (state, testPath + ".exclude.sources", true); + + PrintSummary (state); + + return state.Result; + } + + public ParseResult Parse (string libraryDirectory, string libraryName) { + var state = new State { + Result = new ParseResult (libraryDirectory, libraryName) + }; + + string testPath = Path.Combine (libraryDirectory, libraryName); + TryParseSingleFile (state, testPath + ".sources", false); + TryParseSingleFile (state, testPath + ".exclude.sources", true); + + foreach (var profile in AllProfileNames) { + state.ProfileName = profile; + + foreach (var hostPlatform in AllHostPlatformNames) { + state.HostPlatform = hostPlatform; + + testPath = Path.Combine (libraryDirectory, $"{hostPlatform}_{profile}_{libraryName}"); + TryParseSingleFile (state, testPath + ".sources", false); + TryParseSingleFile (state, testPath + ".exclude.sources", true); + } + + state.HostPlatform = null; + + testPath = Path.Combine (libraryDirectory, $"{profile}_{libraryName}"); + TryParseSingleFile (state, testPath + ".sources", false); + TryParseSingleFile (state, testPath + ".exclude.sources", true); + } + + PrintSummary (state); + + return state.Result; + } + + private void PrintSummary (State state) { + if (TraceLevel > 0) + Console.Error.WriteLine ($"// Parsed {state.SourcesFilesParsed} sources file(s) and {state.ExclusionsFilesParsed} exclusions file(s)."); + } + + private void HandleMetaDirective (State state, string directory, bool asExclusionsList, string directive) { + var include = "#include "; + if (directive.StartsWith (include)) + ParseSingleFile (state, Path.Combine (directory, directive.Substring (include.Length)), asExclusionsList); + } + + private bool TryParseSingleFile (State state, string fileName, bool asExclusionsList) { + if (!File.Exists (fileName)) + return false; + + ParseSingleFile (state, fileName, asExclusionsList); + return true; + } + + private void ParseSingleFile (State state, string fileName, bool asExclusionsList) { + var nullStr = "<none>"; + if (TraceLevel >= 1) + Console.Error.WriteLine ($"// {new String (' ', ParseDepth * 2)}{fileName} [{state.HostPlatform ?? nullStr}] [{state.ProfileName ?? nullStr}]"); + ParseDepth += 1; + + var directory = Path.GetDirectoryName (fileName); + + using (var sr = new StreamReader (fileName)) { + if (asExclusionsList) + state.ExclusionsFilesParsed++; + else + state.SourcesFilesParsed++; + + string line; + while ((line = sr.ReadLine ()) != null) { + if (String.IsNullOrWhiteSpace (line)) + continue; + + if (line.StartsWith ("#")) { + HandleMetaDirective (state, directory, asExclusionsList, line); + continue; + } + + var parts = line.Split (':'); + + if (parts.Length > 1) { + var explicitExclusions = parts[1].Split (','); + + // gensources.sh implemented these explicit exclusions like so: + // ../foo/bar/*.cs:A.cs,B.cs + // This would generate exclusions for ../foo/bar/A.cs and ../foo/bar/B.cs, + // not ./A.cs and ./B.cs as you might expect + + var mainPatternDirectory = Path.GetDirectoryName (parts[0]); + + foreach (var pattern in explicitExclusions) { + state.ParsedExclusions.Add (new ParseEntry { + SourcesFileName = fileName, + Directory = directory, + Pattern = Path.Combine (mainPatternDirectory, pattern), + HostPlatform = state.HostPlatform, + ProfileName = state.ProfileName + }); + } + } + + (asExclusionsList ? state.ParsedExclusions : state.ParsedSources) + .Add (new ParseEntry { + SourcesFileName = fileName, + Directory = directory, + Pattern = parts[0], + HostPlatform = state.HostPlatform, + ProfileName = state.ProfileName + }); + } + } + + ParseDepth -= 1; + } +}
\ No newline at end of file |