-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new Mosa.Utility.CreateCoreLib tool (#1234)
* Add new Mosa.Tool.CreateCoreLib tool Signed-off-by: AnErrupTion <[email protected]> * Rename to Mosa.Utility.CreateCoreLib Signed-off-by: AnErrupTion <[email protected]> --------- Signed-off-by: AnErrupTion <[email protected]>
- Loading branch information
1 parent
b7d9f02
commit 5ac3b6c
Showing
7 changed files
with
270 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
Source/Mosa.Utility.CreateCoreLib/Mosa.Utility.CreateCoreLib.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="CommandLineParser" Version="2.9.1" /> | ||
<PackageReference Include="ICSharpCode.Decompiler" Version="8.2.0.7535" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using CommandLine; | ||
|
||
namespace Mosa.Utility.CreateCoreLib; | ||
|
||
public class Options | ||
{ | ||
[Option('o', "output", Required = true, HelpText = "Sets the output directory.")] | ||
public string? OutputDirectory { get; set; } | ||
|
||
[Option('c', "copy-files", Required = false, HelpText = "If enabled, only copies and patches the source files and stops.")] | ||
public bool CopyFiles { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using System.Diagnostics; | ||
|
||
namespace Mosa.Utility.CreateCoreLib; | ||
|
||
/// <summary> | ||
/// A custom patch system. It isn't designed to be particularly fast, but is rather designed with the simplicity of a singular patch in mind. | ||
/// </summary> | ||
public static class Patcher | ||
{ | ||
/* | ||
* Modes: | ||
* rs - [r]emove line if it [s]tarts with str1 | ||
* R - [R]eplace all occurences of str1 with str2 | ||
*/ | ||
public record PatchRecord(string File, string Mode, string Str1, string? Str2); | ||
|
||
// First round of patches before compilation (i.e. with files directly from the repository), they use paths relative to the current directory. | ||
// The reference API source files weren't designed to be compiled as-is, so they have to be patched very lightly. | ||
public static readonly PatchRecord[] FirstPatches = | ||
[ | ||
new PatchRecord("runtime/src/libraries/System.DirectoryServices/ref/System.DirectoryServices.manual.cs", "rs", "[assembly: TypeForwardedTo(", null), | ||
new PatchRecord("runtime/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs", "R", "protected override bool TryComputeLength", "protected internal override bool TryComputeLength"), | ||
new PatchRecord("runtime/src/libraries/System.Net.Http.WinHttpHandler/ref/System.Net.Http.WinHttpHandler.cs", "R", "protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync", "protected internal override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync"), | ||
new PatchRecord("runtime/src/libraries/System.Data.Common/ref/System.Data.Common.cs", "R", "protected override System.Xml.XPath.XPathNavigator? CreateNavigator", "protected internal override System.Xml.XPath.XPathNavigator? CreateNavigator"), | ||
new PatchRecord("runtime/src/libraries/System.Configuration.ConfigurationManager/ref/System.Configuration.ConfigurationManager.cs", "R", "[System.ObsoleteAttribute(System.Obsoletions.BinaryFormatterMessage + @\"", "[System.ObsoleteAttribute(\"BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information") | ||
]; | ||
|
||
// Second round of patches after decompilation, they use paths relative to the output directory. | ||
// The code produced by ICSharpCode's decompiler isn't perfected, and isn't designed to be compiled directly either. | ||
// While it requires a bit more patching than before, it still isn't a dramatic amount of patches. | ||
public static readonly PatchRecord[] SecondPatches = | ||
[ | ||
new PatchRecord("System.Collections.Generic/PriorityQueue.cs", "R", "IEnumerator<(TElement, TPriority)>", "IEnumerator<(TElement Element, TPriority Priority)>"), | ||
new PatchRecord("System.Collections.Generic/PriorityQueue.cs", "R", "IEnumerable<(TElement, TPriority)>", "IEnumerable<(TElement Element, TPriority Priority)>"), | ||
new PatchRecord("System/Nullable.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System/ReadOnlySpan.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.InteropServices/Marshal.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.InteropServices/MemoryMarshal.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.CompilerServices/Unsafe.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Numerics/Vector.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.Intrinsics/Vector64.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.Intrinsics/Vector128.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.Intrinsics/Vector256.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Runtime.Intrinsics/Vector512.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Threading/Volatile.cs", "R", "[RequiresLocation]", string.Empty), | ||
new PatchRecord("System.Threading/Interlocked.cs", "R", "[RequiresLocation]", string.Empty) | ||
]; | ||
|
||
public static string Patch(PatchRecord patch, string path = "") | ||
{ | ||
var file = !string.IsNullOrEmpty(path) ? Path.Combine(path, patch.File) : patch.File; | ||
|
||
switch (patch.Mode) | ||
{ | ||
case "rs": | ||
{ | ||
var code = File.ReadAllLines(file).ToList(); | ||
var temp = code[..]; | ||
|
||
foreach (var line in temp) | ||
if (line.StartsWith(patch.Str1)) | ||
code.Remove(line); | ||
|
||
return string.Join('\n', code); | ||
} | ||
case "R": return File.ReadAllText(file).Replace(patch.Str1, patch.Str2); | ||
} | ||
|
||
throw new UnreachableException(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
using System.Diagnostics; | ||
using CommandLine; | ||
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; | ||
using ICSharpCode.Decompiler.Metadata; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Mosa.Utility.CreateCoreLib; | ||
|
||
var options = Parser.Default.ParseArguments<Options>(args).Value; | ||
if (options == null) return; | ||
|
||
var outputDirectory = options.OutputDirectory; | ||
var copyFiles = options.CopyFiles; | ||
|
||
if (string.IsNullOrEmpty(outputDirectory)) | ||
{ | ||
Console.WriteLine("Invalid output directory. Usage: Mosa.Utility.CreateCoreLib --output/-o <directory>"); | ||
return; | ||
} | ||
|
||
Directory.CreateDirectory(outputDirectory); | ||
|
||
if (!Directory.Exists("runtime")) | ||
{ | ||
Console.WriteLine("Cloning .NET runtime GitHub repository..."); | ||
Process.Start("git", "clone https://github.com/dotnet/runtime -b release/8.0 --depth=1")?.WaitForExit(); | ||
} | ||
|
||
Console.WriteLine(copyFiles ? "Copying files..." : "Parsing input files..."); | ||
|
||
var syntaxTrees = new List<SyntaxTree>(); | ||
|
||
foreach (var folder in Directory.EnumerateDirectories(Path.Combine("runtime", "src", "libraries"))) | ||
{ | ||
// We don't need those namespaces so we exclude them. | ||
if (folder.Contains("Microsoft.Bcl.") || folder.Contains("Microsoft.Extensions.")) continue; | ||
|
||
var refDirectory = Path.Combine(folder, "ref"); | ||
if (!Directory.Exists(refDirectory)) | ||
continue; | ||
|
||
foreach (var file in Directory.GetFiles(refDirectory, "*.cs")) | ||
{ | ||
// All of these files contain "type forwards", which basically forward certain types to other assemblies. | ||
// Since we have a monolithic assembly, we can safely ignore those (though we'll still have to patch a few). | ||
// They also cause compilation errors because they're not designed to be compiled, so removing them is a must. | ||
if (file.EndsWith(".Forwards.cs") || file.EndsWith(".netframework.cs") || file.EndsWith(".TypeForwards.cs") | ||
|| file.Contains(".Typeforwards.")) | ||
continue; | ||
|
||
var fileName = Path.GetFileName(file); | ||
string? text = null; | ||
|
||
var patch = Patcher.FirstPatches.FirstOrDefault(x => file.EndsWith(x.File)); | ||
if (patch != default) | ||
{ | ||
Console.WriteLine($"Patching {fileName}..."); | ||
text = Patcher.Patch(patch); | ||
} | ||
|
||
if (copyFiles) | ||
{ | ||
var outputPath = Path.Combine(outputDirectory, fileName); | ||
|
||
if (text != null) | ||
File.WriteAllText(outputPath, text); | ||
else | ||
File.Copy(file, outputPath, true); | ||
|
||
continue; | ||
} | ||
|
||
text ??= File.ReadAllText(file); | ||
syntaxTrees.Add(CSharpSyntaxTree.ParseText(text, new CSharpParseOptions(preprocessorSymbols: ["NETCOREAPP"]))); | ||
} | ||
} | ||
|
||
if (copyFiles) return; | ||
|
||
var compilation = CSharpCompilation.Create("System.Runtime", syntaxTrees, null, | ||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); | ||
var outputFile = compilation.AssemblyName + ".dll"; | ||
|
||
Console.WriteLine("Compiling source files..."); | ||
|
||
using (var stream = File.OpenWrite("System.Runtime.dll")) | ||
{ | ||
var result = compilation.Emit(stream); | ||
if (!result.Success) | ||
{ | ||
var failures = result.Diagnostics | ||
.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); | ||
|
||
foreach (var diagnostic in failures) | ||
Console.WriteLine($"{diagnostic.Id}: {diagnostic.GetMessage()}"); | ||
|
||
return; | ||
} | ||
} | ||
|
||
Console.WriteLine("Decompiling assembly..."); | ||
|
||
var module = new PEFile(outputFile); | ||
var resolver = new UniversalAssemblyResolver(outputFile, true, module.Metadata.DetectTargetFrameworkId()); | ||
var decompiler = new WholeProjectDecompiler(resolver); | ||
|
||
decompiler.DecompileProject(module, outputDirectory); | ||
|
||
// We don't need the project file because we should already have one. Even if it's missing, it's trivial to create. | ||
Console.WriteLine("Removing project file..."); | ||
File.Delete(Path.Combine(outputDirectory, Path.ChangeExtension(outputFile, "csproj"))); | ||
|
||
// The assembly will generate its own assembly information at compile time, so we don't need to pre-define it. | ||
Console.WriteLine("Removing Properties folder..."); | ||
Directory.Delete(Path.Combine(outputDirectory, "Properties"), true); | ||
|
||
foreach (var patch in Patcher.SecondPatches) | ||
{ | ||
var file = Path.Combine(outputDirectory, patch.File); | ||
Console.WriteLine($"Patching {patch.File}..."); | ||
|
||
var code = Patcher.Patch(patch, outputDirectory); | ||
File.WriteAllText(file, code); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters