-
Notifications
You must be signed in to change notification settings - Fork 189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement projection tooling for simple P/Invoke cases #2517
Changes from 6 commits
ab56290
49bd30d
8a1a04f
1876c1a
1345f05
65dda63
56e96bc
9a1753c
cad936e
61aeb9e
002ac6a
5ddfc20
a352696
91dd9fd
0b3f817
1da30bd
283938a
7262401
aef5b02
7587908
799187f
d23735a
b8457c9
59a5a65
2866a2e
ac3f569
62b93c7
69ef2e5
99834f5
4856a8e
4ba46cb
497cbe1
aed517c
442d9ae
0574a8b
919f7ce
276918e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using CommandLine; | ||
using SwiftReflector; | ||
|
||
namespace SwiftBindings | ||
{ | ||
public class Options | ||
{ | ||
[Option('d', "dylibs", Required = true, Separator = ',', HelpText = "Paths to the dynamic libraries (dylibs), separated by commas.")] | ||
public IEnumerable<string> DylibPaths { get; set; } | ||
|
||
[Option('s', "swiftinterfaces", Required = true, Separator = ',', HelpText = "Paths to the Swift interface files, separated by commas.")] | ||
public IEnumerable<string> SwiftInterfacePaths { get; set; } | ||
|
||
[Option('o', "output", Required = true, HelpText = "Output directory for generated bindings.")] | ||
public string OutputDirectory { get; set; } | ||
|
||
[Option('h', "help", HelpText = "Display this help message.")] | ||
public bool Help { get; set; } | ||
} | ||
|
||
public class BindingsTool | ||
{ | ||
public static void Main(string[] args) | ||
{ | ||
Parser.Default.ParseArguments<Options>(args).WithParsed(options => | ||
{ | ||
if (options.Help) | ||
{ | ||
Console.WriteLine("Usage:"); | ||
Console.WriteLine(" -d, --dylibs Paths to the dynamic libraries (dylibs), separated by commas."); | ||
Console.WriteLine(" -s, --swiftinterfaces Paths to the Swift interface files, separated by commas."); | ||
Console.WriteLine(" -o, --output Output directory for generated bindings."); | ||
return; | ||
} | ||
|
||
if (options.DylibPaths == null || options.SwiftInterfacePaths == null || options.OutputDirectory == null) | ||
{ | ||
Console.WriteLine("Error: Missing required argument(s)."); | ||
return; | ||
} | ||
|
||
if (options.DylibPaths.Count() != options.SwiftInterfacePaths.Count()) | ||
{ | ||
Console.WriteLine("Error: Number of dylibs, interface files, and modules must match."); | ||
return; | ||
} | ||
|
||
if (!Directory.Exists(options.OutputDirectory)) | ||
{ | ||
Console.WriteLine($"Error: Directory '{options.OutputDirectory}' doesn't exist."); | ||
return; | ||
} | ||
|
||
for (int i = 0; i < options.DylibPaths.Count(); i++) | ||
{ | ||
string dylibPath = options.DylibPaths.ElementAt(i); | ||
string swiftInterfacePath = options.SwiftInterfacePaths.ElementAt(i); | ||
|
||
if (!File.Exists(dylibPath)) | ||
{ | ||
Console.WriteLine($"Error: Dynamic library not found at path '{dylibPath}'."); | ||
return; | ||
} | ||
|
||
if (!File.Exists(swiftInterfacePath)) | ||
{ | ||
Console.WriteLine($"Error: Swift interface file not found at path '{swiftInterfacePath}'."); | ||
return; | ||
} | ||
|
||
GenerateBindings(dylibPath, swiftInterfacePath, options.OutputDirectory); | ||
} | ||
}); | ||
} | ||
|
||
public static void GenerateBindings(string dylibPath, string swiftInterfacePath, string outputDirectory) | ||
{ | ||
BindingsCompiler bindingsCompiler = new BindingsCompiler(); | ||
var errors = new ErrorHandling (); | ||
var moduleInventory = bindingsCompiler.GetModuleInventory(dylibPath, errors); | ||
var moduleDeclarations = bindingsCompiler.GetModuleDeclarations(swiftInterfacePath); | ||
bindingsCompiler.CompileModules(moduleDeclarations, moduleInventory, dylibPath, outputDirectory, errors); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,25 @@ | ||||||
cmake_minimum_required(VERSION 3.24) | ||||||
|
||||||
project(SmokeTests) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the Swift language support in CMake and require using a generator that supports it (ie. Ninja). As this code isn't going in the runtime repo, we can be more reasonable about requiring specific features/options.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Nit: We use our build images. This link is for GitHub Actions images - I do not think it is relevant to our builds. |
||||||
|
||||||
set(SOURCE SmokeTests) | ||||||
|
||||||
if (NOT SWIFT_COMPILER_TARGET) | ||||||
set(SWIFT_PLATFORM "macosx") | ||||||
if (NOT DEFINED CMAKE_OSX_ARCHITECTURES OR CMAKE_OSX_ARCHITECTURES STREQUAL "") | ||||||
set(CMAKE_OSX_ARCHITECTURES "arm64") | ||||||
endif() | ||||||
set(SWIFT_PLATFORM_SUFFIX "11") | ||||||
set(SWIFT_DEPLOYMENT_TARGET ${CMAKE_OSX_DEPLOYMENT_TARGET}) | ||||||
set(SWIFT_COMPILER_TARGET "${CMAKE_OSX_ARCHITECTURES}-apple-${SWIFT_PLATFORM}${SWIFT_DEPLOYMENT_TARGET}${SWIFT_PLATFORM_SUFFIX}") | ||||||
endif() | ||||||
|
||||||
add_custom_target(${SOURCE} ALL | ||||||
COMMAND xcrun swiftc -target ${SWIFT_COMPILER_TARGET} -emit-module -emit-library -enable-library-evolution -emit-module-interface ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift -o ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib | ||||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift | ||||||
COMMENT "Generating ${SOURCE} library" | ||||||
) | ||||||
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib | ||||||
DESTINATION bin | ||||||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,37 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using Xunit; | ||
|
||
namespace SwiftBindings.Tests | ||
{ | ||
public class MyClassTests | ||
public class SmokeTests | ||
{ | ||
[Fact] | ||
public void Test1() | ||
public void DirectPInvokeVoidVoid() | ||
{ | ||
Assert.True(MyClass.ReturnTrue); | ||
BindingsTool.GenerateBindings("libSmokeTests.dylib", "SmokeTests.swiftinterface", ""); | ||
var sourceCode = """ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using SmokeTests; | ||
|
||
namespace Hello { | ||
class MainClass { | ||
public static int Main(string[] args) | ||
{ | ||
TopLevelEntities.SimplePinvokeVoidVoid(); | ||
return 42; | ||
} | ||
} | ||
} | ||
"""; | ||
|
||
int result = TestsHelper.CompileAndExecuteFromFileAndString("TopLevelEntitiesSmokeTests.cs", sourceCode); | ||
Assert.Equal(42, result); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
public func SimplePinvokeVoidVoid() | ||
{ | ||
print("Hello world"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<NativeLibsCommand>cd $(OutputPath)$(TargetFramework) && cmake $(MSBuildThisFileDirectory) && make</NativeLibsCommand> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\src\SwiftBindings.csproj" /> | ||
<ProjectReference Include="../src/SwiftBindings.csproj" /> | ||
|
||
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" /> | ||
</ItemGroup> | ||
|
||
<Target Name="CompileNativeLibs" AfterTargets="Build"> | ||
<Exec Command="$(NativeLibsCommand)" /> | ||
</Target> | ||
</Project> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.Emit; | ||
|
||
namespace SwiftBindings.Tests | ||
{ | ||
public static class TestsHelper | ||
{ | ||
public static int CompileAndExecuteFromFileAndString(string filePath, string sourceCode) | ||
{ | ||
string fileSourceCode = File.ReadAllText(filePath); | ||
var sourceCodes = new[] { fileSourceCode, sourceCode }; | ||
return CompileAndExecute(sourceCodes); | ||
} | ||
|
||
private static int CompileAndExecute(string[] sourceCodes) | ||
{ | ||
var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication); | ||
var syntaxTrees = sourceCodes.Select(code => CSharpSyntaxTree.ParseText(code)).ToArray(); | ||
|
||
var references = new[] | ||
{ | ||
MetadataReference.CreateFromFile(typeof(object).Assembly.Location), | ||
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), | ||
}; | ||
|
||
|
||
var compilation = CSharpCompilation.Create("CompiledAssembly", | ||
syntaxTrees: syntaxTrees, | ||
references: references, | ||
options: options); | ||
|
||
string assemblyPath = Path.Combine(Path.GetTempPath(), "CompiledAssembly.dll"); | ||
using (var stream = new FileStream(assemblyPath, FileMode.Create)) | ||
{ | ||
EmitResult emitResult = compilation.Emit(stream); | ||
|
||
if (!emitResult.Success) | ||
{ | ||
string errorMessage = "Compilation failed:"; | ||
foreach (var diagnostic in emitResult.Diagnostics) | ||
{ | ||
errorMessage += $"\n{diagnostic}"; | ||
} | ||
throw new InvalidOperationException(errorMessage); | ||
} | ||
} | ||
|
||
Assembly compiledAssembly = Assembly.LoadFile(assemblyPath); | ||
|
||
MethodInfo entryPoint = compiledAssembly.EntryPoint; | ||
object[] args = entryPoint.GetParameters().Length == 0 ? null : new object[] { new string[0] }; | ||
int result = (int)entryPoint.Invoke(null, args); | ||
|
||
return result; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original design was to operate on multiple libraries, but frankly, this made things worse overall. I feel like we should simplify and make it take only a single dylib/swiftinterface pair.