From 0cb2370f8ebf9e2bdabb40b977b81920000d21a9 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 29 Dec 2020 23:25:55 +0200 Subject: [PATCH] Make auto-compiling of C# work with .NET Core CodeDom doesn't work in .NET Core (https://github.com/dotnet/runtime/issues/18768) and Roslyn turns out to be too low level (just a compiler and not a building framework) so the best approach is to invoke msbuild as a process. Signed-off-by: Dimitar Dobrev --- Directory.Packages.props | 1 - build/Tests.lua | 4 +- src/Generator/CppSharp.Generator.csproj | 4 - src/Generator/Driver.cs | 90 +++++-------------- src/Generator/Generators/MSBuildGenerator.cs | 55 ++++++++++++ src/Generator/Types/Std/Stdlib.CSharp.cs | 2 +- src/Runtime/UTF8Marshaller.cs | 58 ++++++++++++ .../NamespacesBase.CSharp.csproj | 12 --- tests/NamespacesBase/premake4.lua | 3 +- .../NamespacesDerived.CSharp.csproj | 14 --- .../NamespacesDerived.Gen.cs | 2 +- .../NamespacesDerived.Tests.CSharp.csproj | 14 ++- tests/NamespacesDerived/premake4.lua | 5 +- tests/Test.props | 2 +- 14 files changed, 156 insertions(+), 110 deletions(-) create mode 100644 src/Generator/Generators/MSBuildGenerator.cs create mode 100644 src/Runtime/UTF8Marshaller.cs delete mode 100644 tests/NamespacesBase/NamespacesBase.CSharp.csproj delete mode 100644 tests/NamespacesDerived/NamespacesDerived.CSharp.csproj diff --git a/Directory.Packages.props b/Directory.Packages.props index 15a8a8ca41..41686049fc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,5 @@ - diff --git a/build/Tests.lua b/build/Tests.lua index 62a3d734da..0685e991be 100644 --- a/build/Tests.lua +++ b/build/Tests.lua @@ -84,7 +84,9 @@ function SetupTestProjectsCSharp(name, depends, extraFiles, suffix) str = "Std" end - SetupExternalManagedTestProject(name .. ".CSharp") + if name ~= "NamespacesDerived" then + SetupExternalManagedTestProject(name .. ".CSharp") + end SetupExternalManagedTestProject(name .. ".Tests.CSharp") end diff --git a/src/Generator/CppSharp.Generator.csproj b/src/Generator/CppSharp.Generator.csproj index b232da628b..a96a8f7831 100644 --- a/src/Generator/CppSharp.Generator.csproj +++ b/src/Generator/CppSharp.Generator.csproj @@ -12,8 +12,4 @@ - - - - \ No newline at end of file diff --git a/src/Generator/Driver.cs b/src/Generator/Driver.cs index 5932b75ddf..7c0449f9d4 100644 --- a/src/Generator/Driver.cs +++ b/src/Generator/Driver.cs @@ -1,26 +1,23 @@ using System; -using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using CppSharp.AST; using CppSharp.Generators; +using CppSharp.Generators.C; using CppSharp.Generators.CLI; +using CppSharp.Generators.Cpp; using CppSharp.Generators.CSharp; using CppSharp.Parser; using CppSharp.Passes; using CppSharp.Utils; -using Microsoft.CSharp; using CppSharp.Types; -using CppSharp.Generators.Cpp; -using CppSharp.Generators.C; namespace CppSharp { public class Driver : IDisposable { - public DriverOptions Options { get; private set; } + public DriverOptions Options { get; } public ParserOptions ParserOptions { get; set; } public BindingContext Context { get; private set; } public Generator Generator { get; private set; } @@ -351,68 +348,29 @@ private void WriteGeneratedCodeToFile(string file, string generatedCode) File.WriteAllText(file, generatedCode); } - private static readonly Dictionary libraryMappings = new Dictionary(); - - public void CompileCode(Module module) + public bool CompileCode(Module module) { - var assemblyFile = Path.Combine(Options.OutputDir, module.LibraryName + ".dll"); - - var docFile = Path.ChangeExtension(assemblyFile, ".xml"); + File.WriteAllText(Path.Combine(Options.OutputDir, "Directory.Build.props"), ""); - var compilerOptions = new StringBuilder(); - compilerOptions.Append($" /doc:\"{docFile}\""); - compilerOptions.Append(" /debug:pdbonly"); - compilerOptions.Append(" /unsafe"); + var msBuildGenerator = new MSBuildGenerator(Context, module, libraryMappings); + msBuildGenerator.Process(); + string csproj = Path.Combine(Options.OutputDir, + $"{module.LibraryName}.{msBuildGenerator.FileExtension}"); + File.WriteAllText(csproj, msBuildGenerator.Generate()); - var compilerParameters = new CompilerParameters + string output = ProcessHelper.Run("dotnet", $"msbuild -restore {csproj}", + out int error, out string errorMessage); + if (error == 0) { - GenerateExecutable = false, - TreatWarningsAsErrors = false, - OutputAssembly = assemblyFile, - GenerateInMemory = false, - CompilerOptions = compilerOptions.ToString() - }; - - if (module != Options.SystemModule) - compilerParameters.ReferencedAssemblies.Add( - Path.Combine(Options.OutputDir, $"{Options.SystemModule.LibraryName}.dll")); - // add a reference to System.Core - compilerParameters.ReferencedAssemblies.Add(typeof(Enumerable).Assembly.Location); - - var location = System.Reflection.Assembly.GetExecutingAssembly().Location; - var outputDir = Path.GetDirectoryName(location); - var locationRuntime = Path.Combine(outputDir, "CppSharp.Runtime.dll"); - compilerParameters.ReferencedAssemblies.Add(locationRuntime); - - compilerParameters.ReferencedAssemblies.AddRange( - (from dependency in module.Dependencies - where libraryMappings.ContainsKey(dependency) - select libraryMappings[dependency]).ToArray()); - - compilerParameters.ReferencedAssemblies.AddRange(module.ReferencedAssemblies.ToArray()); - - Diagnostics.Message($"Compiling {module.LibraryName}..."); - CompilerResults compilerResults; - using (var codeProvider = new CSharpCodeProvider( - new Dictionary { - { "CompilerDirectoryPath", ManagedToolchain.FindCSharpCompilerDir() } })) - { - compilerResults = codeProvider.CompileAssemblyFromFile( - compilerParameters, module.CodeFiles.ToArray()); + Diagnostics.Message($@"Compilation succeeded: { + libraryMappings[module] = Path.Combine( + Options.OutputDir, $"{module.LibraryName}.dll")}."); + return true; } - var errors = compilerResults.Errors.Cast().Where(e => !e.IsWarning && - // HACK: auto-compiling on OS X produces "errors" which do not affect compilation so we ignore them - (!Platform.IsMacOS || !e.ErrorText.EndsWith("(Location of the symbol related to previous warning)", StringComparison.Ordinal))).ToList(); - foreach (var error in errors) - Diagnostics.Error(error.ToString()); - - HasCompilationErrors = errors.Count > 0; - if (!HasCompilationErrors) - { - libraryMappings[module] = Path.Combine(outputDir, assemblyFile); - Diagnostics.Message("Compilation succeeded."); - } + Diagnostics.Error(output); + Diagnostics.Error(errorMessage); + return false; } public void AddTranslationUnitPass(TranslationUnitPass pass) @@ -433,6 +391,7 @@ public void Dispose() } private bool hasParsingErrors; + private static readonly Dictionary libraryMappings = new Dictionary(); } public static class ConsoleDriver @@ -498,12 +457,7 @@ public static void Run(ILibrary library) driver.SaveCode(outputs); if (driver.Options.IsCSharpGenerator && driver.Options.CompileCode) - foreach (var module in driver.Options.Modules) - { - driver.CompileCode(module); - if (driver.HasCompilationErrors) - break; - } + driver.Options.Modules.Any(m => !driver.CompileCode(m)); } } } diff --git a/src/Generator/Generators/MSBuildGenerator.cs b/src/Generator/Generators/MSBuildGenerator.cs new file mode 100644 index 0000000000..61d706b0d8 --- /dev/null +++ b/src/Generator/Generators/MSBuildGenerator.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CppSharp.AST; + +namespace CppSharp.Generators +{ + public class MSBuildGenerator : CodeGenerator + { + public MSBuildGenerator(BindingContext context, Module module, Dictionary libraryMappings) + : base(context) + { + this.module = module; + this.libraryMappings = libraryMappings; + } + + public override string FileExtension => "csproj"; + + public override void Process() + { + var location = System.Reflection.Assembly.GetExecutingAssembly().Location; + Write($@" + + + netstandard2.0 + {(Context.TargetInfo.PointerWidth == 64 ? "x64" : "x86")} + {Options.OutputDir} + {module.LibraryName}.xml + Release + true + false + false + false + + + {string.Join(Environment.NewLine, module.CodeFiles.Select(c => + $""))} + + + {string.Join(Environment.NewLine, + new[] { Path.Combine(Path.GetDirectoryName(location), "CppSharp.Runtime.dll") } + .Union(module.Dependencies.Where(libraryMappings.ContainsKey).Select(d => libraryMappings[d])) + .Select(reference => + $@" + {reference} + "))} + +".Trim()); + } + + private readonly Module module; + private readonly Dictionary libraryMappings; + } +} diff --git a/src/Generator/Types/Std/Stdlib.CSharp.cs b/src/Generator/Types/Std/Stdlib.CSharp.cs index 93c0eb70ed..32ad5b3ccc 100644 --- a/src/Generator/Types/Std/Stdlib.CSharp.cs +++ b/src/Generator/Types/Std/Stdlib.CSharp.cs @@ -101,7 +101,7 @@ public override Type CSharpSignatureType(TypePrinterContext ctx) if (enconding == Encoding.ASCII) return new CustomType("[MarshalAs(UnmanagedType.LPStr)] string"); else if (enconding == Encoding.UTF8) - return new CustomType("[MarshalAs(UnmanagedType.LPUTF8Str)] string"); + return new CustomType("[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CppSharp.Runtime.UTF8Marshaller))] string"); else if (enconding == Encoding.Unicode || enconding == Encoding.BigEndianUnicode) return new CustomType("[MarshalAs(UnmanagedType.LPWStr)] string"); else if (enconding == Encoding.UTF32) diff --git a/src/Runtime/UTF8Marshaller.cs b/src/Runtime/UTF8Marshaller.cs new file mode 100644 index 0000000000..6ac831ed51 --- /dev/null +++ b/src/Runtime/UTF8Marshaller.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace CppSharp.Runtime +{ + // HACK: .NET Standard 2.0 which we use in auto-building to support .NET Framework, lacks UnmanagedType.LPUTF8Str + public class UTF8Marshaller : ICustomMarshaler + { + public void CleanUpManagedData(object ManagedObj) + { + } + + public void CleanUpNativeData(IntPtr pNativeData) + => Marshal.FreeHGlobal(pNativeData); + + public int GetNativeDataSize() => -1; + + public IntPtr MarshalManagedToNative(object managedObj) + { + if (managedObj == null) + return IntPtr.Zero; + if (!(managedObj is string)) + throw new MarshalDirectiveException( + "UTF8Marshaler must be used on a string."); + + // not null terminated + byte[] strbuf = Encoding.UTF8.GetBytes((string) managedObj); + IntPtr buffer = Marshal.AllocHGlobal(strbuf.Length + 1); + Marshal.Copy(strbuf, 0, buffer, strbuf.Length); + + // write the terminating null + Marshal.WriteByte(buffer + strbuf.Length, 0); + return buffer; + } + + public unsafe object MarshalNativeToManaged(IntPtr str) + { + if (str == IntPtr.Zero) + return null; + + int byteCount = 0; + var str8 = (byte*) str; + while (*(str8++) != 0) byteCount += sizeof(byte); + + return Encoding.UTF8.GetString((byte*) str, byteCount); + } + + public static ICustomMarshaler GetInstance(string pstrCookie) + { + if (marshaler == null) + marshaler = new UTF8Marshaller(); + return marshaler; + } + + private static UTF8Marshaller marshaler; + } +} diff --git a/tests/NamespacesBase/NamespacesBase.CSharp.csproj b/tests/NamespacesBase/NamespacesBase.CSharp.csproj deleted file mode 100644 index 4ca9764e98..0000000000 --- a/tests/NamespacesBase/NamespacesBase.CSharp.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - NamespacesDerived.Gen - $(MSBuildProjectDirectory)\..\NamespacesDerived\ - false - $(GenDir)NamespacesDerived - - - - - - \ No newline at end of file diff --git a/tests/NamespacesBase/premake4.lua b/tests/NamespacesBase/premake4.lua index 51e1512f05..0c46c42af1 100644 --- a/tests/NamespacesBase/premake4.lua +++ b/tests/NamespacesBase/premake4.lua @@ -8,5 +8,4 @@ end group "Tests/Namespaces" SetupTestNativeProject("NamespacesBase") - targetdir (path.join(gendir, "NamespacesDerived")) - SetupWrapper("NamespacesBase") \ No newline at end of file + targetdir (path.join(gendir, "NamespacesDerived")) \ No newline at end of file diff --git a/tests/NamespacesDerived/NamespacesDerived.CSharp.csproj b/tests/NamespacesDerived/NamespacesDerived.CSharp.csproj deleted file mode 100644 index 73be09d8ef..0000000000 --- a/tests/NamespacesDerived/NamespacesDerived.CSharp.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - false - NamespacesDerived.CSharp.xml - - - - - - - - - - \ No newline at end of file diff --git a/tests/NamespacesDerived/NamespacesDerived.Gen.cs b/tests/NamespacesDerived/NamespacesDerived.Gen.cs index cc017ff1ef..427a0f7dac 100644 --- a/tests/NamespacesDerived/NamespacesDerived.Gen.cs +++ b/tests/NamespacesDerived/NamespacesDerived.Gen.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Reflection; using CppSharp.AST; using CppSharp.Generators; using CppSharp.Utils; @@ -18,6 +17,7 @@ public override void Setup(Driver driver) base.Setup(driver); driver.Options.GenerateDefaultValuesForArguments = true; driver.Options.GenerateClassTemplates = true; + driver.Options.CompileCode = true; driver.Options.DependentNameSpaces.Add("System.Runtime.CompilerServices"); driver.Options.Modules[1].IncludeDirs.Add(GetTestsDirectory("NamespacesDerived")); var @base = "NamespacesBase"; diff --git a/tests/NamespacesDerived/NamespacesDerived.Tests.CSharp.csproj b/tests/NamespacesDerived/NamespacesDerived.Tests.CSharp.csproj index a6968989f3..b6509a70d3 100644 --- a/tests/NamespacesDerived/NamespacesDerived.Tests.CSharp.csproj +++ b/tests/NamespacesDerived/NamespacesDerived.Tests.CSharp.csproj @@ -1 +1,13 @@ - \ No newline at end of file + + + + + + + ..\..\build\gen\NamespacesDerived\NamespacesBase.dll + + + ..\..\build\gen\NamespacesDerived\NamespacesDerived.dll + + + \ No newline at end of file diff --git a/tests/NamespacesDerived/premake4.lua b/tests/NamespacesDerived/premake4.lua index 62b7c64a11..a369545b55 100644 --- a/tests/NamespacesDerived/premake4.lua +++ b/tests/NamespacesDerived/premake4.lua @@ -6,7 +6,4 @@ group "Tests/Namespaces" end SetupTestGeneratorProject("NamespacesDerived") - SetupTestProjectsCSharp("NamespacesDerived", "NamespacesBase") - - project("NamespacesDerived.Tests.CSharp") - links { "NamespacesBase.CSharp" } \ No newline at end of file + SetupTestProjectsCSharp("NamespacesDerived", "NamespacesBase") \ No newline at end of file diff --git a/tests/Test.props b/tests/Test.props index ee6f57779b..46c86991a8 100644 --- a/tests/Test.props +++ b/tests/Test.props @@ -9,7 +9,7 @@ - +