Skip to content

Commit c930b78

Browse files
committed
Make auto-compiling of C# work with .NET Core
CodeDom doesn't work in .NET Core (dotnet/runtime#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 <[email protected]>
1 parent 5041fa3 commit c930b78

15 files changed

+157
-111
lines changed

Directory.Packages.props

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<Project>
22
<ItemGroup>
3-
<PackageVersion Include="System.CodeDom" Version="4.7.0" />
43
<PackageVersion Include="Microsoft.Win32.Registry" Version="4.7.0" />
54
<PackageVersion Include="Microsoft.VisualStudio.Setup.Configuration.Interop" Version="2.3.2262-g94fae01e" />
65
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />

build/Tests.lua

+3-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ function SetupTestProjectsCSharp(name, depends, extraFiles, suffix)
8484
str = "Std"
8585
end
8686

87-
SetupExternalManagedTestProject(name .. ".CSharp")
87+
if name ~= "NamespacesDerived" then
88+
SetupExternalManagedTestProject(name .. ".CSharp")
89+
end
8890
SetupExternalManagedTestProject(name .. ".Tests.CSharp")
8991
end
9092

src/Generator/CppSharp.Generator.csproj

-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,4 @@
1212
<ProjectReference Include="..\Parser\CppSharp.Parser.csproj" />
1313
<ProjectReference Include="$(NativeProjectsDir)Std-symbols.vcxproj" ReferenceOutputAssembly="false" Condition="$(IsWindows)" />
1414
</ItemGroup>
15-
16-
<ItemGroup>
17-
<PackageReference Include="System.CodeDom" PrivateAssets="All" />
18-
</ItemGroup>
1915
</Project>

src/Generator/Driver.cs

+22-68
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
using System;
2-
using System.CodeDom.Compiler;
32
using System.Collections.Generic;
43
using System.IO;
54
using System.Linq;
6-
using System.Text;
75
using CppSharp.AST;
86
using CppSharp.Generators;
7+
using CppSharp.Generators.C;
98
using CppSharp.Generators.CLI;
9+
using CppSharp.Generators.Cpp;
1010
using CppSharp.Generators.CSharp;
1111
using CppSharp.Parser;
1212
using CppSharp.Passes;
1313
using CppSharp.Utils;
14-
using Microsoft.CSharp;
1514
using CppSharp.Types;
16-
using CppSharp.Generators.Cpp;
17-
using CppSharp.Generators.C;
1815

