Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public void WriteCustomAttribute(string attribute, Action? callback = null)
{
AddUsingDirective("System.Runtime.Versioning");
}
else if (attribute.StartsWith("GeneratedCode(", StringComparison.Ordinal))
{
AddUsingDirective("System.CodeDom.Compiler");
}

if (!_customAttrIsForParameter)
{
Expand Down
10 changes: 10 additions & 0 deletions sources/ClangSharp.PInvokeGenerator/GeneratedCodeAttributeMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// 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.

namespace ClangSharp;

public enum GeneratedCodeAttributeMode
{
Assembly,
Type,
None
}
91 changes: 85 additions & 6 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ public void Close()
_ = staticUsingDirectives.Add(staticUsingDirective);
}

// The method class emits its [GeneratedCode] attribute directly rather than through
// WriteCustomAttribute, so its System.CodeDom.Compiler using is not tracked on the
// builder and must be hoisted here for single-file output.
if (!csharpOutputBuilder.IsTestOutput && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type && csharpOutputBuilder.Contents.Any() && _topLevelClassNames.Contains(csharpOutputBuilder.Name))
{
_ = usingDirectives.Add("System.CodeDom.Compiler");
}

if (csharpOutputBuilder.IsTestOutput)
{
testHasAnyContents |= csharpOutputBuilder.Contents.Any();
Expand Down Expand Up @@ -119,6 +127,11 @@ public void Close()
_ = usingDirectives.Add("System");
_ = usingDirectives.Add("System.Diagnostics");

if (_config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Assembly)
{
_ = usingDirectives.Add("System.CodeDom.Compiler");
}

if (_config.GenerateSetsLastSystemErrorAttribute)
{
_ = usingDirectives.Add("System.Runtime.InteropServices");
Expand Down Expand Up @@ -167,14 +180,36 @@ public void Close()
sw.WriteLine();
}

if (generateHelperTypes && _config.GenerateDisableRuntimeMarshalling)
if (generateHelperTypes)
{
// Assembly attributes must precede the namespace declaration, so the
// [assembly: DisableRuntimeMarshalling] attribute is emitted here rather
// than alongside the other helper types.
var emittedAssemblyAttribute = false;

sw.WriteLine("[assembly: DisableRuntimeMarshalling]");
sw.WriteLine();
if (_config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Assembly)
{
// Assembly attributes must precede the namespace declaration. The generated
// helper types identify the assembly as containing generated code, so the
// [assembly: GeneratedCode] marker is emitted here rather than per type.

sw.Write("[assembly: ");
sw.Write(GeneratedCodeAttribute);
sw.WriteLine(']');
emittedAssemblyAttribute = true;
}

if (_config.GenerateDisableRuntimeMarshalling)
{
// Assembly attributes must precede the namespace declaration, so the
// [assembly: DisableRuntimeMarshalling] attribute is emitted here rather
// than alongside the other helper types.

sw.WriteLine("[assembly: DisableRuntimeMarshalling]");
emittedAssemblyAttribute = true;
}

if (emittedAssemblyAttribute)
{
sw.WriteLine();
}
}
}
else if (_config.OutputMode == PInvokeGeneratorOutputMode.Xml)
Expand Down Expand Up @@ -362,6 +397,7 @@ public void Close()
}
}

GenerateGeneratedCodeAssemblyAttribute(this, stream, leaveStreamOpen);
GenerateDisableRuntimeMarshallingAttribute(this, stream, leaveStreamOpen);
hasNamespaceContent = GenerateNativeBitfieldAttribute(this, stream, leaveStreamOpen, hasNamespaceContent);
hasNamespaceContent = GenerateNativeInheritanceAttribute(this, stream, leaveStreamOpen, hasNamespaceContent);
Expand Down Expand Up @@ -412,6 +448,49 @@ public void Close()
_uuidsToGenerate.Clear();
_visitedFiles.Clear();

static void GenerateGeneratedCodeAssemblyAttribute(PInvokeGenerator generator, Stream? stream, bool leaveStreamOpen)
{
var config = generator.Config;

if (config.GeneratedCodeAttributeMode != GeneratedCodeAttributeMode.Assembly)
{
return;
}

if (!config.GenerateMultipleFiles)
{
// In single-file mode the [assembly: GeneratedCode] attribute is emitted at the top
// of the file, before the namespace declaration, since assembly attributes cannot
// appear after a namespace.
return;
}

if (stream is null)
{
var outputPath = Path.Combine(config.OutputLocation, "GeneratedCode.cs");
stream = generator._outputStreamFactory(outputPath);
}

using var sw = new StreamWriter(stream, s_defaultStreamWriterEncoding, DefaultStreamWriterBufferSize, leaveStreamOpen);
sw.NewLine = "\n";

if (!string.IsNullOrEmpty(config.HeaderText))
{
sw.WriteLine(config.HeaderText);
}

sw.WriteLine("using System.CodeDom.Compiler;");
sw.WriteLine();
sw.Write("[assembly: ");
sw.Write(GeneratedCodeAttribute);
sw.WriteLine(']');

if (!leaveStreamOpen)
{
stream = null;
}
}

