Skip to content
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

Closed
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ab56290
Add SwiftRuntimeLibrary project
kotlarmilos Feb 14, 2024
49bd30d
Add SyntaxDynamo project
kotlarmilos Feb 14, 2024
8a1a04f
Add SwiftReflector project
kotlarmilos Feb 14, 2024
1876c1a
Add SwiftBindings project
kotlarmilos Feb 14, 2024
1345f05
Add basic testing coverage
kotlarmilos Feb 14, 2024
65dda63
Fix sample application
kotlarmilos Feb 14, 2024
56e96bc
Remove Swift4 demangler
kotlarmilos Feb 16, 2024
9a1753c
Update the BindingsTool class to use System.CommandLine
kotlarmilos Feb 16, 2024
cad936e
Remove SyntaxDynamo.SwiftLang files
kotlarmilos Feb 16, 2024
61aeb9e
Update README.md
kotlarmilos Feb 16, 2024
002ac6a
Remove Swift4 support
kotlarmilos Feb 16, 2024
5ddfc20
Rename verbose argument
kotlarmilos Feb 16, 2024
a352696
Update src/SwiftBindings/tests/CMakeLists.txt
kotlarmilos Feb 16, 2024
91dd9fd
Use ninja generator for cmake
kotlarmilos Feb 16, 2024
0b3f817
Fix lint formatting
kotlarmilos Feb 16, 2024
1da30bd
Update bindings compiler to use a module name and emit pinvokes and s…
kotlarmilos Feb 16, 2024
283938a
Update Microsoft.DotNet.Arcade.Sdk version
kotlarmilos Feb 16, 2024
7262401
Add .sln file
kotlarmilos Feb 19, 2024
aef5b02
Add nuget.org as package source
kotlarmilos Feb 19, 2024
7587908
Remove ninja generator to test the CI
kotlarmilos Feb 19, 2024
799187f
Revert temporary changes
kotlarmilos Feb 19, 2024
d23735a
Update .gitignore
kotlarmilos Feb 19, 2024
b8457c9
Use cmake instead of ninja
kotlarmilos Feb 19, 2024
59a5a65
Use macOS vmImage
kotlarmilos Feb 19, 2024
2866a2e
Don't override SWIFT_PLATFORM_SUFFIX if set
kotlarmilos Feb 19, 2024
ac3f569
Fix SWIFT_COMPILER_TARGET variable
kotlarmilos Feb 19, 2024
62b93c7
Use cmake instead of ninja
kotlarmilos Feb 19, 2024
69ef2e5
Introduce Swift types database
kotlarmilos Feb 19, 2024
99834f5
Temporarily remove SwiftInterfaceReflector from the project
kotlarmilos Feb 21, 2024
4856a8e
Refactor null exceptions to use ArgumentNullException.ThrowIfNull
kotlarmilos Feb 21, 2024
4ba46cb
Update target framework to net8.0
kotlarmilos Feb 21, 2024
497cbe1
Add src and tests directories for projects
kotlarmilos Feb 21, 2024
aed517c
Introduce PInvoke tests
kotlarmilos Feb 22, 2024
442d9ae
Add uniqueId to CompiledAssembly name
kotlarmilos Feb 22, 2024
0574a8b
Update runtimelab.yml pipeline configuration
kotlarmilos Feb 22, 2024
919f7ce
Change osGroup from Linux to OSX in runtimelab.yml
kotlarmilos Feb 22, 2024
276918e
Add sample bindings to .gitignore
kotlarmilos Feb 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
TargetingPackVersion="$(MicrosoftNETCoreAppVersion)" />
</ItemGroup>

<Import Condition="'$(IsTestProject)' == 'true'" Project="$(RepositoryEngineeringDir)testing\runsettings.targets" />
<!-- <Import Condition="'$(IsTestProject)' == 'true'" Project="$(RepositoryEngineeringDir)testing\runsettings.targets" /> -->

<Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
</Project>
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

This is a collection of tools designed to consume a compiled Apple Swift library and generate bindings and wrappers that enable it to be used as a .NET library.

## Current Status

The components are currently undergoing iterative reviews and will be moved from https://github.com/xamarin/binding-tools-for-swift.
## Installation and usage

The tool will be available as a NuGet CLI package in the [`dotnet-experimental`](https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-experimental) feed.

Options:
```
-d, --dylibs Required. Paths to the dynamic libraries (dylibs), separated by commas.

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.

-s, --swiftinterfaces Required. Paths to the Swift interface files, separated by commas.
-o, --output Required. Output directory for generated bindings.
-h, --help Display this help message.
--version Display version information.
```

## .NET Foundation

Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<UsingToolXliff>false</UsingToolXliff>
<!-- Libs -->
<MicrosoftNETTestSdkVersion>16.7.1</MicrosoftNETTestSdkVersion>
<XUnitVersion>2.4.1</XUnitVersion>
<XUnitVersion>2.4.2</XUnitVersion>
<XUnitRunnerVisualStudioVersion>2.4.3</XUnitRunnerVisualStudioVersion>
<!-- Set the custom NETCoreApp version -->
<MicrosoftNETCoreAppVersion>7.0.0-preview.7.22375.6</MicrosoftNETCoreAppVersion>
Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"sdk": {
"version": "8.0.100",
"version": "7.0.100",
"allowPrerelease": true,
"rollForward": "major"
},
"tools": {
"dotnet": "8.0.100"
"dotnet": "7.0.100"
},
"msbuild-sdks": {
"Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24102.4"
Expand Down
13 changes: 0 additions & 13 deletions src/SwiftBindings/src/MyClass.cs

This file was deleted.

88 changes: 88 additions & 0 deletions src/SwiftBindings/src/Program.cs
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);
}
}
}
10 changes: 8 additions & 2 deletions src/SwiftBindings/src/SwiftBindings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../SwiftReflector/SwiftReflector.csproj" />
<ProjectReference Include="../../SyntaxDynamo/SyntaxDynamo.csproj" />
<ProjectReference Include="../../SwiftRuntimeLibrary/SwiftRuntimeLibrary.csproj" />

<PackageReference Include="CommandLineParser" Version="2.9.1" />
kotlarmilos marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>
</Project>
25 changes: 25 additions & 0 deletions src/SwiftBindings/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.24)

project(SmokeTests)
Copy link
Member

Choose a reason for hiding this comment

The 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
project(SmokeTests)
project(SmokeTests Swift)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the ninja generator is not installed on build machines: https://github.com/actions/runner-images/blob/macOS-12/20240202.1/images/macos/macos-12-Readme.md

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/actions/runner-images/blob/macOS-12/20240202.1/images/macos/macos-12-Readme.md

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
)
29 changes: 26 additions & 3 deletions src/SwiftBindings/tests/SmokeTests.cs
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);
}
}
}
7 changes: 7 additions & 0 deletions src/SwiftBindings/tests/SmokeTests.swift
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");
}
14 changes: 11 additions & 3 deletions src/SwiftBindings/tests/SwiftBindings.Tests.csproj
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) &amp;&amp; cmake $(MSBuildThisFileDirectory) &amp;&amp; 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>

65 changes: 65 additions & 0 deletions src/SwiftBindings/tests/TestsHelper.cs
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;
}
}
}
Loading