1916
namespace CppSharp
2017
{
2118
public class Driver : IDisposable
2219
{
23-
public DriverOptions Options { get; private set; }
20+
public DriverOptions Options { get; }
2421
public ParserOptions ParserOptions { get; set; }
2522
public BindingContext Context { get; private set; }
2623
public Generator Generator { get; private set; }
@@ -351,68 +348,29 @@ private void WriteGeneratedCodeToFile(string file, string generatedCode)
351348
File.WriteAllText(file, generatedCode);
352349
}
353350

354-
private static readonly Dictionary<Module, string> libraryMappings = new Dictionary<Module, string>();
355-
356-
public void CompileCode(Module module)
351+
public bool CompileCode(Module module)
357352
{
358-
var assemblyFile = Path.Combine(Options.OutputDir, module.LibraryName + ".dll");
359-
360-
var docFile = Path.ChangeExtension(assemblyFile, ".xml");
353+
File.WriteAllText(Path.Combine(Options.OutputDir, "Directory.Build.props"), "<Project />");
361354

362-
var compilerOptions = new StringBuilder();
363-
compilerOptions.Append($" /doc:\"{docFile}\"");
364-
compilerOptions.Append(" /debug:pdbonly");
365-
compilerOptions.Append(" /unsafe");
355+
var msBuildGenerator = new MSBuildGenerator(Context, module, libraryMappings);
356+
msBuildGenerator.Process();
357+
string csproj = Path.Combine(Options.OutputDir,
358+
$"{module.LibraryName}.{msBuildGenerator.FileExtension}");
359+
File.WriteAllText(csproj, msBuildGenerator.Generate());
366360

367-
var compilerParameters = new CompilerParameters
361+
string output = ProcessHelper.Run("dotnet", $"build {csproj}",
362+
out int error, out string errorMessage);
363+
if (error == 0)
368364
{
369-
GenerateExecutable = false,
370-
TreatWarningsAsErrors = false,
371-
OutputAssembly = assemblyFile,
372-
GenerateInMemory = false,
373-
CompilerOptions = compilerOptions.ToString()
374-
};
375-
376-
if (module != Options.SystemModule)
377-
compilerParameters.ReferencedAssemblies.Add(
378-
Path.Combine(Options.OutputDir, $"{Options.SystemModule.LibraryName}.dll"));
379-
// add a reference to System.Core
380-
compilerParameters.ReferencedAssemblies.Add(typeof(Enumerable).Assembly.Location);
381-
382-
var location = System.Reflection.Assembly.GetExecutingAssembly().Location;
383-
var outputDir = Path.GetDirectoryName(location);
384-
var locationRuntime = Path.Combine(outputDir, "CppSharp.Runtime.dll");
385-
compilerParameters.ReferencedAssemblies.Add(locationRuntime);
386-
387-
compilerParameters.ReferencedAssemblies.AddRange(
388-
(from dependency in module.Dependencies
389-
where libraryMappings.ContainsKey(dependency)
390-
select libraryMappings[dependency]).ToArray());
391-
392-
compilerParameters.ReferencedAssemblies.AddRange(module.ReferencedAssemblies.ToArray());
393-
394-
Diagnostics.Message($"Compiling {module.LibraryName}...");
395-
CompilerResults compilerResults;
396-
using (var codeProvider = new CSharpCodeProvider(
397-
new Dictionary<string, string> {
398-
{ "CompilerDirectoryPath", ManagedToolchain.FindCSharpCompilerDir() } }))
399-
{
400-
compilerResults = codeProvider.CompileAssemblyFromFile(
401-
compilerParameters, module.CodeFiles.ToArray());
365+
Diagnostics.Message($@"Compilation succeeded: {
366+
libraryMappings[module] = Path.Combine(
367+
Options.OutputDir, $"{module.LibraryName}.dll")}.");
368+
return true;
402369
}
403370

404-
var errors = compilerResults.Errors.Cast<CompilerError>().Where(e => !e.IsWarning &&
405-
// HACK: auto-compiling on OS X produces "errors" which do not affect compilation so we ignore them
406-
(!Platform.IsMacOS || !e.ErrorText.EndsWith("(Location of the symbol related to previous warning)", StringComparison.Ordinal))).ToList();
407-
foreach (var error in errors)
408-
Diagnostics.Error(error.ToString());
409-
410-
HasCompilationErrors = errors.Count > 0;
411-
if (!HasCompilationErrors)
412-
{
413-
libraryMappings[module] = Path.Combine(outputDir, assemblyFile);
414-
Diagnostics.Message("Compilation succeeded.");
415-
}
371+
Diagnostics.Error(output);
372+
Diagnostics.Error(errorMessage);
373+
return false;
416374
}
417375

418376
public void AddTranslationUnitPass(TranslationUnitPass pass)
@@ -433,6 +391,7 @@ public void Dispose()
433391
}
434392

435393
private bool hasParsingErrors;
394+
private static readonly Dictionary<Module, string> libraryMappings = new Dictionary<Module, string>();
436395
}
437396

438397
public static class ConsoleDriver
@@ -498,12 +457,7 @@ public static void Run(ILibrary library)
498457

