diff --git a/ClangSharp.slnx b/ClangSharp.slnx
index 443546c4..9b682777 100644
--- a/ClangSharp.slnx
+++ b/ClangSharp.slnx
@@ -113,5 +113,6 @@
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 59deeab5..c9dc746d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -17,7 +17,6 @@
-
diff --git a/README.md b/README.md
index be44db44..35bd4435 100644
--- a/README.md
+++ b/README.md
@@ -169,7 +169,7 @@ Options:
-r, --remap A declaration name to be remapped to another name during binding generation. []
-rt, --remap-type A type (record or enum) declaration name to be remapped to another name during binding generation. Takes precedence over --remap and is useful when a type and field share a name. []
-rf, --remap-field A field declaration name to be remapped to another name during binding generation. Takes precedence over --remap and is useful when a type and field share a name. []
- -std Language standard to compile for. []
+ -std, --std Language standard to compile for. []
-to, --test-output The output location to write the generated tests to. []
-t, --traverse A file name included either directly or indirectly by -f that should be traversed during binding generation. []
-v, --version Prints the current version information for the tool and its native dependencies.
@@ -183,6 +183,7 @@ Options:
-wmi, --with-manual-import A remapped function name to be treated as a manual import during binding generation. Supports wildcards. []
-wn, --with-namespace A namespace to be used for the given remapped declaration name during binding generation. Supports wildcards. []
-wp, --with-packing Overrides the StructLayoutAttribute.Pack property for the given type. Supports wildcards. []
+ -wro, --with-readonly Add the readonly modifier to a given instance method. Supports wildcards. []
-wsle, --with-setlasterror Add the SetLastError=true modifier or SetsSystemLastError attribute to a given DllImport or UnmanagedFunctionPointer. Supports wildcards. []
-wsgct, --with-suppressgctransition Add the SuppressGCTransition calling convention to a given DllImport or UnmanagedFunctionPointer. Supports wildcards. []
-wts, --with-transparent-struct A remapped type name to be treated as a transparent wrapper during binding generation. Supports wildcards. []
@@ -196,79 +197,80 @@ You can use * as catch-all rule for remapping procedures. For example if you wan
The available configuration options (visible with `-c help`) are:
```
---config, -c A configuration option that controls how the bindings are generated. Specify 'help' to see the available options.
+--config, -c 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
+ ?, 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.
- default-codegen Bindings should be generated for the previous LTS version of .NET/C#. This is currently .NET 8/C# 12.
- latest-codegen Bindings should be generated for the current LTS/STS version of .NET/C#. This is currently .NET 10/C# 14.
- preview-codegen Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14.
+ compatible-codegen Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation.
+ default-codegen Bindings should be generated for the current LTS version of .NET/C#. This is currently .NET 8/C# 12.
+ latest-codegen Bindings should be generated for the current STS version of .NET/C#. This is currently .NET 10/C# 14.
+ preview-codegen Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14.
# 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.
+ 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.
+ 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. These 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-fnptr-codegen Generated bindings for latest or preview codegen should not use function pointers.
- 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;'
+ 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. These 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-fnptr-codegen Generated bindings for latest or preview codegen should not use function pointers.
+ 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
- trimmable-vtbls VTBLs should be defined but not used in helper methods to reduce metadata bloat when trimming.
+ 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
+ trimmable-vtbls VTBLs should be defined but not used in helper methods to reduce metadata bloat when trimming.
# 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.
+ 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-callconv-member-function Instance function pointers should use [CallConvMemberFunction] where applicable.
- generate-cpp-attributes [CppAttributeList("")] should be generated to document the encountered C++ attributes.
- generate-disable-runtime-marshalling [assembly: DisableRuntimeMarshalling] should be generated.
- generate-doc-includes xml documentation tags should be generated for declarations.
- generate-file-scoped-namespaces Namespaces should be scoped to the file to reduce nesting.
- generate-guid-member Types with an associated GUID should have a corresponding member generated.
- generate-helper-types Code files should be generated for various helper attributes and declared transparent structs.
- generate-macro-bindings Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones.
- generate-marker-interfaces Bindings for marker interfaces representing native inheritance hierarchies should be generated.
- generate-native-bitfield-attribute [NativeBitfield("", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout.
- generate-native-inheritance-attribute [NativeInheritance("")] attribute should be generated to document the encountered C++ base type.
- generate-generic-pointer-wrapper Pointer should be used for limited generic type support.
- generate-setslastsystemerror-attribute [SetsLastSystemError] attribute should be generated rather than using SetLastError = true.
- generate-template-bindings Bindings for template-definitions should be generated. This is currently experimental.
- generate-unmanaged-constants Unmanaged constants should be generated using static ref readonly properties. This is currently experimental.
- generate-vtbl-index-attribute [VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method.
+ generate-aggressive-inlining [MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to generated helper functions.
+ generate-callconv-member-function Instance function pointers should use [CallConvMemberFunction] where applicable.
+ generate-cpp-attributes [CppAttributeList("")] should be generated to document the encountered C++ attributes.
+ generate-disable-runtime-marshalling [assembly: DisableRuntimeMarshalling] should be generated.
+ generate-doc-includes xml documentation tags should be generated for declarations.
+ generate-file-scoped-namespaces Namespaces should be scoped to the file to reduce nesting.
+ generate-fixed-buffer-indexer-overloads Fixed sized buffer helper types should generate additional uint, nint, and nuint indexer overloads.
+ generate-guid-member Types with an associated GUID should have a corresponding member generated.
+ generate-helper-types Code files should be generated for various helper attributes and declared transparent structs.
+ generate-macro-bindings Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones.
+ generate-marker-interfaces Bindings for marker interfaces representing native inheritance hierarchies should be generated.
+ generate-native-bitfield-attribute [NativeBitfield("", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout.
+ generate-native-inheritance-attribute [NativeInheritance("")] attribute should be generated to document the encountered C++ base type.
+ generate-generic-pointer-wrapper Pointer should be used for limited generic type support.
+ generate-setslastsystemerror-attribute [SetsLastSystemError] attribute should be generated rather than using SetLastError = true.
+ generate-template-bindings Bindings for template-definitions should be generated. This is currently experimental.
+ generate-unmanaged-constants Unmanaged constants should be generated using static ref readonly properties. This is currently experimental.
+ generate-vtbl-index-attribute [VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method.
# Stripping Options
- strip-enum-member-type-name Strips the enum type name from the beginning of its member names.
+ strip-enum-member-type-name Strips the enum type name from the beginning of its member names.
# 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.
+ 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.
```
### Using locally built versions
diff --git a/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj b/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
index 73f6008d..c13ea9c7 100644
--- a/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
+++ b/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
@@ -8,6 +8,8 @@
true
linux-arm64;linux-x64;osx-arm64;win-arm64;win-x64
net10.0
+
+ $(NoWarn);CA1303
@@ -24,11 +26,11 @@
-
+
-
+
diff --git a/sources/ClangSharpPInvokeGenerator/CommandLine.cs b/sources/ClangSharpPInvokeGenerator/CommandLine.cs
new file mode 100644
index 00000000..41442b46
--- /dev/null
+++ b/sources/ClangSharpPInvokeGenerator/CommandLine.cs
@@ -0,0 +1,334 @@
+// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace ClangSharp;
+
+internal enum CommandLineOptionKind
+{
+ Flag,
+ SingleValue,
+ MultipleValue,
+}
+
+internal sealed class CommandLineOption
+{
+ private readonly List _values;
+
+ public CommandLineOption(string[] aliases, string description, CommandLineOptionKind kind, string? valueName = null, string? defaultValue = null, string[]? allowedValues = null)
+ {
+ Aliases = aliases;
+ Description = description;
+ Kind = kind;
+ ValueName = valueName ?? aliases[0].TrimStart('-');
+ DefaultValue = defaultValue;
+ AllowedValues = allowedValues;
+ _values = [];
+ }
+
+ public string[] Aliases { get; }
+
+ public string[]? AllowedValues { get; }
+
+ // The canonical name used in diagnostics; the first alias is always the long form.
+ public string CanonicalName => Aliases[0];
+
+ public string? DefaultValue { get; }
+
+ public string Description { get; }
+
+ public bool IsPresent { get; set; }
+
+ public CommandLineOptionKind Kind { get; }
+
+ public string ValueName { get; }
+
+ // The effective value for a single-value option: the last one specified or the default.
+ public string SingleValue => (_values.Count != 0) ? _values[^1] : (DefaultValue ?? "");
+
+ public void AddValue(string value) => _values.Add(value);
+
+ public string[] GetValues() => [.. _values];
+}
+
+internal readonly struct HelpRow(string term, string description)
+{
+ public string Term { get; } = term;
+
+ public string Description { get; } = description;
+}
+
+internal sealed class CommandLineParser
+{
+ private const int MaxResponseFileDepth = 32;
+
+ private readonly IReadOnlyList _options;
+ private readonly Dictionary _optionsByAlias;
+ private readonly List _errors;
+
+ public CommandLineParser(IReadOnlyList options)
+ {
+ _options = options;
+ _optionsByAlias = new Dictionary(StringComparer.Ordinal);
+ _errors = [];
+
+ foreach (var option in options)
+ {
+ foreach (var alias in option.Aliases)
+ {
+ _optionsByAlias.Add(alias, option);
+ }
+ }
+ }
+
+ public IReadOnlyList Errors => _errors;
+
+ public void Parse(IReadOnlyList args)
+ {
+ var tokens = new List();
+
+ foreach (var arg in args)
+ {
+ ExpandToken(arg, tokens, depth: 0);
+ }
+
+ var index = 0;
+
+ while (index < tokens.Count)
+ {
+ var token = tokens[index];
+
+ if (!TryMatchOption(token, out var option, out var inlineValue))
+ {
+ _errors.Add($"Error: Unrecognized command or argument '{token}'.");
+ index++;
+ continue;
+ }
+
+ index++;
+ option.IsPresent = true;
+
+ if (option.Kind == CommandLineOptionKind.Flag)
+ {
+ if (inlineValue is not null)
+ {
+ _errors.Add($"Error: Option '{option.CanonicalName}' does not accept an argument.");
+ }
+ continue;
+ }
+
+ if (inlineValue is not null)
+ {
+ AddValue(option, inlineValue);
+ continue;
+ }
+
+ var count = 0;
+
+ while ((index < tokens.Count) && !TryMatchOption(tokens[index], out _, out _))
+ {
+ AddValue(option, tokens[index]);
+ index++;
+ count++;
+
+ if (option.Kind == CommandLineOptionKind.SingleValue)
+ {
+ break;
+ }
+ }
+
+ if (count == 0)
+ {
+ _errors.Add($"Error: Required argument missing for option: '{option.CanonicalName}'.");
+ }
+ }
+ }
+
+ private void AddValue(CommandLineOption option, string value)
+ {
+ if ((option.AllowedValues is not null) && !option.AllowedValues.Contains(value, StringComparer.Ordinal))
+ {
+ _errors.Add($"Error: Argument '{value}' not recognized for option '{option.CanonicalName}'. Must be one of: {string.Join(", ", option.AllowedValues)}.");
+ return;
+ }
+
+ option.AddValue(value);
+ }
+
+ private bool TryMatchOption(string token, out CommandLineOption option, out string? inlineValue)
+ {
+ inlineValue = null;
+
+ if (_optionsByAlias.TryGetValue(token, out option!))
+ {
+ return true;
+ }
+
+ // Support the `--alias=value` (or `-alias=value`) inline form, but only when the text
+ // before the first '=' is itself a known alias. This keeps bare `name=value` tokens
+ // (such as a `--remap` value) intact even when the value contains '=' or spaces.
+
+ if ((token.Length != 0) && (token[0] == '-'))
+ {
+ var equalsIndex = token.IndexOf('=', StringComparison.Ordinal);
+
+ if (equalsIndex > 0)
+ {
+ var alias = token[..equalsIndex];
+
+ if (_optionsByAlias.TryGetValue(alias, out option!))
+ {
+ inlineValue = token[(equalsIndex + 1)..];
+ return true;
+ }
+ }
+ }
+
+ option = null!;
+ return false;
+ }
+
+ private void ExpandToken(string token, List result, int depth)
+ {
+ if ((token.Length == 0) || (token[0] != '@'))
+ {
+ result.Add(token);
+ return;
+ }
+
+ var responseFilePath = token[1..];
+
+ if (depth > MaxResponseFileDepth)
+ {
+ _errors.Add($"Error: Response file recursion limit exceeded at '{responseFilePath}'.");
+ return;
+ }
+
+ if (!File.Exists(responseFilePath))
+ {
+ _errors.Add($"Error: Response file not found '{responseFilePath}'.");
+ return;
+ }
+
+ string[] lines;
+
+ try
+ {
+ lines = File.ReadAllLines(responseFilePath);
+ }
+ catch (IOException ex)
+ {
+ _errors.Add($"Error: {ex.Message}");
+ return;
+ }
+
+ // Each non-empty, non-comment line is treated as a single token; the contents are
+ // never split on whitespace so a value containing spaces is preserved as-is.
+
+ foreach (var line in lines)
+ {
+ var trimmed = line.Trim();
+
+ if ((trimmed.Length == 0) || (trimmed[0] == '#'))
+ {
+ continue;
+ }
+
+ ExpandToken(trimmed, result, depth + 1);
+ }
+ }
+
+ public void WriteHelp(TextWriter writer, string name, string description, string epilogTitle, string epilog)
+ {
+ writer.WriteLine(name);
+ writer.WriteLine($" {description}");
+ writer.WriteLine();
+ writer.WriteLine("Usage:");
+ writer.WriteLine($" {name} [options]");
+ writer.WriteLine();
+ writer.WriteLine("Options:");
+
+ var terms = new string[_options.Count];
+ var maxTermLength = 0;
+
+ for (var i = 0; i < _options.Count; i++)
+ {
+ var option = _options[i];
+ var term = string.Join(", ", GetDisplayAliases(option));
+
+ if (option.Kind != CommandLineOptionKind.Flag)
+ {
+ term += $" <{option.ValueName}>";
+ }
+
+ terms[i] = term;
+ maxTermLength = Math.Max(maxTermLength, term.Length);
+ }
+
+ for (var i = 0; i < _options.Count; i++)
+ {
+ var option = _options[i];
+ writer.WriteLine($" {terms[i].PadRight(maxTermLength + 2)}{option.Description}{GetDefaultSuffix(option)}");
+ }
+
+ writer.WriteLine();
+ writer.WriteLine(epilogTitle);
+ writer.WriteLine(epilog);
+ }
+
+ public static void WriteOptionHelp(TextWriter writer, CommandLineOption option, IReadOnlyList rows)
+ {
+ writer.WriteLine($"{string.Join(", ", option.Aliases)}\t{option.Description}");
+ writer.WriteLine();
+ WriteTwoColumn(writer, rows);
+ }
+
+ public static void WriteTwoColumn(TextWriter writer, IReadOnlyList rows)
+ {
+ writer.WriteLine("Options:");
+
+ var maxTermLength = 0;
+
+ foreach (var row in rows)
+ {
+ if (row.Description.Length != 0)
+ {
+ maxTermLength = Math.Max(maxTermLength, row.Term.Length);
+ }
+ }
+
+ foreach (var row in rows)
+ {
+ if (row.Description.Length == 0)
+ {
+ writer.WriteLine((row.Term.Length == 0) ? "" : $" {row.Term}");
+ }
+ else
+ {
+ writer.WriteLine($" {row.Term.PadRight(maxTermLength + 2)}{row.Description}");
+ }
+ }
+
+ writer.WriteLine();
+ }
+
+ private static IEnumerable GetDisplayAliases(CommandLineOption option)
+ {
+ // Match the historical help ordering: short (`-x`) aliases first, then long (`--xx`).
+ return option.Aliases.Where(static alias => !alias.StartsWith("--", StringComparison.Ordinal))
+ .Concat(option.Aliases.Where(static alias => alias.StartsWith("--", StringComparison.Ordinal)));
+ }
+
+ private static string GetDefaultSuffix(CommandLineOption option)
+ {
+ if (option.Kind == CommandLineOptionKind.Flag)
+ {
+ return "";
+ }
+
+ return string.IsNullOrEmpty(option.DefaultValue) ? " []" : $" [default: {option.DefaultValue}]";
+ }
+}
diff --git a/sources/ClangSharpPInvokeGenerator/CustomHelpBuilder.cs b/sources/ClangSharpPInvokeGenerator/CustomHelpBuilder.cs
deleted file mode 100644
index 5c6ba64e..00000000
--- a/sources/ClangSharpPInvokeGenerator/CustomHelpBuilder.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more 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 rights reserved. Licensed under the MIT License (MIT).
-
-using System.CommandLine;
-using System.CommandLine.Help;
-using System.CommandLine.IO;
-
-namespace ClangSharp;
-
-internal sealed class CustomHelpBuilder(IConsole console, LocalizationResources localizationResources, int maxWidth = int.MaxValue) : HelpBuilder(localizationResources, maxWidth)
-{
- public void Write(Option option)
- {
- Write(string.Join(", ", option.Aliases));
- Write("\t");
- Write(option.Description ?? "");
- WriteLine();
- }
-
- public void Write(string value) => console.Out.Write(value);
-
- public void Write(params TwoColumnHelpRow[] helpItems)
- {
- WriteLine("Options:");
- var _ = new Command("unused");
- WriteColumns(helpItems, new HelpContext(this, _, console.Out.CreateTextWriter()));
- WriteLine();
- }
-
- public void WriteLine() => console.Out.WriteLine();
-
- public void WriteLine(string value) => console.Out.WriteLine(value);
-}
diff --git a/sources/ClangSharpPInvokeGenerator/Program.Options.cs b/sources/ClangSharpPInvokeGenerator/Program.Options.cs
index eafe38a4..404b7d5e 100644
--- a/sources/ClangSharpPInvokeGenerator/Program.Options.cs
+++ b/sources/ClangSharpPInvokeGenerator/Program.Options.cs
@@ -1,10 +1,5 @@
// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.
-using System;
-using System.CommandLine;
-using System.CommandLine.Help;
-using System.CommandLine.Invocation;
-
namespace ClangSharp;
internal static partial class Program
@@ -49,606 +44,193 @@ internal static partial class Program
private static readonly string[] s_withTransparentStructOptionAliases = ["--with-transparent-struct", "-wts"];
private static readonly string[] s_withTypeOptionAliases = ["--with-type", "-wt"];
private static readonly string[] s_withUsingOptionAliases = ["--with-using", "-wu"];
-
- private static readonly Option s_additionalOption = GetAdditionalOption();
- private static readonly Option s_configOption = GetConfigOption();
- private static readonly Option s_defineMacros = GetDefineMacroOption();
- private static readonly Option s_excludedNames = GetExcludeOption();
- private static readonly Option s_files = GetFileOption();
- private static readonly Option s_fileDirectory = GetFileDirectoryOption();
- private static readonly Option s_headerFile = GetHeaderOption();
- private static readonly Option s_includedNames = GetIncludeOption();
- private static readonly Option s_includeDirectories = GetIncludeDirectoryOption();
- private static readonly Option s_language = GetLanguageOption();
- private static readonly Option s_libraryPath = GetLibraryOption();
- private static readonly Option s_methodClassName = GetMethodClassNameOption();
- private static readonly Option s_methodPrefixToStrip = GetPrefixStripOption();
- private static readonly Option s_namespaceName = GetNamespaceOption();
- private static readonly Option s_nativeTypeNamesToStrip = GetNativeTypeNamesStripOption();
- private static readonly Option s_outputLocation = GetOutputOption();
- private static readonly Option s_outputMode = GetOutputModeOption();
- private static readonly Option s_remappedNameValuePairs = GetRemapOption();
- private static readonly Option s_remappedTypeNameValuePairs = GetRemapTypeOption();
- private static readonly Option s_remappedFieldNameValuePairs = GetRemapFieldOption();
- private static readonly Option s_std = GetStdOption();
- private static readonly Option s_testOutputLocation = GetTestOutputOption();
- private static readonly Option s_traversalNames = GetTraverseOption();
- private static readonly Option s_versionOption = GetVersionOption();
- private static readonly Option s_withAccessSpecifierNameValuePairs = GetWithAccessSpecifierOption();
- private static readonly Option s_withAttributeNameValuePairs = GetWithAttributeOption();
- private static readonly Option s_withCallConvNameValuePairs = GetWithCallConvOption();
- private static readonly Option s_withClassNameValuePairs = GetWithClassOption();
- private static readonly Option s_withGuidNameValuePairs = GetWithGuidOption();
- private static readonly Option s_withLengthNameValuePairs = GetWithLengthOption();
- private static readonly Option s_withLibraryPathNameValuePairs = GetWithLibraryPathOption();
- private static readonly Option s_withManualImports = GetWithManualImportOption();
- private static readonly Option s_withNamespaceNameValuePairs = GetWithNamespaceOption();
- private static readonly Option s_withPackingNameValuePairs = GetWithPackingOption();
- private static readonly Option s_withReadonlys = GetWithReadonlyOption();
- private static readonly Option s_withSetLastErrors = GetWithSetLastErrorOption();
- private static readonly Option s_withSuppressGCTransitions = GetWithSuppressGCTransitionOption();
- private static readonly Option s_withTransparentStructNameValuePairs = GetWithTransparentStructOption();
- private static readonly Option s_withTypeNameValuePairs = GetWithTypeOption();
- private static readonly Option s_withUsingNameValuePairs = GetWithUsingOption();
-
- private static readonly RootCommand s_rootCommand = GetRootCommand();
-
- private static readonly TwoColumnHelpRow[] s_configOptions =
+ private static readonly string[] s_helpOptionAliases = ["--help", "-?", "-h"];
+
+ private static readonly CommandLineOption s_additionalOption = Multi(s_additionalOptionAliases, "An argument to pass to Clang when parsing the input files.");
+ private static readonly CommandLineOption s_configOption = Multi(s_configOptionAliases, "A configuration option that controls how the bindings are generated. Specify 'help' to see the available options.");
+ private static readonly CommandLineOption s_defineMacros = Multi(s_defineMacroOptionAliases, "Define to (or 1 if omitted).");
+ private static readonly CommandLineOption s_excludedNames = Multi(s_excludeOptionAliases, "A declaration name to exclude from binding generation.");
+ private static readonly CommandLineOption s_files = Multi(s_fileOptionAliases, "A file to parse and generate bindings for.");
+ private static readonly CommandLineOption s_fileDirectory = Single(s_fileDirectionOptionAliases, "The base path for files to parse.");
+ private static readonly CommandLineOption s_headerFile = Single(s_headerOptionAliases, "A file which contains the header to prefix every generated file with.");
+ private static readonly CommandLineOption s_includedNames = Multi(s_includeOptionAliases, "A declaration name to include in binding generation.");
+ private static readonly CommandLineOption s_includeDirectories = Multi(s_includeDirectoryOptionAliases, "Add directory to include search path.");
+ private static readonly CommandLineOption s_language = Single(s_languageOptionAliases, "Treat subsequent input files as having type .", defaultValue: "c++", valueName: "c|c++", allowedValues: ["c", "c++"]);
+ private static readonly CommandLineOption s_libraryPath = Single(s_libraryOptionAliases, "The string to use in the DllImport attribute used when generating bindings.");
+ private static readonly CommandLineOption s_methodClassName = Single(s_methodClassNameOptionAliases, "The name of the static class that will contain the generated method bindings.", defaultValue: "Methods");
+ private static readonly CommandLineOption s_namespaceName = Single(s_namespaceOptionAliases, "The namespace in which to place the generated bindings.");
+ private static readonly CommandLineOption s_nativeTypeNamesToStrip = Multi(s_nativeTypeNamesStripOptionAliases, "The contents to strip from the generated NativeTypeName attributes.");
+ private static readonly CommandLineOption s_outputMode = Single(s_outputModeOptionAliases, "The mode describing how the information collected from the headers are presented in the resultant bindings.", defaultValue: "CSharp", valueName: "CSharp|Xml");
+ private static readonly CommandLineOption s_outputLocation = Single(s_outputOptionAliases, "The output location to write the generated bindings to.");
+ private static readonly CommandLineOption s_methodPrefixToStrip = Single(s_prefixStripOptionAliases, "The prefix to strip from the generated method bindings.");
+ private static readonly CommandLineOption s_remappedNameValuePairs = Multi(s_remapOptionAliases, "A declaration name to be remapped to another name during binding generation.");
+ private static readonly CommandLineOption s_remappedTypeNameValuePairs = Multi(s_remapTypeOptionAliases, "A type (record or enum) declaration name to be remapped to another name during binding generation. Takes precedence over --remap and is useful when a type and field share a name.");
+ private static readonly CommandLineOption s_remappedFieldNameValuePairs = Multi(s_remapFieldOptionAliases, "A field declaration name to be remapped to another name during binding generation. Takes precedence over --remap and is useful when a type and field share a name.");
+ private static readonly CommandLineOption s_std = Single(s_stdOptionAliases, "Language standard to compile for.");
+ private static readonly CommandLineOption s_testOutputLocation = Single(s_testOutputOptionAliases, "The output location to write the generated tests to.");
+ private static readonly CommandLineOption s_traversalNames = Multi(s_traverseOptionAliases, "A file name included either directly or indirectly by -f that should be traversed during binding generation.");
+ private static readonly CommandLineOption s_versionOption = Flag(s_versionOptionAliases, "Prints the current version information for the tool and its native dependencies.");
+ private static readonly CommandLineOption s_withAccessSpecifierNameValuePairs = Multi(s_withAccessSpecifierOptionAliases, "An access specifier to be used with the given qualified or remapped declaration name during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withAttributeNameValuePairs = Multi(s_withAttributeOptionAliases, "An attribute to be added to the given remapped declaration name during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withCallConvNameValuePairs = Multi(s_withCallConvOptionAliases, "A calling convention to be used for the given declaration during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withClassNameValuePairs = Multi(s_withClassOptionAliases, "A class to be used for the given remapped constant or function declaration name during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withGuidNameValuePairs = Multi(s_withGuidOptionAliases, "A GUID to be used for the given declaration during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withLengthNameValuePairs = Multi(s_withLengthOptionAliases, "A length to be used for the given declaration during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withLibraryPathNameValuePairs = Multi(s_withLibraryPathOptionAliases, "A library path to be used for the given declaration during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withManualImports = Multi(s_withManualImportOptionAliases, "A remapped function name to be treated as a manual import during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withNamespaceNameValuePairs = Multi(s_withNamespaceOptionAliases, "A namespace to be used for the given remapped declaration name during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withPackingNameValuePairs = Multi(s_withPackingOptionAliases, "Overrides the StructLayoutAttribute.Pack property for the given type. Supports wildcards.");
+ private static readonly CommandLineOption s_withReadonlys = Multi(s_withReadonlyOptionAliases, "Add the readonly modifier to a given instance method. Supports wildcards.");
+ private static readonly CommandLineOption s_withSetLastErrors = Multi(s_withSetLastErrorOptionAliases, "Add the SetLastError=true modifier or SetsSystemLastError attribute to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.");
+ private static readonly CommandLineOption s_withSuppressGCTransitions = Multi(s_withSuppressGCTransitionOptionAliases, "Add the SuppressGCTransition calling convention to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.");
+ private static readonly CommandLineOption s_withTransparentStructNameValuePairs = Multi(s_withTransparentStructOptionAliases, "A remapped type name to be treated as a transparent wrapper during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withTypeNameValuePairs = Multi(s_withTypeOptionAliases, "A type to be used for the given enum declaration during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_withUsingNameValuePairs = Multi(s_withUsingOptionAliases, "A using directive to be included for the given remapped declaration name during binding generation. Supports wildcards.");
+ private static readonly CommandLineOption s_helpOption = Flag(s_helpOptionAliases, "Show help and usage information");
+
+ private static readonly CommandLineOption[] s_options =
[
- new TwoColumnHelpRow("?, h, help", "Show help and usage information for -c, --config"),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Codegen Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("compatible-codegen", "Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation."),
- new TwoColumnHelpRow("default-codegen", "Bindings should be generated for the current LTS version of .NET/C#. This is currently .NET 8/C# 12."),
- new TwoColumnHelpRow("latest-codegen", "Bindings should be generated for the current STS version of .NET/C#. This is currently .NET 10/C# 14."),
- new TwoColumnHelpRow("preview-codegen", "Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# File Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("single-file", "Bindings should be generated to a single output file. This is the default."),
- new TwoColumnHelpRow("multi-file", "Bindings should be generated so there is approximately one type per file."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Type Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("unix-types", "Bindings should be generated assuming Unix defaults. This is the default on Unix platforms."),
- new TwoColumnHelpRow("windows-types", "Bindings should be generated assuming Windows defaults. This is the default on Windows platforms."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Exclusion Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("exclude-anonymous-field-helpers", "The helper ref properties generated for fields in nested anonymous structs and unions should not be generated."),
- new TwoColumnHelpRow("exclude-com-proxies", "Types recognized as COM proxies should not have bindings generated. These are currently function declarations ending with _UserFree, _UserMarshal, _UserSize, _UserUnmarshal, _Proxy, or _Stub."),
- new TwoColumnHelpRow("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"),
- new TwoColumnHelpRow("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."),
- new TwoColumnHelpRow("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."),
- new TwoColumnHelpRow("exclude-fnptr-codegen", "Generated bindings for latest or preview codegen should not use function pointers."),
- new TwoColumnHelpRow("exclude-funcs-with-body", "Bindings for functions with bodies should not be generated."),
- new TwoColumnHelpRow("exclude-using-statics-for-enums", "Enum usages should be fully qualified and should not include a corresponding 'using static EnumName;'"),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Vtbl Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("explicit-vtbls", "VTBLs should have an explicit type generated with named fields per entry."),
- new TwoColumnHelpRow("implicit-vtbls", "VTBLs should be implicit to reduce metadata bloat. This is the current default"),
- new TwoColumnHelpRow("trimmable-vtbls", "VTBLs should be defined but not used in helper methods to reduce metadata bloat when trimming."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Test Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("generate-tests-nunit", "Basic tests validating size, blittability, and associated metadata should be generated for NUnit."),
- new TwoColumnHelpRow("generate-tests-xunit", "Basic tests validating size, blittability, and associated metadata should be generated for XUnit."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Generation Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("generate-aggressive-inlining", "[MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to generated helper functions."),
- new TwoColumnHelpRow("generate-callconv-member-function", "Instance function pointers should use [CallConvMemberFunction] where applicable."),
- new TwoColumnHelpRow("generate-cpp-attributes", "[CppAttributeList(\"\")] should be generated to document the encountered C++ attributes."),
- new TwoColumnHelpRow("generate-disable-runtime-marshalling", "[assembly: DisableRuntimeMarshalling] should be generated."),
- new TwoColumnHelpRow("generate-doc-includes", " xml documentation tags should be generated for declarations."),
- new TwoColumnHelpRow("generate-file-scoped-namespaces", "Namespaces should be scoped to the file to reduce nesting."),
- new TwoColumnHelpRow("generate-fixed-buffer-indexer-overloads", "Fixed sized buffer helper types should generate additional uint, nint, and nuint indexer overloads."),
- new TwoColumnHelpRow("generate-guid-member", "Types with an associated GUID should have a corresponding member generated."),
- new TwoColumnHelpRow("generate-helper-types", "Code files should be generated for various helper attributes and declared transparent structs."),
- new TwoColumnHelpRow("generate-macro-bindings", "Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones."),
- new TwoColumnHelpRow("generate-marker-interfaces", "Bindings for marker interfaces representing native inheritance hierarchies should be generated."),
- new TwoColumnHelpRow("generate-native-bitfield-attribute", "[NativeBitfield(\"\", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout."),
- new TwoColumnHelpRow("generate-native-inheritance-attribute", "[NativeInheritance(\"\")] attribute should be generated to document the encountered C++ base type."),
- new TwoColumnHelpRow("generate-generic-pointer-wrapper", "Pointer should be used for limited generic type support."),
- new TwoColumnHelpRow("generate-setslastsystemerror-attribute", "[SetsLastSystemError] attribute should be generated rather than using SetLastError = true."),
- new TwoColumnHelpRow("generate-template-bindings", "Bindings for template-definitions should be generated. This is currently experimental."),
- new TwoColumnHelpRow("generate-unmanaged-constants", "Unmanaged constants should be generated using static ref readonly properties. This is currently experimental."),
- new TwoColumnHelpRow("generate-vtbl-index-attribute", "[VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Stripping Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("strip-enum-member-type-name", "Strips the enum type name from the beginning of its member names."),
-
- new TwoColumnHelpRow("", ""),
- new TwoColumnHelpRow("# Logging Options", ""),
- new TwoColumnHelpRow("", ""),
-
- new TwoColumnHelpRow("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."),
- new TwoColumnHelpRow("log-potential-typedef-remappings", "A list of potential typedef remappings should be generated. This can help identify missing remappings."),
- new TwoColumnHelpRow("log-visited-files", "A list of the visited files should be generated. This can help identify traversal issues."),
+ s_additionalOption,
+ s_configOption,
+ s_defineMacros,
+ s_excludedNames,
+ s_files,
+ s_fileDirectory,
+ s_headerFile,
+ s_includedNames,
+ s_includeDirectories,
+ s_language,
+ s_libraryPath,
+ s_methodClassName,
+ s_namespaceName,
+ s_outputMode,
+ s_outputLocation,
+ s_methodPrefixToStrip,
+ s_nativeTypeNamesToStrip,
+ s_remappedNameValuePairs,
+ s_remappedTypeNameValuePairs,
+ s_remappedFieldNameValuePairs,
+ s_std,
+ s_testOutputLocation,
+ s_traversalNames,
+ s_versionOption,
+ s_withAccessSpecifierNameValuePairs,
+ s_withAttributeNameValuePairs,
+ s_withCallConvNameValuePairs,
+ s_withClassNameValuePairs,
+ s_withGuidNameValuePairs,
+ s_withLengthNameValuePairs,
+ s_withLibraryPathNameValuePairs,
+ s_withManualImports,
+ s_withNamespaceNameValuePairs,
+ s_withPackingNameValuePairs,
+ s_withReadonlys,
+ s_withSetLastErrors,
+ s_withSuppressGCTransitions,
+ s_withTransparentStructNameValuePairs,
+ s_withTypeNameValuePairs,
+ s_withUsingNameValuePairs,
+ s_helpOption,
];
- private static Option GetAdditionalOption()
- {
- return new Option(
- aliases: s_additionalOptionAliases,
- description: "An argument to pass to Clang when parsing the input files.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetConfigOption()
- {
- return new Option(
- aliases: s_configOptionAliases,
- description: "A configuration option that controls how the bindings are generated. Specify 'help' to see the available options.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetDefineMacroOption()
- {
- return new Option(
- aliases: s_defineMacroOptionAliases,
- description: "Define to (or 1 if omitted).",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetExcludeOption()
- {
- return new Option(
- aliases: s_excludeOptionAliases,
- description: "A declaration name to exclude from binding generation.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetFileOption()
- {
- return new Option(
- aliases: s_fileOptionAliases,
- description: "A file to parse and generate bindings for.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetFileDirectoryOption()
- {
- return new Option(
- aliases: s_fileDirectionOptionAliases,
- description: "The base path for files to parse.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetHeaderOption()
- {
- return new Option(
- aliases: s_headerOptionAliases,
- description: "A file which contains the header to prefix every generated file with.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetIncludeOption()
- {
- return new Option(
- aliases: s_includeOptionAliases,
- description: "A declaration name to include in binding generation.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetIncludeDirectoryOption()
- {
- return new Option(
- aliases: s_includeDirectoryOptionAliases,
- description: "Add directory to include search path.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
+ private static readonly CommandLineParser s_parser = new(s_options);
- private static Option GetLanguageOption()
- {
- return new Option(
- aliases: s_languageOptionAliases,
- description: "Treat subsequent input files as having type .",
- getDefaultValue: () => "c++"
- ).FromAmong("c", "c++");
- }
-
- private static Option GetLibraryOption()
- {
- return new Option(
- aliases: s_libraryOptionAliases,
- description: "The string to use in the DllImport attribute used when generating bindings.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetMethodClassNameOption()
- {
- return new Option(
- aliases: s_methodClassNameOptionAliases,
- description: "The name of the static class that will contain the generated method bindings.",
- getDefaultValue: () => "Methods"
- );
- }
-
- private static Option GetNamespaceOption()
- {
- return new Option(
- aliases: s_namespaceOptionAliases,
- description: "The namespace in which to place the generated bindings.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetNativeTypeNamesStripOption()
- {
- return new Option(
- aliases: s_nativeTypeNamesStripOptionAliases,
- description: "The contents to strip from the generated NativeTypeName attributes.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetOutputModeOption()
- {
- return new Option(
- aliases: s_outputModeOptionAliases,
- description: "The mode describing how the information collected from the headers are presented in the resultant bindings.",
- getDefaultValue: () => PInvokeGeneratorOutputMode.CSharp
- );
- }
-
- private static Option GetOutputOption()
- {
- return new Option(
- aliases: s_outputOptionAliases,
- description: "The output location to write the generated bindings to.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetPrefixStripOption()
- {
- return new Option(
- aliases: s_prefixStripOptionAliases,
- description: "The prefix to strip from the generated method bindings.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetRemapOption()
- {
- return new Option(
- aliases: s_remapOptionAliases,
- description: "A declaration name to be remapped to another name during binding generation.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetRemapTypeOption()
- {
- return new Option(
- aliases: s_remapTypeOptionAliases,
- description: "A type (record or enum) declaration name to be remapped to another name during binding generation. Takes precedence over --remap and is useful when a type and field share a name.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetRemapFieldOption()
- {
- return new Option(
- aliases: s_remapFieldOptionAliases,
- description: "A field declaration name to be remapped to another name during binding generation. Takes precedence over --remap and is useful when a type and field share a name.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static RootCommand GetRootCommand()
- {
- var rootCommand = new RootCommand("ClangSharp P/Invoke Binding Generator")
- {
- s_additionalOption,
- s_configOption,
- s_defineMacros,
- s_excludedNames,
- s_files,
- s_fileDirectory,
- s_headerFile,
- s_includedNames,
- s_includeDirectories,
- s_language,
- s_libraryPath,
- s_methodClassName,
- s_namespaceName,
- s_outputMode,
- s_outputLocation,
- s_methodPrefixToStrip,
- s_nativeTypeNamesToStrip,
- s_remappedNameValuePairs,
- s_remappedTypeNameValuePairs,
- s_remappedFieldNameValuePairs,
- s_std,
- s_testOutputLocation,
- s_traversalNames,
- s_versionOption,
- s_withAccessSpecifierNameValuePairs,
- s_withAttributeNameValuePairs,
- s_withCallConvNameValuePairs,
- s_withClassNameValuePairs,
- s_withGuidNameValuePairs,
- s_withLengthNameValuePairs,
- s_withLibraryPathNameValuePairs,
- s_withManualImports,
- s_withNamespaceNameValuePairs,
- s_withPackingNameValuePairs,
- s_withReadonlys,
- s_withSetLastErrors,
- s_withSuppressGCTransitions,
- s_withTransparentStructNameValuePairs,
- s_withTypeNameValuePairs,
- s_withUsingNameValuePairs
- };
- Handler.SetHandler(rootCommand, (Action)Run);
- return rootCommand;
- }
-
- private static Option GetStdOption()
- {
- return new Option(
- aliases: s_stdOptionAliases,
- description: "Language standard to compile for.",
- getDefaultValue: () => ""
- );
- }
-
- private static Option GetTestOutputOption()
- {
- return new Option(
- aliases: s_testOutputOptionAliases,
- description: "The output location to write the generated tests to.",
- getDefaultValue: () => string.Empty
- );
- }
-
- private static Option GetVersionOption()
- {
- return new Option(
- aliases: s_versionOptionAliases,
- description: "Prints the current version information for the tool and its native dependencies."
- ) {
- Arity = ArgumentArity.Zero
- };
- }
-
- private static Option GetTraverseOption()
- {
- return new Option(
- aliases: s_traverseOptionAliases,
- description: "A file name included either directly or indirectly by -f that should be traversed during binding generation.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithAccessSpecifierOption()
- {
- return new Option(
- aliases: s_withAccessSpecifierOptionAliases,
- description: "An access specifier to be used with the given qualified or remapped declaration name during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithAttributeOption()
- {
- return new Option(
- aliases: s_withAttributeOptionAliases,
- description: "An attribute to be added to the given remapped declaration name during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithCallConvOption()
- {
- return new Option(
- aliases: s_withCallConvOptionAliases,
- description: "A calling convention to be used for the given declaration during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithClassOption()
- {
- return new Option(
- aliases: s_withClassOptionAliases,
- description: "A class to be used for the given remapped constant or function declaration name during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithGuidOption()
- {
- return new Option(
- aliases: s_withGuidOptionAliases,
- description: "A GUID to be used for the given declaration during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithLengthOption()
- {
- return new Option(
- aliases: s_withLengthOptionAliases,
- description: "A length to be used for the given declaration during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithLibraryPathOption()
- {
- return new Option(
- aliases: s_withLibraryPathOptionAliases,
- description: "A library path to be used for the given declaration during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithManualImportOption()
- {
- return new Option(
- aliases: s_withManualImportOptionAliases,
- description: "A remapped function name to be treated as a manual import during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithNamespaceOption()
- {
- return new Option(
- aliases: s_withNamespaceOptionAliases,
- description: "A namespace to be used for the given remapped declaration name during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithReadonlyOption()
- {
- return new Option(
- aliases: s_withReadonlyOptionAliases,
- description: "Add the readonly modifier to a given instance method. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithSetLastErrorOption()
- {
- return new Option(
- aliases: s_withSetLastErrorOptionAliases,
- description: "Add the SetLastError=true modifier or SetsSystemLastError attribute to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithSuppressGCTransitionOption()
- {
- return new Option(
- aliases: s_withSuppressGCTransitionOptionAliases,
- description: "Add the SuppressGCTransition calling convention to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
-
- private static Option GetWithTransparentStructOption()
- {
- return new Option(
- aliases: s_withTransparentStructOptionAliases,
- description: "A remapped type name to be treated as a transparent wrapper during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
+ private static readonly HelpRow[] s_configOptions =
+ [
+ new HelpRow("?, h, help", "Show help and usage information for -c, --config"),
+
+ new HelpRow("", ""),
+ new HelpRow("# Codegen Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("compatible-codegen", "Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation."),
+ new HelpRow("default-codegen", "Bindings should be generated for the current LTS version of .NET/C#. This is currently .NET 8/C# 12."),
+ new HelpRow("latest-codegen", "Bindings should be generated for the current STS version of .NET/C#. This is currently .NET 10/C# 14."),
+ new HelpRow("preview-codegen", "Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14."),
+
+ new HelpRow("", ""),
+ new HelpRow("# File Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("single-file", "Bindings should be generated to a single output file. This is the default."),
+ new HelpRow("multi-file", "Bindings should be generated so there is approximately one type per file."),
+
+ new HelpRow("", ""),
+ new HelpRow("# Type Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("unix-types", "Bindings should be generated assuming Unix defaults. This is the default on Unix platforms."),
+ new HelpRow("windows-types", "Bindings should be generated assuming Windows defaults. This is the default on Windows platforms."),
+
+ new HelpRow("", ""),
+ new HelpRow("# Exclusion Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("exclude-anonymous-field-helpers", "The helper ref properties generated for fields in nested anonymous structs and unions should not be generated."),
+ new HelpRow("exclude-com-proxies", "Types recognized as COM proxies should not have bindings generated. These are currently function declarations ending with _UserFree, _UserMarshal, _UserSize, _UserUnmarshal, _Proxy, or _Stub."),
+ new HelpRow("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"),
+ new HelpRow("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."),
+ new HelpRow("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."),
+ new HelpRow("exclude-fnptr-codegen", "Generated bindings for latest or preview codegen should not use function pointers."),
+ new HelpRow("exclude-funcs-with-body", "Bindings for functions with bodies should not be generated."),
+ new HelpRow("exclude-using-statics-for-enums", "Enum usages should be fully qualified and should not include a corresponding 'using static EnumName;'"),
+
+ new HelpRow("", ""),
+ new HelpRow("# Vtbl Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("explicit-vtbls", "VTBLs should have an explicit type generated with named fields per entry."),
+ new HelpRow("implicit-vtbls", "VTBLs should be implicit to reduce metadata bloat. This is the current default"),
+ new HelpRow("trimmable-vtbls", "VTBLs should be defined but not used in helper methods to reduce metadata bloat when trimming."),
+
+ new HelpRow("", ""),
+ new HelpRow("# Test Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("generate-tests-nunit", "Basic tests validating size, blittability, and associated metadata should be generated for NUnit."),
+ new HelpRow("generate-tests-xunit", "Basic tests validating size, blittability, and associated metadata should be generated for XUnit."),
+
+ new HelpRow("", ""),
+ new HelpRow("# Generation Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("generate-aggressive-inlining", "[MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to generated helper functions."),
+ new HelpRow("generate-callconv-member-function", "Instance function pointers should use [CallConvMemberFunction] where applicable."),
+ new HelpRow("generate-cpp-attributes", "[CppAttributeList(\"\")] should be generated to document the encountered C++ attributes."),
+ new HelpRow("generate-disable-runtime-marshalling", "[assembly: DisableRuntimeMarshalling] should be generated."),
+ new HelpRow("generate-doc-includes", " xml documentation tags should be generated for declarations."),
+ new HelpRow("generate-file-scoped-namespaces", "Namespaces should be scoped to the file to reduce nesting."),
+ new HelpRow("generate-fixed-buffer-indexer-overloads", "Fixed sized buffer helper types should generate additional uint, nint, and nuint indexer overloads."),
+ new HelpRow("generate-guid-member", "Types with an associated GUID should have a corresponding member generated."),
+ new HelpRow("generate-helper-types", "Code files should be generated for various helper attributes and declared transparent structs."),
+ new HelpRow("generate-macro-bindings", "Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones."),
+ new HelpRow("generate-marker-interfaces", "Bindings for marker interfaces representing native inheritance hierarchies should be generated."),
+ new HelpRow("generate-native-bitfield-attribute", "[NativeBitfield(\"\", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout."),
+ new HelpRow("generate-native-inheritance-attribute", "[NativeInheritance(\"\")] attribute should be generated to document the encountered C++ base type."),
+ new HelpRow("generate-generic-pointer-wrapper", "Pointer should be used for limited generic type support."),
+ new HelpRow("generate-setslastsystemerror-attribute", "[SetsLastSystemError] attribute should be generated rather than using SetLastError = true."),
+ new HelpRow("generate-template-bindings", "Bindings for template-definitions should be generated. This is currently experimental."),
+ new HelpRow("generate-unmanaged-constants", "Unmanaged constants should be generated using static ref readonly properties. This is currently experimental."),
+ new HelpRow("generate-vtbl-index-attribute", "[VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method."),
+
+ new HelpRow("", ""),
+ new HelpRow("# Stripping Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("strip-enum-member-type-name", "Strips the enum type name from the beginning of its member names."),
+
+ new HelpRow("", ""),
+ new HelpRow("# Logging Options", ""),
+ new HelpRow("", ""),
+
+ new HelpRow("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."),
+ new HelpRow("log-potential-typedef-remappings", "A list of potential typedef remappings should be generated. This can help identify missing remappings."),
+ new HelpRow("log-visited-files", "A list of the visited files should be generated. This can help identify traversal issues."),
+ ];
- private static Option GetWithTypeOption()
- {
- return new Option(
- aliases: s_withTypeOptionAliases,
- description: "A type to be used for the given enum declaration during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
+ private static CommandLineOption Multi(string[] aliases, string description) => new(aliases, description, CommandLineOptionKind.MultipleValue);
- private static Option GetWithUsingOption()
- {
- return new Option(
- aliases: s_withUsingOptionAliases,
- description: "A using directive to be included for the given remapped declaration name during binding generation. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
+ private static CommandLineOption Single(string[] aliases, string description, string defaultValue = "", string? valueName = null, string[]? allowedValues = null) => new(aliases, description, CommandLineOptionKind.SingleValue, valueName, defaultValue, allowedValues);
- private static Option GetWithPackingOption()
- {
- return new Option(
- aliases: s_withPackingOptionAliases,
- description: "Overrides the StructLayoutAttribute.Pack property for the given type. Supports wildcards.",
- getDefaultValue: Array.Empty
- ) {
- AllowMultipleArgumentsPerToken = true
- };
- }
+ private static CommandLineOption Flag(string[] aliases, string description) => new(aliases, description, CommandLineOptionKind.Flag);
}
diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs
index eb3e58a6..fc8bd19d 100644
--- a/sources/ClangSharpPInvokeGenerator/Program.cs
+++ b/sources/ClangSharpPInvokeGenerator/Program.cs
@@ -2,17 +2,11 @@
using System;
using System.Collections.Generic;
-using System.CommandLine;
-using System.CommandLine.Builder;
-using System.CommandLine.Help;
-using System.CommandLine.Invocation;
-using System.CommandLine.IO;
-using System.CommandLine.Parsing;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Runtime.InteropServices;
-using System.Threading.Tasks;
using ClangSharp.Interop;
using static ClangSharp.Interop.CXDiagnosticSeverity;
using static ClangSharp.Interop.CXErrorCode;
@@ -22,92 +16,91 @@ namespace ClangSharp;
internal static partial class Program
{
- public static IEnumerable GetExtendedHelp(HelpContext context)
+ private const string Name = "ClangSharpPInvokeGenerator";
+ private const string Description = "ClangSharp P/Invoke Binding Generator";
+
+ // The clang release the tool targets, tracked via the assembly version (major.minor.build)
+ // so it stays in sync with VersionPrefix and matches the clang/clangsharp lines below it.
+ private static readonly string Version = GetVersion();
+
+ private const string WildcardsTitle = "Wildcards:";
+ private const string Wildcards = "You can use * as catch-all rule for remapping procedures. For example if you want make all of your generated code internal you can use --with-access-specifier *=Internal.";
+
+ public static int Main(params string[] args)
{
- foreach (var sectionDelegate in HelpBuilder.Default.GetLayout())
+ s_parser.Parse(args);
+
+ if (s_helpOption.IsPresent)
{
- yield return sectionDelegate;
+ s_parser.WriteHelp(Console.Out, Name, Description, WildcardsTitle, Wildcards);
+ return 0;
}
- yield return _ => {
- Console.WriteLine("Wildcards:");
- Console.WriteLine("You can use * as catch-all rule for remapping procedures. For example if you want make all of your generated code internal you can use --with-access-specifier *=Internal.");
- };
+ if (s_versionOption.IsPresent)
+ {
+ Console.WriteLine($"{Description} version {Version}");
+ Console.WriteLine($" {clang.getClangVersion()}");
+ Console.WriteLine($" {clangsharp.getVersion()}");
+ return 0;
+ }
+
+ return Run();
}
- public static async Task Main(params string[] args)
+ private static string GetVersion()
{
- var parser = new CommandLineBuilder(s_rootCommand)
- .UseHelp(context => context.HelpBuilder.CustomizeLayout(GetExtendedHelp))
- .UseEnvironmentVariableDirective()
- .UseParseDirective()
- .UseSuggestDirective()
- .RegisterWithDotnetSuggest()
- .UseTypoCorrections()
- .UseParseErrorReporting()
- .UseExceptionHandler()
- .CancelOnProcessTermination()
- .Build();
- return await parser.InvokeAsync(args).ConfigureAwait(false);
+ var version = Assembly.GetExecutingAssembly().GetName().Version;
+ return (version is not null) ? $"{version.Major}.{version.Minor}.{version.Build}" : "";
}
- public static void Run(InvocationContext context)
+ public static int Run()
{
- ArgumentNullException.ThrowIfNull(context);
-
- var additionalArgs = context.ParseResult.GetValueForOption(s_additionalOption) ?? [];
- var configSwitches = context.ParseResult.GetValueForOption(s_configOption) ?? [];
- var defineMacros = context.ParseResult.GetValueForOption(s_defineMacros) ?? [];
- var excludedNames = context.ParseResult.GetValueForOption(s_excludedNames) ?? [];
- var files = context.ParseResult.GetValueForOption(s_files) ?? [];
- var fileDirectory = context.ParseResult.GetValueForOption(s_fileDirectory) ?? "";
- var headerFile = context.ParseResult.GetValueForOption(s_headerFile) ?? "";
- var includedNames = context.ParseResult.GetValueForOption(s_includedNames) ?? [];
- var includeDirectories = context.ParseResult.GetValueForOption(s_includeDirectories) ?? [];
- var language = context.ParseResult.GetValueForOption(s_language) ?? "";
- var libraryPath = context.ParseResult.GetValueForOption(s_libraryPath) ?? "";
- var methodClassName = context.ParseResult.GetValueForOption(s_methodClassName) ?? "";
- var methodPrefixToStrip = context.ParseResult.GetValueForOption(s_methodPrefixToStrip) ?? "";
- var nativeTypeNamesToStrip = context.ParseResult.GetValueForOption(s_nativeTypeNamesToStrip) ?? [];
- var namespaceName = context.ParseResult.GetValueForOption(s_namespaceName) ?? "";
- var outputLocation = context.ParseResult.GetValueForOption(s_outputLocation) ?? "";
- var outputMode = context.ParseResult.GetValueForOption(s_outputMode);
- var remappedNameValuePairs = context.ParseResult.GetValueForOption(s_remappedNameValuePairs) ?? [];
- var remappedTypeNameValuePairs = context.ParseResult.GetValueForOption(s_remappedTypeNameValuePairs) ?? [];
- var remappedFieldNameValuePairs = context.ParseResult.GetValueForOption(s_remappedFieldNameValuePairs) ?? [];
- var std = context.ParseResult.GetValueForOption(s_std) ?? "";
- var testOutputLocation = context.ParseResult.GetValueForOption(s_testOutputLocation) ?? "";
- var traversalNames = context.ParseResult.GetValueForOption(s_traversalNames) ?? [];
- var withAccessSpecifierNameValuePairs = context.ParseResult.GetValueForOption(s_withAccessSpecifierNameValuePairs) ?? [];
- var withAttributeNameValuePairs = context.ParseResult.GetValueForOption(s_withAttributeNameValuePairs) ?? [];
- var withCallConvNameValuePairs = context.ParseResult.GetValueForOption(s_withCallConvNameValuePairs) ?? [];
- var withClassNameValuePairs = context.ParseResult.GetValueForOption(s_withClassNameValuePairs) ?? [];
- var withGuidNameValuePairs = context.ParseResult.GetValueForOption(s_withGuidNameValuePairs) ?? [];
- var withLengthNameValuePairs = context.ParseResult.GetValueForOption(s_withLengthNameValuePairs) ?? [];
- var withLibraryPathNameValuePairs = context.ParseResult.GetValueForOption(s_withLibraryPathNameValuePairs) ?? [];
- var withManualImports = context.ParseResult.GetValueForOption(s_withManualImports) ?? [];
- var withNamespaceNameValuePairs = context.ParseResult.GetValueForOption(s_withNamespaceNameValuePairs) ?? [];
- var withReadonlys = context.ParseResult.GetValueForOption(s_withReadonlys) ?? [];
- var withSetLastErrors = context.ParseResult.GetValueForOption(s_withSetLastErrors) ?? [];
- var withSuppressGCTransitions = context.ParseResult.GetValueForOption(s_withSuppressGCTransitions) ?? [];
- var withTransparentStructNameValuePairs = context.ParseResult.GetValueForOption(s_withTransparentStructNameValuePairs) ?? [];
- var withTypeNameValuePairs = context.ParseResult.GetValueForOption(s_withTypeNameValuePairs) ?? [];
- var withUsingNameValuePairs = context.ParseResult.GetValueForOption(s_withUsingNameValuePairs) ?? [];
- var withPackingNameValuePairs = context.ParseResult.GetValueForOption(s_withPackingNameValuePairs) ?? [];
-
- var versionResult = context.ParseResult.FindResultFor(s_versionOption);
-
- if (versionResult is not null)
+ var errorList = new List(s_parser.Errors);
+
+ var additionalArgs = s_additionalOption.GetValues();
+ var configSwitches = s_configOption.GetValues();
+ var defineMacros = s_defineMacros.GetValues();
+ var excludedNames = s_excludedNames.GetValues();
+ var files = s_files.GetValues();
+ var fileDirectory = s_fileDirectory.SingleValue;
+ var headerFile = s_headerFile.SingleValue;
+ var includedNames = s_includedNames.GetValues();
+ var includeDirectories = s_includeDirectories.GetValues();
+ var language = s_language.SingleValue;
+ var libraryPath = s_libraryPath.SingleValue;
+ var methodClassName = s_methodClassName.SingleValue;
+ var methodPrefixToStrip = s_methodPrefixToStrip.SingleValue;
+ var nativeTypeNamesToStrip = s_nativeTypeNamesToStrip.GetValues();
+ var namespaceName = s_namespaceName.SingleValue;
+ var outputLocation = s_outputLocation.SingleValue;
+ var remappedNameValuePairs = s_remappedNameValuePairs.GetValues();
+ var remappedTypeNameValuePairs = s_remappedTypeNameValuePairs.GetValues();
+ var remappedFieldNameValuePairs = s_remappedFieldNameValuePairs.GetValues();
+ var std = s_std.SingleValue;
+ var testOutputLocation = s_testOutputLocation.SingleValue;
+ var traversalNames = s_traversalNames.GetValues();
+ var withAccessSpecifierNameValuePairs = s_withAccessSpecifierNameValuePairs.GetValues();
+ var withAttributeNameValuePairs = s_withAttributeNameValuePairs.GetValues();
+ var withCallConvNameValuePairs = s_withCallConvNameValuePairs.GetValues();
+ var withClassNameValuePairs = s_withClassNameValuePairs.GetValues();
+ var withGuidNameValuePairs = s_withGuidNameValuePairs.GetValues();
+ var withLengthNameValuePairs = s_withLengthNameValuePairs.GetValues();
+ var withLibraryPathNameValuePairs = s_withLibraryPathNameValuePairs.GetValues();
+ var withManualImports = s_withManualImports.GetValues();
+ var withNamespaceNameValuePairs = s_withNamespaceNameValuePairs.GetValues();
+ var withReadonlys = s_withReadonlys.GetValues();
+ var withSetLastErrors = s_withSetLastErrors.GetValues();
+ var withSuppressGCTransitions = s_withSuppressGCTransitions.GetValues();
+ var withTransparentStructNameValuePairs = s_withTransparentStructNameValuePairs.GetValues();
+ var withTypeNameValuePairs = s_withTypeNameValuePairs.GetValues();
+ var withUsingNameValuePairs = s_withUsingNameValuePairs.GetValues();
+ var withPackingNameValuePairs = s_withPackingNameValuePairs.GetValues();
+
+ if (!Enum.TryParse(s_outputMode.SingleValue, ignoreCase: true, out var outputMode))
{
- context.Console.WriteLine($"{s_rootCommand.Description} version 21.1.8");
- context.Console.WriteLine($" {clang.getClangVersion()}");
- context.Console.WriteLine($" {clangsharp.getVersion()}");
- context.ExitCode = 0;
- return;
+ errorList.Add($"Error: Unrecognized output mode: {s_outputMode.SingleValue}. Must be one of CSharp or Xml");
}
- var errorList = new List();
-
if (files.Length == 0)
{
errorList.Add("Error: No input C/C++ files provided. Use --file or -f");
@@ -502,34 +495,24 @@ public static void Run(InvocationContext context)
if (printConfigHelp)
{
- var helpBuilder = new CustomHelpBuilder(context.Console, context.LocalizationResources);
-
- helpBuilder.Write(s_configOption);
- helpBuilder.WriteLine();
- helpBuilder.Write(s_configOptions);
-
- context.ExitCode = -1;
- return;
+ CommandLineParser.WriteOptionHelp(Console.Out, s_configOption, s_configOptions);
+ return -1;
}
if (errorList.Count != 0)
{
- context.Console.Error.Write($"Error in args for '{files.FirstOrDefault()}'");
- context.Console.Error.Write(Environment.NewLine);
+ Console.Error.Write($"Error in args for '{files.FirstOrDefault()}'");
+ Console.Error.Write(Environment.NewLine);
foreach (var error in errorList)
{
- context.Console.Error.Write(error);
- context.Console.Error.Write(Environment.NewLine);
+ Console.Error.Write(error);
+ Console.Error.Write(Environment.NewLine);
}
- context.Console.Error.Write(Environment.NewLine);
-
- using var textWriter = context.Console.Out.CreateTextWriter();
- var customHelpBuilder = new CustomHelpBuilder(context.Console, context.LocalizationResources);
- customHelpBuilder.Write(s_rootCommand, textWriter);
+ Console.Error.Write(Environment.NewLine);
- context.ExitCode = -1;
- return;
+ s_parser.WriteHelp(Console.Out, Name, Description, WildcardsTitle, Wildcards);
+ return -1;
}
var clangCommandLineArgs = string.IsNullOrWhiteSpace(std)
@@ -599,19 +582,19 @@ public static void Run(InvocationContext context)
if (translationUnitError != CXError_Success)
{
- context.Console.WriteLine($"Error: Parsing failed for '{filePath}' due to '{translationUnitError}'.");
+ Console.WriteLine($"Error: Parsing failed for '{filePath}' due to '{translationUnitError}'.");
skipProcessing = true;
}
else if (handle.NumDiagnostics != 0)
{
- context.Console.WriteLine($"Diagnostics for '{filePath}':");
+ Console.WriteLine($"Diagnostics for '{filePath}':");
for (uint i = 0; i < handle.NumDiagnostics; ++i)
{
using var diagnostic = handle.GetDiagnostic(i);
- context.Console.Write(" ");
- context.Console.WriteLine(diagnostic.Format(CXDiagnostic.DefaultDisplayOptions).ToString());
+ Console.Write(" ");
+ Console.WriteLine(diagnostic.Format(CXDiagnostic.DefaultDisplayOptions).ToString());
skipProcessing |= diagnostic.Severity == CXDiagnostic_Error;
skipProcessing |= diagnostic.Severity == CXDiagnostic_Fatal;
@@ -620,8 +603,8 @@ public static void Run(InvocationContext context)
if (skipProcessing)
{
- context.Console.WriteLine($"Skipping '{filePath}' due to one or more errors listed above.");
- context.Console.WriteLine("");
+ Console.WriteLine($"Skipping '{filePath}' due to one or more errors listed above.");
+ Console.WriteLine("");
exitCode = -1;
continue;
@@ -634,12 +617,12 @@ public static void Run(InvocationContext context)
using var translationUnit = TranslationUnit.GetOrCreate(handle);
Debug.Assert(translationUnit is not null);
- context.Console.WriteLine($"Processing '{filePath}'");
+ Console.WriteLine($"Processing '{filePath}'");
pinvokeGenerator.GenerateBindings(translationUnit, filePath, clangCommandLineArgs, translationFlags);
}
catch (Exception e)
{
- context.Console.WriteLine(e.ToString());
+ Console.WriteLine(e.ToString());
}
#pragma warning restore CA1031 // Do not catch general exception types
@@ -647,12 +630,12 @@ public static void Run(InvocationContext context)
if (pinvokeGenerator.Diagnostics.Count != 0)
{
- context.Console.WriteLine($"Diagnostics for binding generation of {pinvokeGenerator.FilePath}:");
+ Console.WriteLine($"Diagnostics for binding generation of {pinvokeGenerator.FilePath}:");
foreach (var diagnostic in pinvokeGenerator.Diagnostics)
{
- context.Console.Write(" ");
- context.Console.WriteLine(diagnostic.ToString());
+ Console.Write(" ");
+ Console.WriteLine(diagnostic.ToString());
if (diagnostic.Level == DiagnosticLevel.Warning)
{
@@ -676,7 +659,7 @@ public static void Run(InvocationContext context)
}
}
- context.ExitCode = exitCode;
+ return exitCode;
}
private static void ParseKeyValuePairs(IEnumerable keyValuePairs, List errorList, out Dictionary result)
diff --git a/tests/ClangSharpPInvokeGenerator.UnitTests/ClangSharpPInvokeGenerator.UnitTests.csproj b/tests/ClangSharpPInvokeGenerator.UnitTests/ClangSharpPInvokeGenerator.UnitTests.csproj
new file mode 100644
index 00000000..160a63ea
--- /dev/null
+++ b/tests/ClangSharpPInvokeGenerator.UnitTests/ClangSharpPInvokeGenerator.UnitTests.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+ ClangSharp.UnitTests
+ net10.0
+
+
+
+
+
+
+
+
+ $(NoWarn);CA1515;CA1707;CA1711;CA1861;IDE0130
+
+
+
+
+
+
+
diff --git a/tests/ClangSharpPInvokeGenerator.UnitTests/CommandLineParserTests.cs b/tests/ClangSharpPInvokeGenerator.UnitTests/CommandLineParserTests.cs
new file mode 100644
index 00000000..34e8755c
--- /dev/null
+++ b/tests/ClangSharpPInvokeGenerator.UnitTests/CommandLineParserTests.cs
@@ -0,0 +1,179 @@
+// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.
+
+using System.IO;
+using NUnit.Framework;
+
+namespace ClangSharp.UnitTests;
+
+public sealed class CommandLineParserTests
+{
+ private static CommandLineOption Multi(params string[] aliases)
+ => new(aliases, "", CommandLineOptionKind.MultipleValue);
+
+ private static CommandLineOption Single(params string[] aliases)
+ => new(aliases, "", CommandLineOptionKind.SingleValue);
+
+ private static CommandLineOption Flag(params string[] aliases)
+ => new(aliases, "", CommandLineOptionKind.Flag);
+
+ // Regression test for dotnet/clangsharp#554: a `name=value` token whose value contains
+ // spaces must be preserved as a single value rather than split on whitespace.
+ [Test]
+ public void SpacedValueIsPreservedAsSingleValue()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ parser.Parse(["--remap", "__arglist=@params string[] args"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "__arglist=@params string[] args" }));
+ }
+
+ [Test]
+ public void MultiplePairsAcrossSeparateTokensAreDistinctValues()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ parser.Parse(["--remap", "A=B", "C=D"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "A=B", "C=D" }));
+ }
+
+ [Test]
+ public void InlineEqualsFormIsSplitOnlyOnTheAliasBoundary()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ parser.Parse(["--remap=Foo=System.String bar"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "Foo=System.String bar" }));
+ }
+
+ [Test]
+ public void BareNameValueTokenIsTreatedAsValueNotOption()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ parser.Parse(["--remap", "-Foo=Bar"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "-Foo=Bar" }));
+ }
+
+ // A value that happens to start with a known short alias prefix (e.g. `-m64` after
+ // `--additional`, where `-m` is a distinct option) must be kept as a value.
+ [Test]
+ public void ValueStartingWithAnotherOptionPrefixIsNotMatchedAsThatOption()
+ {
+ var additional = Multi("--additional", "-a");
+ var methodClass = Single("--methodClassName", "-m");
+ var parser = new CommandLineParser([additional, methodClass]);
+
+ parser.Parse(["--additional", "-m64"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(additional.GetValues(), Is.EqualTo(new[] { "-m64" }));
+ Assert.That(methodClass.IsPresent, Is.False);
+ }
+
+ [Test]
+ public void SingleValueOptionConsumesExactlyOneToken()
+ {
+ var output = Single("--output", "-o");
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([output, remap]);
+
+ parser.Parse(["--output", "out.cs", "--remap", "A=B"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(output.SingleValue, Is.EqualTo("out.cs"));
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "A=B" }));
+ }
+
+ [Test]
+ public void FlagOptionTakesNoArgument()
+ {
+ var version = Flag("--version");
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([version, remap]);
+
+ parser.Parse(["--version", "--remap", "A=B"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(version.IsPresent, Is.True);
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "A=B" }));
+ }
+
+ [Test]
+ public void UnrecognizedOptionProducesAnError()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ parser.Parse(["--unknown"]);
+
+ Assert.That(parser.Errors, Has.Count.EqualTo(1));
+ Assert.That(parser.Errors[0], Does.Contain("--unknown"));
+ }
+
+ [Test]
+ public void AllowedValuesRejectsUnexpectedValue()
+ {
+ var language = new CommandLineOption(["--language", "-x"], "", CommandLineOptionKind.SingleValue, allowedValues: ["c", "c++"]);
+ var parser = new CommandLineParser([language]);
+
+ parser.Parse(["--language", "rust"]);
+
+ Assert.That(parser.Errors, Has.Count.EqualTo(1));
+ Assert.That(parser.Errors[0], Does.Contain("rust"));
+ }
+
+ // A response file must yield one token per non-empty, non-comment line so that a value
+ // containing spaces survives intact (the core of the #554 fix).
+ [Test]
+ public void ResponseFileTreatsEachLineAsASingleToken()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ var responseFile = Path.GetTempFileName();
+
+ try
+ {
+ File.WriteAllLines(responseFile, [
+ "# a comment",
+ "--remap",
+ "__arglist=@params string[] args",
+ "",
+ "Foo=System.String bar",
+ ]);
+
+ parser.Parse([$"@{responseFile}"]);
+
+ Assert.That(parser.Errors, Is.Empty);
+ Assert.That(remap.GetValues(), Is.EqualTo(new[] { "__arglist=@params string[] args", "Foo=System.String bar" }));
+ }
+ finally
+ {
+ File.Delete(responseFile);
+ }
+ }
+
+ [Test]
+ public void MissingResponseFileProducesAnError()
+ {
+ var remap = Multi("--remap", "-r");
+ var parser = new CommandLineParser([remap]);
+
+ parser.Parse(["@does-not-exist.rsp"]);
+
+ Assert.That(parser.Errors, Has.Count.EqualTo(1));
+ Assert.That(parser.Errors[0], Does.Contain("Response file not found"));
+ }
+}