From a19e8fb1eea9f83cbe4af16c61affa8ab31c428e Mon Sep 17 00:00:00 2001 From: badcel <1218031+badcel@users.noreply.github.com> Date: Tue, 21 Jan 2025 21:55:20 +0100 Subject: [PATCH] Add GObject-2.0.Integration.csproj --- .../GObject-2.0.Integration.csproj | 22 +++++++ .../Integration/Generator.cs | 12 ++++ .../Integration/Subclass.cs | 21 +++++++ .../Integration/Subclass/SubclassAttribute.cs | 15 +++++ .../Integration/Subclass/SubclassCode.cs | 40 +++++++++++++ .../Integration/Subclass/SubclassData.cs | 3 + .../Subclass/SubclassValuesProvider.cs | 28 +++++++++ .../Properties/launchSettings.json | 9 +++ src/GirCore.sln | 15 +++++ .../GridView/CustomObjectGridViewWindow.cs | 12 +--- src/Samples/Gtk-4.0/GridView/GridView.csproj | 1 + .../GirTest-0.1.Tests.csproj | 3 + .../SubclassIntegrationTest.cs | 58 +++++++++++++++++++ 13 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 src/Extensions/GObject-2.0.Integration/GObject-2.0.Integration.csproj create mode 100644 src/Extensions/GObject-2.0.Integration/Integration/Generator.cs create mode 100644 src/Extensions/GObject-2.0.Integration/Integration/Subclass.cs create mode 100644 src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassAttribute.cs create mode 100644 src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassCode.cs create mode 100644 src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassData.cs create mode 100644 src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassValuesProvider.cs create mode 100644 src/Extensions/GObject-2.0.Integration/Properties/launchSettings.json create mode 100644 src/Tests/Libs/GirTest-0.1.Tests/SubclassIntegrationTest.cs diff --git a/src/Extensions/GObject-2.0.Integration/GObject-2.0.Integration.csproj b/src/Extensions/GObject-2.0.Integration/GObject-2.0.Integration.csproj new file mode 100644 index 000000000..bc06ed9b0 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/GObject-2.0.Integration.csproj @@ -0,0 +1,22 @@ + + + + GirCore.GObject-2.0.Integration + GObject.Integration + Source Generators to make it easy to integrate C# with the GObject type system. + + false + true + + + + + + + + + + + + diff --git a/src/Extensions/GObject-2.0.Integration/Integration/Generator.cs b/src/Extensions/GObject-2.0.Integration/Integration/Generator.cs new file mode 100644 index 000000000..f1f0f70a4 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Integration/Generator.cs @@ -0,0 +1,12 @@ +using Microsoft.CodeAnalysis; + +namespace GObject.Integration; + +[Generator] +public class Generator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.EnableSubclassSupport(); + } +} \ No newline at end of file diff --git a/src/Extensions/GObject-2.0.Integration/Integration/Subclass.cs b/src/Extensions/GObject-2.0.Integration/Integration/Subclass.cs new file mode 100644 index 000000000..6bda37516 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Integration/Subclass.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; + +namespace GObject.Integration; + +public static class Subclass +{ + public static void EnableSubclassSupport(this IncrementalGeneratorInitializationContext context) + { + //https://andrewlock.net/creating-a-source-generator-part-1-creating-an-incremental-source-generator/ + + context.RegisterPostInitializationOutput(ctx => ctx.AddSource( + hintName: SubclassAttribute.FileName, + source: SubclassAttribute.Code + )); + + context.RegisterImplementationSourceOutput( + source: context.GetSubclassValuesProvider(), + action: SubclassCode.Generate + ); + } +} \ No newline at end of file diff --git a/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassAttribute.cs b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassAttribute.cs new file mode 100644 index 000000000..4aaf044e6 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassAttribute.cs @@ -0,0 +1,15 @@ +namespace GObject.Integration; + +public static class SubclassAttribute +{ + public const string FullyQualifiedName = "GObject.SubclassAttribute"; + + public const string FileName = "SubclassAttribute.g.cs"; + + public const string Code = """ + namespace GObject; + + [System.AttributeUsage(System.AttributeTargets.Class)] + public class SubclassAttribute : System.Attribute { } + """; +} \ No newline at end of file diff --git a/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassCode.cs b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassCode.cs new file mode 100644 index 000000000..e0c4438a7 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassCode.cs @@ -0,0 +1,40 @@ +using Microsoft.CodeAnalysis; + +namespace GObject.Integration; + +public static class SubclassCode +{ + public static void Generate(SourceProductionContext context, SubclassData subclassData) + { + context.AddSource( + hintName:$"{subclassData.Name}.Subclass.g.cs", + source: ToCode(subclassData) + ); + } + private static string ToCode(SubclassData subclassData) + { + return $$""" + namespace GridViewSample; + + //TODO: HIER MUSS EIN Primary Constructor rein, der ein Handle braucht und das an die Base weitergibt + //public class My(AboutDialogHandle handle) : AboutDialog(handle) + //{ + // public My(params GObject.ConstructArgument[] constructArguments) : this(Gtk.Internal.AboutDialogHandle.For(false, constructArguments)) { } + // Dann kann ein User den Constructor so schreiben: + // public My(int a) : this() + + public partial class {{subclassData.Name}} : GObject.GTypeProvider, GObject.InstanceFactory + { + private static readonly GObject.Type GType = GObject.Internal.SubclassRegistrar.Register<{{subclassData.Name}}, GObject.Object>(); + public static new GObject.Type GetGType() => GType; + + static object GObject.InstanceFactory.Create(System.IntPtr handle, bool ownsHandle) + { + return new {{subclassData.Name}}(handle, ownsHandle); + } + + private {{subclassData.Name}}(System.IntPtr ptr, bool ownsHandle) : base(new GObject.Internal.ObjectHandle(ptr, ownsHandle)) { } + } + """; + } +} \ No newline at end of file diff --git a/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassData.cs b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassData.cs new file mode 100644 index 000000000..a359b09e7 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassData.cs @@ -0,0 +1,3 @@ +namespace GObject.Integration; + +public record SubclassData(string Name, string Parent); \ No newline at end of file diff --git a/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassValuesProvider.cs b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassValuesProvider.cs new file mode 100644 index 000000000..cce40c0b7 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Integration/Subclass/SubclassValuesProvider.cs @@ -0,0 +1,28 @@ +using System.Threading; +using Microsoft.CodeAnalysis; + +namespace GObject.Integration; + +public static class SubclassValuesProvider +{ + public static IncrementalValuesProvider GetSubclassValuesProvider(this IncrementalGeneratorInitializationContext context) + { + return context.SyntaxProvider + .ForAttributeWithMetadataName( + fullyQualifiedMetadataName: SubclassAttribute.FullyQualifiedName, + predicate: static (_, _) => true, + transform: GetSubclassData) + .Where(data => data is not null)!; + } + + private static SubclassData? GetSubclassData(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + var semanticModel = context.SemanticModel; + var enumDeclarationSyntax = context.TargetNode; + + if (semanticModel.GetDeclaredSymbol(enumDeclarationSyntax, cancellationToken) is not INamedTypeSymbol symbol) + return null; + + return new SubclassData(Name: symbol.Name, "Test"); + } +} \ No newline at end of file diff --git a/src/Extensions/GObject-2.0.Integration/Properties/launchSettings.json b/src/Extensions/GObject-2.0.Integration/Properties/launchSettings.json new file mode 100644 index 000000000..c9194f6d9 --- /dev/null +++ b/src/Extensions/GObject-2.0.Integration/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "GObject-2.0.Integration": { + "commandName": "DebugRoslynComponent", + "targetProject": "../../Samples/Gtk-4.0/GridView/GridView.csproj" + } + } +} diff --git a/src/GirCore.sln b/src/GirCore.sln index 111e5c639..443c6cf59 100644 --- a/src/GirCore.sln +++ b/src/GirCore.sln @@ -260,6 +260,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BoxLayout", "BoxLayout", "{ ..\docs\docs\tutorial\gtk\img\BoxLayout\Windows.png = ..\docs\docs\tutorial\gtk\img\BoxLayout\Windows.png EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GObject-2.0.Integration", "Extensions\GObject-2.0.Integration\GObject-2.0.Integration.csproj", "{25255BB4-BB40-4E90-A934-FA91B2E9022D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -909,6 +911,18 @@ Global {43F76895-E4F5-42CF-B225-524F53B7660F}.Release|x64.Build.0 = Release|Any CPU {43F76895-E4F5-42CF-B225-524F53B7660F}.Release|x86.ActiveCfg = Release|Any CPU {43F76895-E4F5-42CF-B225-524F53B7660F}.Release|x86.Build.0 = Release|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Debug|x64.ActiveCfg = Debug|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Debug|x64.Build.0 = Debug|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Debug|x86.ActiveCfg = Debug|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Debug|x86.Build.0 = Debug|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Release|Any CPU.Build.0 = Release|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Release|x64.ActiveCfg = Release|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Release|x64.Build.0 = Release|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Release|x86.ActiveCfg = Release|Any CPU + {25255BB4-BB40-4E90-A934-FA91B2E9022D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {BF7F9B0B-CB43-4161-BFAD-C6EE479FC86B} = {386AE10F-B7AC-4C97-AC5C-202D3662A868} @@ -990,5 +1004,6 @@ Global {0A902770-C2A3-440B-86BD-5E4A9DC92F88} = {D14CDE9E-BE14-47DC-B231-7770A40FE716} {43F76895-E4F5-42CF-B225-524F53B7660F} = {D14CDE9E-BE14-47DC-B231-7770A40FE716} {61F2AC19-E853-4FAD-8EF3-6F3707D1F796} = {C6411C41-C911-4942-A108-0EFBE55082D7} + {25255BB4-BB40-4E90-A934-FA91B2E9022D} = {82ACABCF-0CE8-40ED-9402-8499407E846F} EndGlobalSection EndGlobal diff --git a/src/Samples/Gtk-4.0/GridView/CustomObjectGridViewWindow.cs b/src/Samples/Gtk-4.0/GridView/CustomObjectGridViewWindow.cs index 005905608..ba1256928 100644 --- a/src/Samples/Gtk-4.0/GridView/CustomObjectGridViewWindow.cs +++ b/src/Samples/Gtk-4.0/GridView/CustomObjectGridViewWindow.cs @@ -9,15 +9,9 @@ namespace GridViewSample; -public class ItemData : GObject.Object, GTypeProvider, InstanceFactory +[GObject.Subclass] +public partial class ItemData : GObject.Object { - private static readonly Type GType = SubclassRegistrar.Register(); - public static new Type GetGType() => GType; - static object InstanceFactory.Create(IntPtr handle, bool ownsHandle) - { - return new ItemData(handle, ownsHandle); - } - public string? ImagePath { get; set; } public string? Text { get; set; } public string? Description { get; set; } @@ -28,8 +22,6 @@ public ItemData(string imagePath, string text, string description) : base(Object Text = text; Description = description; } - - private ItemData(IntPtr ptr, bool ownsHandle) : base(new ObjectHandle(ptr, ownsHandle)) { } } public class CustomObjectGridViewWindow : Window diff --git a/src/Samples/Gtk-4.0/GridView/GridView.csproj b/src/Samples/Gtk-4.0/GridView/GridView.csproj index c24d63834..0b9ebc27c 100644 --- a/src/Samples/Gtk-4.0/GridView/GridView.csproj +++ b/src/Samples/Gtk-4.0/GridView/GridView.csproj @@ -1,6 +1,7 @@  + diff --git a/src/Tests/Libs/GirTest-0.1.Tests/GirTest-0.1.Tests.csproj b/src/Tests/Libs/GirTest-0.1.Tests/GirTest-0.1.Tests.csproj index 9dad3b991..add6f834f 100644 --- a/src/Tests/Libs/GirTest-0.1.Tests/GirTest-0.1.Tests.csproj +++ b/src/Tests/Libs/GirTest-0.1.Tests/GirTest-0.1.Tests.csproj @@ -5,6 +5,9 @@ + + + diff --git a/src/Tests/Libs/GirTest-0.1.Tests/SubclassIntegrationTest.cs b/src/Tests/Libs/GirTest-0.1.Tests/SubclassIntegrationTest.cs new file mode 100644 index 000000000..fb8f419b3 --- /dev/null +++ b/src/Tests/Libs/GirTest-0.1.Tests/SubclassIntegrationTest.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + + +namespace GirTest.Tests; + +[TestClass, TestCategory("BindingTest")] +public class SubclassIntegrationTest : Test +{ + [TestMethod] + public void GeneratesCode() + { + var result = RunGeneratorOn(""" + [GObject.Subclass] + public partial class CustomSubclassOfObject{ } + """); + var sources = result.Results.First().GeneratedSources; + + sources[0].HintName.Should().Be("SubclassAttribute.g.cs"); + sources[0].SourceText.ToString().Should().Be(""" + namespace GObject; + + [System.AttributeUsage(System.AttributeTargets.Class)] + public class SubclassAttribute : System.Attribute { } + """); + + sources[1].HintName.Should().Be("CustomSubclassOfObject.Subclass.g.cs"); + sources[1].SourceText.ToString().Should().Be(""" + namespace GridViewSample; + + public partial class CustomSubclassOfObject : GObject.GTypeProvider, GObject.InstanceFactory + { + private static readonly GObject.Type GType = GObject.Internal.SubclassRegistrar.Register(); + public static new GObject.Type GetGType() => GType; + + static object GObject.InstanceFactory.Create(System.IntPtr handle, bool ownsHandle) + { + return new CustomSubclassOfObject(handle, ownsHandle); + } + } + """); + } + + private static GeneratorDriverRunResult RunGeneratorOn(string source) + { + var inputCompilation = CSharpCompilation.Create("compilation", [CSharpSyntaxTree.ParseText(source)]); + var generator = new GObject.Integration.Generator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out _); + + return driver.GetRunResult(); + } +} \ No newline at end of file