499458
driver.SaveCode(outputs);
500459
if (driver.Options.IsCSharpGenerator && driver.Options.CompileCode)
501-
foreach (var module in driver.Options.Modules)
502-
{
503-
driver.CompileCode(module);
504-
if (driver.HasCompilationErrors)
505-
break;
506-
}
460+
driver.Options.Modules.Any(m => !driver.CompileCode(m));
507461
}
508462
}
509463
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using CppSharp.AST;
6+
7+
namespace CppSharp.Generators
8+
{
9+
public class MSBuildGenerator : CodeGenerator
10+
{
11+
public MSBuildGenerator(BindingContext context, Module module,
12+
Dictionary<Module, string> libraryMappings)
13+
: base(context)
14+
{
15+
this.module = module;
16+
this.libraryMappings = libraryMappings;
17+
}
18+
19+
public override string FileExtension => "csproj";
20+
21+
public override void Process()
22+
{
23+
var location = System.Reflection.Assembly.GetExecutingAssembly().Location;
24+
Write($@"
25+
<Project Sdk=""Microsoft.NET.Sdk"">
26+
<PropertyGroup>
27+
<TargetFramework>netstandard2.0</TargetFramework>
28+
<PlatformTarget>{(Context.TargetInfo.PointerWidth == 64 ? "x64" : "x86")}</PlatformTarget>
29+
<OutputPath>{Options.OutputDir}</OutputPath>
30+
<DocumentationFile>{module.LibraryName}.xml</DocumentationFile>
31+
<Configuration>Release</Configuration>
32+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
33+
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
34+
<EnableDefaultItems>false</EnableDefaultItems>
35+
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
36+
</PropertyGroup>
37+
<ItemGroup>
38+
{string.Join(Environment.NewLine, module.CodeFiles.Select(c =>
39+
$"<Compile Include=\"{c}\" />"))}
40+
</ItemGroup>
41+
<ItemGroup>
42+
{string.Join(Environment.NewLine,
43+
new[] { Path.Combine(Path.GetDirectoryName(location), "CppSharp.Runtime.dll") }
44+
.Union(module.Dependencies.Where(libraryMappings.ContainsKey).Select(d => libraryMappings[d]))
45+
.Select(reference =>
46+
$@"<Reference Include=""{Path.GetFileNameWithoutExtension(reference)}"">
47+
<HintPath>{reference}</HintPath>
48+
</Reference>"))}
49+
</ItemGroup>
50+
</Project>".Trim());
51+
}
52+
53+
private readonly Module module;
54+
private readonly Dictionary<Module, string> libraryMappings;
55+
}
56+
}

src/Generator/Types/Std/Stdlib.CSharp.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public override Type CSharpSignatureType(TypePrinterContext ctx)
101101
if (enconding == Encoding.ASCII)
102102
return new CustomType("[MarshalAs(UnmanagedType.LPStr)] string");
103103
else if (enconding == Encoding.UTF8)
104-
return new CustomType("[MarshalAs(UnmanagedType.LPUTF8Str)] string");
104+
return new CustomType("[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CppSharp.Runtime.UTF8Marshaller))] string");
105105
else if (enconding == Encoding.Unicode || enconding == Encoding.BigEndianUnicode)
106106
return new CustomType("[MarshalAs(UnmanagedType.LPWStr)] string");
107107
else if (enconding == Encoding.UTF32)

src/Package/CppSharp.Package.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ It can also be used as a library to parse native code into a syntax tree with a
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="System.CodeDom" />
1817
<PackageReference Include="Microsoft.Win32.Registry" />
1918
<PackageReference Include="Microsoft.VisualStudio.Setup.Configuration.Interop" />
2019
<Content Include="$(PackageDir)runtimes\**" PackagePath="runtimes" />

src/Runtime/UTF8Marshaller.cs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.Text;
4+
5+
namespace CppSharp.Runtime
6+
{
7+
// HACK: .NET Standard 2.0 which we use in auto-building to support .NET Framework, lacks UnmanagedType.LPUTF8Str
8+
public class UTF8Marshaller : ICustomMarshaler
9+
{
10+
public void CleanUpManagedData(object ManagedObj)
11+
{
12+
}
13+
14+
public void CleanUpNativeData(IntPtr pNativeData)
15+
=> Marshal.FreeHGlobal(pNativeData);
16+
17+
public int GetNativeDataSize() => -1;
18+
19+
public IntPtr MarshalManagedToNative(object managedObj)
20+
{
21+
if (managedObj == null)
22+
return IntPtr.Zero;
23+
if (!(managedObj is string))
24+
throw new MarshalDirectiveException(
25+
"UTF8Marshaler must be used on a string.");
26+
27+
// not null terminated
28+
byte[] strbuf = Encoding.UTF8.GetBytes((string) managedObj);
29+
IntPtr buffer = Marshal.AllocHGlobal(strbuf.Length + 1);
30+
Marshal.Copy(strbuf, 0, buffer, strbuf.Length);
31+
32+
// write the terminating null
33+
Marshal.WriteByte(buffer + strbuf.Length, 0);
34+
return buffer;
35+
}
36+
37+
public unsafe object MarshalNativeToManaged(IntPtr str)
38+
{
39+
if (str == IntPtr.Zero)
40+
return null;
41+
42+
int byteCount = 0;
43+
var str8 = (byte*) str;
44+
while (*(str8++) != 0) byteCount += sizeof(byte);
45+
46+
return Encoding.UTF8.GetString((byte*) str, byteCount);
47+
}
48+
49+
public static ICustomMarshaler GetInstance(string pstrCookie)
50+
{
51+
if (marshaler == null)
52+
marshaler = new UTF8Marshaller();
53+
return marshaler;
54+
}
55+
56+
private static UTF8Marshaller marshaler;
57+
}
58+
}

tests/NamespacesBase/NamespacesBase.CSharp.csproj

-12
This file was deleted.

tests/NamespacesBase/premake4.lua

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ end
88

99
group "Tests/Namespaces"
1010
SetupTestNativeProject("NamespacesBase")
11-
targetdir (path.join(gendir, "NamespacesDerived"))
12-
SetupWrapper("NamespacesBase")
11+
targetdir (path.join(gendir, "NamespacesDerived"))

tests/NamespacesDerived/NamespacesDerived.CSharp.csproj

-14
This file was deleted.

tests/NamespacesDerived/NamespacesDerived.Gen.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.IO;
2-
using System.Reflection;
32
using CppSharp.AST;
43
using CppSharp.Generators;
54
using CppSharp.Utils;
@@ -18,6 +17,7 @@ public override void Setup(Driver driver)
1817
base.Setup(driver);
1918
driver.Options.GenerateDefaultValuesForArguments = true;
2019
driver.Options.GenerateClassTemplates = true;
20+
driver.Options.CompileCode = true;
2121
driver.Options.DependentNameSpaces.Add("System.Runtime.CompilerServices");
2222
driver.Options.Modules[1].IncludeDirs.Add(GetTestsDirectory("NamespacesDerived"));
2323
var @base = "NamespacesBase";
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
1-
<Project Sdk="Microsoft.NET.Sdk" />
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<ItemGroup>
3+
<ProjectReference Include="NamespacesDerived.Gen.csproj" ReferenceOutputAssembly="false" />
4+
</ItemGroup>
5+
<ItemGroup>
6+
<Reference Include="NamespacesBase">
7+
<HintPath>..\..\build\gen\NamespacesDerived\NamespacesBase.dll</HintPath>
8+
</Reference>
9+
<Reference Include="NamespacesDerived">
10+
<HintPath>..\..\build\gen\NamespacesDerived\NamespacesDerived.dll</HintPath>
11+
</Reference>
12+
</ItemGroup>
13+
</Project>

tests/NamespacesDerived/premake4.lua

+1-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,4 @@ group "Tests/Namespaces"
66
end
77

88
SetupTestGeneratorProject("NamespacesDerived")
9-
SetupTestProjectsCSharp("NamespacesDerived", "NamespacesBase")
10-
11-
project("NamespacesDerived.Tests.CSharp")
12-
links { "NamespacesBase.CSharp" }
9+
SetupTestProjectsCSharp("NamespacesDerived", "NamespacesBase")

tests/Test.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</ItemGroup>
1010

1111
<ItemGroup>
12-
<ProjectReference Include="$(TestName).CSharp.csproj" Condition="$(MSBuildProjectName.EndsWith('CSharp'))" />
12+
<ProjectReference Include="$(TestName).CSharp.csproj" Condition="$(MSBuildProjectName.EndsWith('CSharp')) AND $(TestName) != 'NamespacesDerived'" />
1313
<ProjectReference Include="$(NativeProjectsDir)$(TestName).Native.vcxproj" Condition="$(IsWindows)" ReferenceOutputAssembly="false" />
1414
<ProjectReference Include="$(NativeProjectsDir)$(TestName).CLI.vcxproj" Condition="$(MSBuildProjectName.EndsWith('CLI')) AND $(IsWindows)" />
1515
</ItemGroup>

0 commit comments

Comments
 (0)