static void GenerateDisableRuntimeMarshallingAttribute(PInvokeGenerator generator, Stream? stream, bool leaveStreamOpen)
{
var config = generator.Config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ private void VisitEnumDecl(EnumDecl enumDecl)
WriteCustomAttrs = static context => {
(var enumDecl, var generator) = ((EnumDecl, PInvokeGenerator))context;

generator.WithAttributes(enumDecl);
generator.WithAttributes(enumDecl, emitGeneratedCodeAttribute: true);
generator.WithUsings(enumDecl);
},
CustomAttrGeneratorData = (enumDecl, this),
Expand Down Expand Up @@ -1401,7 +1401,7 @@ void ForFunctionProtoType(TypedefDecl typedefDecl, FunctionProtoType functionPro
WriteCustomAttrs = static context => {
(var typedefDecl, var generator) = ((TypedefDecl, PInvokeGenerator))context;

generator.WithAttributes(typedefDecl);
generator.WithAttributes(typedefDecl, emitGeneratedCodeAttribute: true);
generator.WithUsings(typedefDecl);
},
CustomAttrGeneratorData = (typedefDecl, this),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ private void VisitRecordDecl(RecordDecl recordDecl)
WriteCustomAttrs = static context => {
(var recordDecl, var generator) = ((RecordDecl, PInvokeGenerator))context;

generator.WithAttributes(recordDecl);
generator.WithAttributes(recordDecl, emitGeneratedCodeAttribute: true);
generator.WithUsings(recordDecl);
},
CustomAttrGeneratorData = (recordDecl, this),
Expand Down
27 changes: 26 additions & 1 deletion sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ public sealed partial class PInvokeGenerator : IDisposable
private const string AnonymousRecordPrefix = $"{AnonymousNamePrefix}Record_";
private const string AnonymousTypeKindTag = "_e__";

// The [GeneratedCode] annotation emitted to mark output as generated. By default it rides on the
// assembly (via [assembly: GeneratedCode]) when helper types are generated; --config
// generate-generated-code=type instead annotates each generated top-level type, and =none emits
// neither. The version is the ClangSharp assembly version (without any prerelease/build metadata) so
// it is deterministic within a release and only churns the assembly/helper-type baselines on bump.
private static readonly string GeneratedCodeAttribute = $"GeneratedCode(\"ClangSharp\", \"{typeof(PInvokeGenerator).Assembly.GetName().Version?.ToString() ?? ""}\")";

private static readonly Encoding s_defaultStreamWriterEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private static readonly string[] s_doubleColonSeparator = ["::"];
private static readonly char[] s_doubleQuoteSeparator = ['"'];
Expand Down Expand Up @@ -630,6 +637,11 @@ private void CloseOutputBuilder(Stream stream, IOutputBuilder outputBuilder, boo
csharpOutputBuilder.AddUsingDirective(withUsing);
}
}

if (!outputBuilder.IsTestOutput && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type)
{
csharpOutputBuilder.AddUsingDirective("System.CodeDom.Compiler");
}
}

var usingDirectives = new SortedSet<string>(csharpOutputBuilder.UsingDirectives, StringComparer.Ordinal);
Expand Down Expand Up @@ -740,6 +752,14 @@ void ForCSharp(CSharpOutputBuilder outputBuilder)
sw.WriteLine(".</summary>");
}

if (!outputBuilder.IsTestOutput && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type)
{
sw.Write(indentationString);
sw.Write('[');
sw.Write(GeneratedCodeAttribute);
sw.WriteLine(']');
}

if (_topLevelClassAttributes.GetAlternateLookup<ReadOnlySpan<char>>().TryGetValue(nonTestName, out var withAttributes))
{
if (withAttributes.Count != 0)
Expand Down Expand Up @@ -2151,11 +2171,16 @@ private static IEnumerable<Attr> GetAttributesFor(NamedDecl namedDecl)
return declAttrs;
}

