7d7f676260
Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
420 lines
15 KiB
C#
420 lines
15 KiB
C#
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;
|
|
}
|
|
|
|
testPath = Path.Combine (libraryDirectory, $"{hostPlatform}_{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;
|
|
}
|
|
} |