diff --git a/README.md b/README.md index 730131bd..05ed8d7d 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,41 @@ # ClangSharp -ClangSharp provides Clang bindings written in C#. It is self-hosted and auto-generates itself parsing the Clang C header files. +ClangSharp provides Clang bindings written in C#. It is self-hosted and auto-generates itself by parsing the Clang C header files using ClangSharpPInvokeGenerator. | Job | Debug Status | Release Status | | --- | ------------ | -------------- | | Windows x86 | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=windows_debug_x86)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=windows_release_x86)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | | Windows x64 | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=windows_debug_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=windows_release_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | -| Ubuntu 16.04 x64 | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=ubuntu_debug_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=ubuntu_release_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | +| Ubuntu 18.04 x64 | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=ubuntu_debug_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=ubuntu_release_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | | MacOS x64 | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=macos_debug_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | [![Build Status](https://dev.azure.com/ms/ClangSharp/_apis/build/status/microsoft.ClangSharp?branchName=master&jobName=macos_release_x64)](https://dev.azure.com/ms/ClangSharp/_build/latest?definitionId=155&branchName=master) | -A nuget package for the project is provided here: https://www.nuget.org/packages/clangsharp. NOTE: This is currently out of date as compared to the current sources and we hope to have a new package published soon. +A nuget package for the project is provided here: https://www.nuget.org/packages/clangsharp. +A .NET tool for the P/Invoke generator project is provided here: https://www.nuget.org/packages/ClangSharpPInvokeGenerator -A convenience package which provides the native libclang library for several platforms is provided here: https://www.nuget.org/packages/libclang +A convenience package which provides the native libClang library for several platforms is provided here: https://www.nuget.org/packages/libclang +A helper package which exposes many Clang APIs missing from libClang is provided here: https://www.nuget.org/packages/libClangSharp -## Building ClangSharp +NOTE: These may be out of date as compared to the latest sources. New versions are published as appropriate and a nightly feed is not currently available. -ClangSharp provides several build scripts in the repository root. On Windows these scripts end with `.cmd` and expect arguments with a `-` prefix. On Unix these scripts end with `.sh` and expect arguments with a `--` prefix. +## Table of Contents -By default, each script performs only the action specified in its name (i.e. `restore` only restores, `build` only builds, `test` only tests, and `pack` only packs). You can specify additional actions to be run by passing that name as an argument to the script (e.g. `build.cmd -restore` will perform a package restore and build; `test.cmd -pack` will run tests and package artifacts). +* [Microsoft Open Source Code of Conduct](#microsoft-open-source-code-of-conduct) +* [License](#license) +* [Features](#features) +* [Building Managed](#building-managed) +* [Building Native](#building-native) +* [Generating Bindings](#generating-bindings) +* [Spotlight](#spotlight) -Certain actions are dependent on a previous action having been run at least once. `build` depends on `restore`, `test` depends on `build`, and `pack` depends on `build`. This means the recommended first time action is `build -restore`. +## Microsoft Open Source Code of Conduct -You can reproduce what the CI environment does by running `./scripts/cibuild.cmd` on Windows or `./scripts.cibuild.sh` on Unix. This will download the required .NET SDK locally and use that to build the repo. It will also run through all available actions in the appropriate order. +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -You can see any additional options that are available by passing `-help` on Windows or `--help` on Unix to the available build scripts. +### License + +Copyright (c) Microsoft and Contributors. All rights reserved. +Licensed under the University of Illinois/NCSA Open Source License. +See [LICENSE.txt](LICENSE.txt) in the project root for license information. ## Features @@ -33,12 +45,197 @@ You can see any additional options that are available by passing `-help` on Wind * Exposes an again slightly higher abstraction that tries to mirror the Clang C++ Type Hierarchy where possible * Nearly identical to the Clang C APIs, e.g. `clang_getDiagnosticSpelling` in C, vs. `clang.getDiagnosticSpelling` (notice the . in the C# API) -## ClangSharp P/Invoke Binding Generator +## Building Managed -A great example of ClangSharp's use case is its self-hosting mechanism: [ClangSharp P/Invoke Binding Generator](sources/ClangSharpPInvokeGenerator). +ClangSharp requires the [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) and can be built simply with `dotnet build -c Release`. -This program will take a given set of C or C++ header files and generate C# bindings from them. It is still a work-in-progress and not every declaration can have bindings generated today (contributions are welcome). +You can reproduce what the CI environment does by running `./scripts/cibuild.cmd` on Windows or `./scripts.cibuild.sh` on Unix. +This will download the required .NET SDK locally and use that to build the repo; it will also run through all available actions in the appropriate order. -## Microsoft Open Source Code of Conduct +There are also several build scripts in the repository root. On Windows these scripts end with `.cmd` and expect arguments with a `-` prefix. On Unix these scripts end with `.sh` and expect arguments with a `--` prefix. +By default, each script performs only the action specified in its name (i.e. `restore` only restores, `build` only builds, `test` only tests, and `pack` only packs). You can specify additional actions to be run by passing that name as an argument to the script (e.g. `build.cmd -restore` will perform a package restore and build; `test.cmd -pack` will run tests and package artifacts). +Certain actions are dependent on a previous action having been run at least once. `build` depends on `restore`, `test` depends on `build`, and `pack` depends on `build`. This means the recommended first time action is `build -restore`. -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +You can see any additional options that are available by passing `-help` on Windows or `--help` on Unix to the available build scripts. + +## Building Native + +ClangSharp provides a helper library, `libClangSharp`, that exposes additional functionality that is not available in `libClang`. +Building this requires [CMake 3.13 or later](https://cmake.org/download/) as well as a version of MSVC or Clang that supports C++ 17. + +To succesfully build `libClangSharp` you must first build Clang (https://clang.llvm.org/get_started.html). The process done on Windows is roughly: +```cmd +git clone https://github.com/llvm/llvm-project +cd llvm-project +mkdir artifacts/bin +cd artifacts/bin +cmake -DLLVM_ENABLE_PROJECTS=clang -DCMAKE_INSTALL_PREFIX=../install -G "Visual Studio 16 2019" -A x64 -Thost=x64 ../../llvm +``` + +You can then open `LLVM.sln` in Visual Studio, change the configuration to `Release` and build the `install` project. +Afterwards, you can then build `libClangSharp` where the process followed is roughly: +```cmd +git clone https://github.com/microsoft/clangsharp +mkdir artifacts/bin/native +cd artifacts/bin/native +cmake -DPATH_TO_LLVM=../../../../llvm-project/artifacts/install/ -G "Visual Studio 16 2019" -A x64 -Thost=x64 ../../.. +``` + +You can then open `libClangSharp.sln` in Visual Studio, change the configuration to `Release` and build the `ALL_BUILD` project. + +## Generating Bindings + +This program will take a given set of C or C++ header files and generate C# bindings from them. It is still a work-in-progress and not every declaration can have bindings generated today (contributions are welcome). + +The simplest and recommended setup is to install the generator as a .NET tool and then use response files: +``` +dotnet tool install --global ClangSharpPInvokeGenerator --version 11.0.0-beta2 +ClangSharpPInvokeGenerator @generate.rsp +``` + +A response file allows you to specify and checkin the command line arguments in a text file, with one argument per line. For example: https://github.com/microsoft/ClangSharp/blob/master/sources/ClangSharpPInvokeGenerator/Properties/GenerateClang.rsp +At a minimum, the command line expects one or more input files (`-f`), an output namespace (`-n`), and an output location (`-o`). A typical response file may also specify explicit files to traverse, configuration options, name remappings, and other fixups. + +The full set of available switches: +``` +ClangSharpPInvokeGenerator: + ClangSharp P/Invoke Binding Generator + +Usage: + ClangSharpPInvokeGenerator [options] + +Options: + -a, --additional <> An argument to pass to Clang when parsing the input files. + [default: System.String[]] + -c, --config <> Aconfiguration option that controls how the bindings are + generated. Specify 'help' to see the available options. + [default: System.String[]] + -D, --define-macro <=> Define to (or 1 if omitted). + [default: System.String[]] + -e, --exclude <> Adeclaration name to exclude from binding generation. + [default: System.String[]] + -f, --file <> Afile to parse and generate bindings for. [default: + System.String[]] + -F, --file-directory <> The base path for files to parse. [default: ] + -h, --headerFile <> Afile which contains the header to prefix every generated + file with. [default: ] + -I, --include-directory <> Add directory to include search path. [default: + System.String[]] + -x, --language <> Treat subsequent input files as having type . + [default: c++] + -l, --libraryPath <> The string to use in the DllImport attribute used when + generating bindings. [default: ] + -m, --methodClassName <> The name of the static class that will contain the generated + method bindings. [default: Methods] + -n, --namespace <> The namespace in which to place the generated bindings. + [default: ] + -o, --output <> The output location to write the generated bindings to. + [default: ] + -p, --prefixStrip <> The prefix to strip from the generated method bindings. + [default: ] + -r, --remap <=> Adeclaration name to be remapped to another name during + binding generation. [default: System.String[]] + -std <> Language standard to compile for. [default: c++17] + -to, --test-output <> The output location to write the generated tests to. + [default: ] + -t, --traverse <> Afile name included either directly or indirectly by -f that + should be traversed during binding generation. [default: + System.String[]] + -wa, --with-attribute <=> An attribute to be added to the given remapped declaration + name during binding generation. [default: System.String[]] + -wcc, --with-callconv <=> Acalling convention to be used for the given declaration + during binding generation. [default: System.String[]] + -wlb, --with-librarypath <=> Alibrary path to be used for the given declaration during + binding generation. [default: System.String[]] + -wsle, --with-setlasterror <=> Add the SetLastError=true modifier to a given DllImport or + UnmanagedFunctionPointer. [default: System.String[]] + -wt, --with-type <=> Atype to be used for the given enum declaration during + binding generation. [default: System.String[]] + -wu, --with-using <=> Ausing directive to be included for the given remapped + declaration name during binding generation. [default: + System.String[]] + -om, --output-mode The mode describing how the information collected from the + headers are presented in the resultant bindings. [default: + CSharp] + --version Show version information + -?, -h, --help Show help and usage information +``` + +The available configuration options (visible with `-c help`) are: +``` +-c, --config <> A configuration option that controls how the bindings are generated. Specify 'help' to see the available options. + +Options: + ?, h, help Show help and usage information for -c, --config + + compatible-codegen Bindings should be generated with .NET Standard 2.0 compatibility. Setting + this disables preview code generation. + latest-codegen Bindings should be generated for the latest stable version of .NET/C#. This + is currently .NET 5/C# 9. + preview-codegen Bindings should be generated for the latest preview version of .NET/C#. + This is currently .NET 6/C# 10. + + single-file Bindings should be generated to a single output file. This is the default. + multi-file Bindings should be generated so there is approximately one type per file. + + unix-types Bindings should be generated assuming Unix defaults. This is the default on + Unix platforms. + windows-types Bindings should be generated assuming Windows defaults. This is the default + on Windows platforms. + + exclude-anonymous-field-helpers The helper ref properties generated for fields in nested anonymous structs + and unions should not be generated. + exclude-com-proxies Types recognized as COM proxies should not have bindings generated. Thes + are currently function declarations ending with _UserFree, _UserMarshal, + _UserSize, _UserUnmarshal, _Proxy, or _Stub. + exclude-default-remappings Default remappings for well known types should not be added. This currently + includes intptr_t, ptrdiff_t, size_t, and uintptr_t + exclude-empty-records Bindings for records that contain no members should not be generated. These + are commonly encountered for opaque handle like types such as HWND. + exclude-enum-operators Bindings for operators over enum types should not be generated. These are + largely unnecessary in C# as the operators are available by default. + exclude-funcs-with-body Bindings for functions with bodies should not be generated. + exclude-using-statics-for-enums Enum usages should be fully qualified and should not include a + corresponding 'using static EnumName;' + + explicit-vtbls VTBLs should have an explicit type generated with named fields per entry. + implicit-vtbls VTBLs should be implicit to reduce metadata bloat. This is the current + default + + generate-tests-nunit Basic tests validating size, blittability, and associated metadata should + be generated for NUnit. + generate-tests-xunit Basic tests validating size, blittability, and associated metadata should + be generated for XUnit. + + generate-aggressive-inlining [MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to + generated helper functions. + generate-cpp-attributes A[CppAttributeList("")] should be generated to document the encountered C++ + attributes. + generate-macro-bindings Bindings for macro-definitions should be generated. This currently only + works with value like macros and not function-like ones. + generate-native-inheritance-attribute A[NativeInheritance("")] attribute should be generated to document the + encountered C++ base type. + + log-exclusions Alist of excluded declaration types should be generated. This will also log + if the exclusion was due to an exact or partial match. + log-potential-typedef-remappings Alist of potential typedef remappings should be generated. This can help + identify missing remappings. + log-visited-files Alist of the visited files should be generated. This can help identify + traversal issues. + + preview-codegen-fnptr Generated bindings should use function pointers instead of IntPtr where + possible. + preview-codegen-nint Generated bindings should use nint and nuint instead of IntPtr and UIntPtr + where possible. +``` + +## Spotlight + +The P/Invoke Generator is currently used by several projects: + +* [microsoft/clangsharp](https://github.com/microsoft/clangsharp) - ClangSharp is self-hosting +* [microsoft/llvmsharp](https://github.com/microsoft/llvmsharp) - Bindings over libLLVM +* [microsoft/win32metadata](https://github.com/microsoft/win32metadata) - Bindings over the Windows SDK meant for downstream use by projects such as CsWin32, RsWin32, etc +* [terrafx/terrafx.interop.windows](https://github.com/terrafx/terrafx.interop.windows) - Bindings for D3D10, D3D11, D3D12, D2D1, DWrite, WIC, User32, and more in a single NuGet +* [terrafx/terrafx.interop.vulkan](https://github.com/terrafx/terrafx.interop.vulkan) - Bindings for Vulkan +* [terrafx/terrafx.interop.xlib](https://github.com/terrafx/terrafx.interop.xlib) - Bindings for Xlib diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index a9f4faff..d7d10bc1 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -937,7 +937,7 @@ void ForFunctionDecl(ParmVarDecl parmVarDecl, FunctionDecl functionDecl) Name = escapedName, Type = typeName, NativeTypeName = nativeTypeName, - CppAttributes = _config.GenerateNativeInheritanceAttribute + CppAttributes = _config.GenerateCppAttributes ? parmVarDecl.Attrs.Select(x => EscapeString(x.Spelling)) : null, CustomAttrGeneratorData = (name, this), @@ -983,7 +983,7 @@ void ForTypedefDecl(ParmVarDecl parmVarDecl, TypedefDecl typedefDecl) Name = escapedName, Type = typeName, NativeTypeName = nativeTypeName, - CppAttributes = _config.GenerateNativeInheritanceAttribute + CppAttributes = _config.GenerateCppAttributes ? parmVarDecl.Attrs.Select(x => EscapeString(x.Spelling)) : null, CustomAttrGeneratorData = (name, this), diff --git a/sources/ClangSharpPInvokeGenerator/CustomHelpBuilder.cs b/sources/ClangSharpPInvokeGenerator/CustomHelpBuilder.cs new file mode 100644 index 00000000..9cf3d433 --- /dev/null +++ b/sources/ClangSharpPInvokeGenerator/CustomHelpBuilder.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft and Contributors. All rights reserved. Licensed under the University of Illinois/NCSA Open Source License. See LICENSE.txt in the project root for license information. + +// Portions of this code are ported from https://github.com/dotnet/command-line-api +// The original source is Copyright © .NET Foundation and Contributor. All rigts reserved. Licensed under the MIT License (MIT). + +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Help; +using System.CommandLine.IO; +using System.Linq; + +namespace ClangSharp +{ + internal sealed class CustomHelpBuilder : HelpBuilder + { + public CustomHelpBuilder(IConsole console, int? columnGutter = null, int? indentationSize = null, int? maxWidth = null) + : base(console, columnGutter, indentationSize, maxWidth) + { + } + + public void Write(IReadOnlyCollection<(string Name, string Description)> options) + { + if (options.Count == 0) + { + return; + } + + AppendHeading("Options:"); + Indent(); + + var table = CreateTable(options, (option) => new string[2] { + option.Name, + option.Description, + }); + + var columnWidths = ColumnWidths(table); + AppendTable(table, columnWidths); + + Outdent(); + AppendBlankLine(); + } + + private void AppendBlankLine() + { + Console.Out.WriteLine(); + } + + private void AppendHeading(string heading) + { + if (heading is null) + { + throw new ArgumentNullException(nameof(heading)); + } + + AppendLine(heading); + } + + private void AppendLine(string text, int? offset = null) + { + if (string.IsNullOrEmpty(text)) + { + Console.Out.WriteLine(); + return; + } + + AppendPadding(offset); + Console.Out.WriteLine(text); + } + + private void AppendPadding(int? offset = null) + { + string padding = GetPadding(offset ?? CurrentIndentation); + Console.Out.Write(padding); + } + + private void AppendRow(IEnumerable row, IReadOnlyList columnWidths) + { + var array = row.Select((string element, int index) => SplitText(element, columnWidths[index])).ToArray(); + int num = array.Max((IReadOnlyList lines) => lines.Count); + + for (int i = 0; i < num; i++) + { + int num2 = 0; + int num3 = 0; + + AppendPadding(CurrentIndentation); + + for (int j = 0; j < array.Length; j++) + { + var readOnlyList = array[j]; + + if (i < readOnlyList.Count) + { + string text = readOnlyList[i]; + + if (!string.IsNullOrEmpty(text)) + { + int num4 = num2 - num3; + AppendText(text, num4); + num3 += num4 + text.Length; + } + } + + num2 += columnWidths[j] + ColumnGutter; + } + + AppendBlankLine(); + } + } + + + private void AppendTable(IEnumerable> table, IReadOnlyList columnWidths) + { + foreach (IEnumerable item in table) + { + AppendRow(item, columnWidths); + } + } + + private void AppendText(string text, int? offset = null) + { + AppendPadding(offset); + Console.Out.Write(text ?? ""); + } + + private IReadOnlyList ColumnWidths(IEnumerable> table) + { + if (!table.Any()) + { + return Array.Empty(); + } + + int count = table.First().Count; + int num = -1; + int[] array = new int[count]; + for (int j = 0; j < count; j++) + { + array[j] = num; + } + + int[] array2 = new int[count]; + int i; + for (i = 0; i < count; i++) + { + array2[i] = table.Max((IReadOnlyList row) => row[i].Length); + } + + int num2 = array2.Count((int width) => width > 0); + int num3 = GetAvailableWidth() - ColumnGutter * (num2 - 1); + if (num3 - num2 < 0 || num2 == 0) + { + return array2; + } + + int num4 = num2; + int num5 = 0; + int num6 = 5; + while (num4 > 0) + { + int num7 = (num3 - array.Where((int width) => width > 0).Sum()) / num4; + bool flag = num4 == num5 || num6 <= 1; + for (int k = 0; k < count; k++) + { + if (array[k] == num) + { + int num8 = array2[k]; + if (flag) + { + num8 = Math.Min(num8, num7); + } + + if (num8 <= num7) + { + array[k] = num8; + } + } + } + + num5 = num4; + num4 = array.Count((int width) => width < 0); + num6--; + } + + return array; + } + } +} diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index da4b5a93..a0a1832e 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Help; using System.CommandLine.Invocation; +using System.CommandLine.IO; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -16,6 +17,66 @@ namespace ClangSharp public class Program { private static RootCommand s_rootCommand; + private static Option s_configOption; + + private static readonly (string Name, string Description)[] s_configOptions = new (string Name, string Description)[] + { + ("?, h, help", "Show help and usage information for -c, --config"), + + ("", ""), // Codegen Options + + ("compatible-codegen", "Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation."), + ("latest-codegen", "Bindings should be generated for the latest stable version of .NET/C#. This is currently .NET 5/C# 9."), + ("preview-codegen", "Bindings should be generated for the latest preview version of .NET/C#. This is currently .NET 6/C# 10."), + + ("", ""), // File Options + + ("single-file", "Bindings should be generated to a single output file. This is the default."), + ("multi-file", "Bindings should be generated so there is approximately one type per file."), + + ("", ""), // Type Options + + ("unix-types", "Bindings should be generated assuming Unix defaults. This is the default on Unix platforms."), + ("windows-types", "Bindings should be generated assuming Windows defaults. This is the default on Windows platforms."), + + ("", ""), // Exclusion Options + + ("exclude-anonymous-field-helpers", "The helper ref properties generated for fields in nested anonymous structs and unions should not be generated."), + ("exclude-com-proxies", "Types recognized as COM proxies should not have bindings generated. Thes are currently function declarations ending with _UserFree, _UserMarshal, _UserSize, _UserUnmarshal, _Proxy, or _Stub."), + ("exclude-default-remappings", "Default remappings for well known types should not be added. This currently includes intptr_t, ptrdiff_t, size_t, and uintptr_t"), + ("exclude-empty-records", "Bindings for records that contain no members should not be generated. These are commonly encountered for opaque handle like types such as HWND."), + ("exclude-enum-operators", "Bindings for operators over enum types should not be generated. These are largely unnecessary in C# as the operators are available by default."), + ("exclude-funcs-with-body", "Bindings for functions with bodies should not be generated."), + ("exclude-using-statics-for-enums", "Enum usages should be fully qualified and should not include a corresponding 'using static EnumName;'"), + + ("", ""), // VTBL Options + + ("explicit-vtbls", "VTBLs should have an explicit type generated with named fields per entry."), + ("implicit-vtbls", "VTBLs should be implicit to reduce metadata bloat. This is the current default"), + + ("", ""), // Test Options + + ("generate-tests-nunit", "Basic tests validating size, blittability, and associated metadata should be generated for NUnit."), + ("generate-tests-xunit", "Basic tests validating size, blittability, and associated metadata should be generated for XUnit."), + + ("", ""), // Generation Options + + ("generate-aggressive-inlining", "[MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to generated helper functions."), + ("generate-cpp-attributes", "A [CppAttributeList(\"\")] should be generated to document the encountered C++ attributes."), + ("generate-macro-bindings", "Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones."), + ("generate-native-inheritance-attribute", "A [NativeInheritance(\"\")] attribute should be generated to document the encountered C++ base type."), + + ("", ""), // Logging Options + + ("log-exclusions", "A list of excluded declaration types should be generated. This will also log if the exclusion was due to an exact or partial match."), + ("log-potential-typedef-remappings", "A list of potential typedef remappings should be generated. This can help identify missing remappings."), + ("log-visited-files", "A list of the visited files should be generated. This can help identify traversal issues."), + + ("", ""), // Preview Options + + ("preview-codegen-fnptr", "Generated bindings should use function pointers instead of IntPtr where possible."), + ("preview-codegen-nint", "Generated bindings should use nint and nuint instead of IntPtr and UIntPtr where possible."), + }; public static async Task Main(params string[] args) { @@ -106,11 +167,22 @@ public static int Run(InvocationContext context) ParseKeyValuePairs(withUsingNameValuePairs, errorList, out Dictionary> withUsings); var configOptions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? PInvokeGeneratorConfigurationOptions.None : PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; + var printConfigHelp = false; foreach (var configSwitch in configSwitches) { switch (configSwitch) { + case "?": + case "h": + case "help": + { + printConfigHelp = true; + break; + } + + // Codegen Options + case "compatible-codegen": { configOptions |= PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; @@ -118,157 +190,171 @@ public static int Run(InvocationContext context) break; } - case "default-remappings": + case "latest-codegen": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.NoDefaultRemappings; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GeneratePreviewCode; break; } - case "exclude-com-proxies": + case "preview-codegen": { - configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeComProxies; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; + configOptions |= PInvokeGeneratorConfigurationOptions.GeneratePreviewCode; break; } - case "exclude-empty-records": + // File Options + + case "single-file": { - configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeEmptyRecords; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles; break; } - case "exclude-enum-operators": + case "multi-file": { - configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeEnumOperators; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles; break; } - case "explicit-vtbls": + // Type Options + + case "unix-types": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateExplicitVtbls; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; break; } - case "generate-aggressive-inlining": + case "windows-types": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateAggressiveInlining; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; break; } - case "generate-macro-bindings": + // Exclusion Options + + case "exclude-anonymous-field-helpers": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateMacroBindings; + configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeAnonymousFieldHelpers; break; } - case "generate-tests-nunit": + case "exclude-com-proxies": { - if (string.IsNullOrWhiteSpace(testOutputLocation)) - { - errorList.Add("Error: No test output file location provided. Use --test-output or -to"); - } - - if (configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit)) - { - errorList.Add("Cannot generate both NUnit and XUnit tests."); - } - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit; + configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeComProxies; break; } - case "generate-tests-xunit": + case "exclude-default-remappings": + case "no-default-remappings": { - if (string.IsNullOrWhiteSpace(testOutputLocation)) - { - errorList.Add("Error: No test output file location provided. Use --test-output or -to"); - } - - if (configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit)) - { - errorList.Add("Cannot generate both NUnit and XUnit tests."); - } - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit; + configOptions |= PInvokeGeneratorConfigurationOptions.NoDefaultRemappings; break; } - case "implicit-vtbls": + case "exclude-empty-records": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateExplicitVtbls; + configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeEmptyRecords; break; } - case "latest-codegen": + case "exclude-enum-operators": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; - configOptions &= ~PInvokeGeneratorConfigurationOptions.GeneratePreviewCode; + configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeEnumOperators; break; } - case "log-exclusions": + case "exclude-funcs-with-body": { - configOptions |= PInvokeGeneratorConfigurationOptions.LogExclusions; + configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeFunctionsWithBody; break; } - case "log-visited-files": + case "exclude-using-statics-for-enums": + case "dont-use-using-statics-for-enums": { - configOptions |= PInvokeGeneratorConfigurationOptions.LogVisitedFiles; + configOptions |= PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums; break; } - case "multi-file": + // VTBL Options + + case "explicit-vtbls": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateExplicitVtbls; break; } - case "no-default-remappings": + case "implicit-vtbls": { - configOptions |= PInvokeGeneratorConfigurationOptions.NoDefaultRemappings; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateExplicitVtbls; break; } - case "preview-codegen": + // Test Options + + case "generate-tests-nunit": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; - configOptions |= PInvokeGeneratorConfigurationOptions.GeneratePreviewCode; + if (string.IsNullOrWhiteSpace(testOutputLocation)) + { + errorList.Add("Error: No test output file location provided. Use --test-output or -to"); + } + + if (configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit)) + { + errorList.Add("Cannot generate both NUnit and XUnit tests."); + } + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit; break; } - case "preview-codegen-nint": + case "generate-tests-xunit": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; - configOptions |= PInvokeGeneratorConfigurationOptions.GeneratePreviewCodeNint; + if (string.IsNullOrWhiteSpace(testOutputLocation)) + { + errorList.Add("Error: No test output file location provided. Use --test-output or -to"); + } + + if (configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit)) + { + errorList.Add("Cannot generate both NUnit and XUnit tests."); + } + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit; break; } - case "preview-codegen-fnptr": + // Generation Options + + case "generate-aggressive-inlining": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; - configOptions |= PInvokeGeneratorConfigurationOptions.GeneratePreviewCodeFnptr; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateAggressiveInlining; break; } - case "single-file": + case "generate-cpp-attributes": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateCppAttributes; break; } - case "unix-types": + case "generate-macro-bindings": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateMacroBindings; break; } - case "windows-types": + case "generate-native-inheritance-attribute": { - configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateNativeInheritanceAttribute; break; } - case "exclude-funcs-with-body": + // Logging Options + + case "log-exclusions": { - configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeFunctionsWithBody; + configOptions |= PInvokeGeneratorConfigurationOptions.LogExclusions; break; } @@ -278,27 +364,33 @@ public static int Run(InvocationContext context) break; } - case "exclude-anonymous-field-helpers": + case "log-visited-files": { - configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeAnonymousFieldHelpers; + configOptions |= PInvokeGeneratorConfigurationOptions.LogVisitedFiles; break; } - case "generate-cpp-attributes": + // Preview Options + + case "preview-codegen-fnptr": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateCppAttributes; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; + configOptions |= PInvokeGeneratorConfigurationOptions.GeneratePreviewCodeFnptr; break; } - case "generate-native-inheritance-attribute": + case "preview-codegen-nint": { - configOptions |= PInvokeGeneratorConfigurationOptions.GenerateNativeInheritanceAttribute; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode; + configOptions |= PInvokeGeneratorConfigurationOptions.GeneratePreviewCodeNint; break; } - case "dont-use-using-statics-for-enums": + // Legacy Options + + case "default-remappings": { - configOptions |= PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums; + configOptions &= ~PInvokeGeneratorConfigurationOptions.NoDefaultRemappings; break; } @@ -315,6 +407,19 @@ public static int Run(InvocationContext context) errorList.Add("Error: No test format provided. Use --config generate-tests-nunit or --config generate-tests-xunit"); } + if (printConfigHelp) + { + var helpBuilder = new CustomHelpBuilder(context.Console); + helpBuilder.Write(s_configOption); + + context.Console.Out.WriteLine(); + context.Console.Out.WriteLine(); + + helpBuilder.Write(s_configOptions); + + return -1; + } + if (errorList.Any()) { foreach (var error in errorList) @@ -508,17 +613,19 @@ private static void AddAdditionalOption(RootCommand rootCommand) private static void AddConfigOption(RootCommand rootCommand) { - var option = new Option(new string[] { "--config", "-c" }, "A configuration option that controls how the bindings are generated.") + if (s_configOption is null) { - Argument = new Argument("") + s_configOption = new Option(new string[] { "--config", "-c" }, "A configuration option that controls how the bindings are generated. Specify 'help' to see the available options.") { - ArgumentType = typeof(string), - Arity = ArgumentArity.OneOrMore, - } - }; - option.Argument.SetDefaultValue(Array.Empty()); - - rootCommand.AddOption(option); + Argument = new Argument("") + { + ArgumentType = typeof(string), + Arity = ArgumentArity.OneOrMore, + } + }; + s_configOption.Argument.SetDefaultValue(Array.Empty()); + } + rootCommand.AddOption(s_configOption); } private static void AddDefineMacroOption(RootCommand rootCommand) diff --git a/sources/ClangSharpPInvokeGenerator/Properties/launchSettings.json b/sources/ClangSharpPInvokeGenerator/Properties/launchSettings.json index ebbab5d2..5fdf6cc0 100644 --- a/sources/ClangSharpPInvokeGenerator/Properties/launchSettings.json +++ b/sources/ClangSharpPInvokeGenerator/Properties/launchSettings.json @@ -12,6 +12,10 @@ "GenerateLLVM": { "commandName": "Project", "commandLineArgs": "\"@$(MSBuildProjectDirectory)/Properties/GenerateLLVM.rsp\" --file-directory \"$(LLVMIncludePath)\" --include-directory \"$(LLVMIncludePath)\" --libraryPath $(LibLLVMName)" + }, + "GenerateLocal": { + "commandName": "Project", + "commandLineArgs": "-?" } } }