private void WithAttributes(NamedDecl namedDecl, bool onlySupportedOSPlatform = false, bool isTestOutput = false)
private void WithAttributes(NamedDecl namedDecl, bool onlySupportedOSPlatform = false, bool isTestOutput = false, bool emitGeneratedCodeAttribute = false)
{
var outputBuilder = isTestOutput ? _testOutputBuilder : _outputBuilder;
Debug.Assert(outputBuilder is not null);

if (emitGeneratedCodeAttribute && !isTestOutput && !onlySupportedOSPlatform && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type)
{
outputBuilder.WriteCustomAttribute(GeneratedCodeAttribute);
}

if (TryGetRemappedValue(namedDecl, _config._withAttributes, out var attributes, matchStar: true))
{
foreach (var attribute in attributes.Where((a) => !onlySupportedOSPlatform || a.StartsWith("SupportedOSPlatform(", StringComparison.Ordinal)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,24 @@ public IReadOnlyCollection<string> ExcludedNames

public bool GenerateFixedBufferIndexerOverloads => (_options & PInvokeGeneratorConfigurationOptions.GenerateFixedBufferIndexerOverloads) != 0;

public GeneratedCodeAttributeMode GeneratedCodeAttributeMode
{
get
{
if ((_options & PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute) != 0)
{
return GeneratedCodeAttributeMode.None;
}

if ((_options & PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType) != 0)
{
return GeneratedCodeAttributeMode.Type;
}

return GeneratedCodeAttributeMode.Assembly;
}
}

public bool GenerateGenericPointerWrapper => (_options & PInvokeGeneratorConfigurationOptions.GenerateGenericPointerWrapper) != 0;

public bool GenerateGuidMember => (_options & PInvokeGeneratorConfigurationOptions.GenerateGuidMember) != 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,8 @@ public enum PInvokeGeneratorConfigurationOptions : long
DontUseUsingStaticsForGuidMember = 1L << 40,

GenerateFixedBufferIndexerOverloads = 1L << 41,

GenerateGeneratedCodeAttributeAsType = 1L << 42,

ExcludeGeneratedCodeAttribute = 1L << 43,
}
1 change: 1 addition & 0 deletions sources/ClangSharpPInvokeGenerator/Program.Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ internal static partial class Program
new HelpRow("generate-doc-includes", "<include> 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-generated-code=<mode>", "Controls the emission of the GeneratedCode attribute. 'assembly' (default) emits a single '[assembly: GeneratedCode]' when helper types are generated; 'type' instead annotates each generated top-level type; 'none' emits neither."),
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."),
Expand Down
40 changes: 39 additions & 1 deletion sources/ClangSharpPInvokeGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,12 @@ public static int Run()
var configOptions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? PInvokeGeneratorConfigurationOptions.None : PInvokeGeneratorConfigurationOptions.GenerateUnixTypes;
var printConfigHelp = false;

foreach (var configSwitch in configSwitches)
foreach (var configSwitchRaw in configSwitches)
{
var separatorIndex = configSwitchRaw.IndexOf('=', StringComparison.Ordinal);
var configSwitch = separatorIndex >= 0 ? configSwitchRaw[..separatorIndex] : configSwitchRaw;
var configSwitchValue = separatorIndex >= 0 ? configSwitchRaw[(separatorIndex + 1)..] : "";

switch (configSwitch)
{
case "?":
Expand Down Expand Up @@ -381,6 +385,40 @@ public static int Run()
break;
}

case "generate-generated-code":
{
switch (configSwitchValue)
{
case "" or "assembly":
{
configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType;
configOptions &= ~PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute;
break;
}

case "type":
{
configOptions |= PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType;
configOptions &= ~PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute;
break;
}

case "none":
{
configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute;
configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType;
break;
}

default:
{
errorList.Add($"Error: Unrecognized generate-generated-code value: {configSwitchValue}. Expected 'assembly', 'type', or 'none'.");
break;
}
}
break;
}

case "generate-guid-member":
{
configOptions |= PInvokeGeneratorConfigurationOptions.GenerateGuidMember;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Runtime.InteropServices;

[assembly: GeneratedCode("ClangSharp", "21.1.8.3")]

namespace ClangSharp.Test
{
public enum MyEnum
{
MyEnum_Value,
}

public partial struct MyStruct
{
public int value;
}

public static unsafe partial class Methods
{
[DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)]
public static extern void MyFunction([NativeTypeName("MyCallback")] delegate* unmanaged[Cdecl]<int, void> callback);
}

/// <summary>Defines the type of a member as it was used in the native signature.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
[Conditional("DEBUG")]
internal sealed partial class NativeTypeNameAttribute : Attribute
{
private readonly string _name;

/// <summary>Initializes a new instance of the <see cref="NativeTypeNameAttribute" /> class.</summary>
/// <param name="name">The name of the type that was used in the native signature.</param>
public NativeTypeNameAttribute(string name)
{
_name = name;
}

/// <summary>Gets the name of the type that was used in the native signature.</summary>
public string Name => _name;
}

/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional("DEBUG")]
internal sealed partial class NativeAnnotationAttribute : Attribute
{
private readonly string _annotation;

/// <summary>Initializes a new instance of the <see cref="NativeAnnotationAttribute" /> class.</summary>
/// <param name="annotation">The annotation that was used in the native declaration.</param>
public NativeAnnotationAttribute(string annotation)
{
_annotation = annotation;
}

/// <summary>Gets the annotation that was used in the native declaration.</summary>
public string Annotation => _annotation;
}
}
Loading
Loading