From 7edcc35866286d5aafabf06bc399580a702c977d Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Sun, 1 Aug 2021 00:16:58 +0100 Subject: [PATCH 01/10] Starting work on SilkTouch, no Scraper-specific work yet --- Silk.NET.sln | 108 ++++++++++++++++++ src/generators/.gitkeep | 0 src/generators/README.md | 3 + .../Silk.NET.SilkTouch.ClangSharp.Xml.csproj | 8 ++ .../Configuration/Config.cs | 26 +++++ .../Configuration/FormFactors.cs | 15 +++ .../Configuration/ProjectConfiguration.cs | 10 ++ .../Generation/SilkTouchContext.cs | 40 +++++++ .../Silk.NET.SilkTouch.Common.csproj | 15 +++ .../EmitterGenerator.cs | 13 +++ .../Silk.NET.SilkTouch.Emitter.csproj | 12 ++ .../OverloaderGenerator.cs | 13 +++ .../Silk.NET.SilkTouch.Overloader.csproj | 12 ++ .../Silk.NET.SilkTouch.Roslyn/Constants.cs | 10 ++ .../Silk.NET.SilkTouch.Roslyn/Diagnostics.cs | 23 ++++ .../Silk.NET.SilkTouch.Roslyn.csproj | 13 +++ .../SilkTouchSourceGenerator.cs | 77 +++++++++++++ .../ScraperGenerator.cs | 13 +++ .../Silk.NET.SilkTouch.Scraper.csproj | 12 ++ src/generators/SilkTouch/Program.cs | 12 ++ src/generators/SilkTouch/SilkTouch.csproj | 8 ++ 21 files changed, 443 insertions(+) delete mode 100644 src/generators/.gitkeep create mode 100644 src/generators/README.md create mode 100644 src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj create mode 100644 src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj create mode 100644 src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj create mode 100644 src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj create mode 100644 src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj create mode 100644 src/generators/SilkTouch/Program.cs create mode 100644 src/generators/SilkTouch/SilkTouch.csproj diff --git a/Silk.NET.sln b/Silk.NET.sln index bc9729eb5f..25033bc10e 100644 --- a/Silk.NET.sln +++ b/Silk.NET.sln @@ -35,6 +35,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bindings", "bindings", "{9020C7C6-C366-4BD3-8C8A-F81394EC7174}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generators", "generators", "{8238D9F3-E158-4633-8017-B29AA3AD61F7}" +ProjectSection(SolutionItems) = preProject + src\generators\README.md = src\generators\README.md +EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Assimp.Native", "src\infrastructure\Silk.NET.Assimp.Native\Silk.NET.Assimp.Native.csproj", "{1FBF178D-3F9B-46C5-9D50-21D4D547113E}" EndProject @@ -46,6 +49,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.OpenAL.Soft.Native EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Maths.Benchmarks", "src\benchmarks\Silk.NET.Maths.Benchmarks\Silk.NET.Maths.Benchmarks.csproj", "{CB8B28DE-456A-4B8E-85A6-2C50CEE08CA2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Scraper", "src\generators\Silk.NET.SilkTouch.Scraper\Silk.NET.SilkTouch.Scraper.csproj", "{EA623F04-DADA-4714-B2C5-44C82E211492}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Common", "src\generators\Silk.NET.SilkTouch.Common\Silk.NET.SilkTouch.Common.csproj", "{C6833D95-14A8-499A-85E4-C09FA094DCE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilkTouch", "src\generators\SilkTouch\SilkTouch.csproj", "{7DF67686-3F46-4569-935F-4A9E9903B575}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Roslyn", "src\generators\Silk.NET.SilkTouch.Roslyn\Silk.NET.SilkTouch.Roslyn.csproj", "{11841F56-7603-40D9-BC06-10EBBE17D905}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.ClangSharp.Xml", "src\generators\Silk.NET.SilkTouch.ClangSharp.Xml\Silk.NET.SilkTouch.ClangSharp.Xml.csproj", "{CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Emitter", "src\generators\Silk.NET.SilkTouch.Emitter\Silk.NET.SilkTouch.Emitter.csproj", "{50F26B27-32B6-4D66-ADD5-CC9C38373B19}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Overloader", "src\generators\Silk.NET.SilkTouch.Overloader\Silk.NET.SilkTouch.Overloader.csproj", "{46A89F89-77B3-4679-A82F-04A552545B16}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -128,6 +145,90 @@ Global {CB8B28DE-456A-4B8E-85A6-2C50CEE08CA2}.Release|x64.Build.0 = Release|Any CPU {CB8B28DE-456A-4B8E-85A6-2C50CEE08CA2}.Release|x86.ActiveCfg = Release|Any CPU {CB8B28DE-456A-4B8E-85A6-2C50CEE08CA2}.Release|x86.Build.0 = Release|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Debug|x64.Build.0 = Debug|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Debug|x86.Build.0 = Debug|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Release|Any CPU.Build.0 = Release|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Release|x64.ActiveCfg = Release|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Release|x64.Build.0 = Release|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Release|x86.ActiveCfg = Release|Any CPU + {EA623F04-DADA-4714-B2C5-44C82E211492}.Release|x86.Build.0 = Release|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Debug|x64.Build.0 = Debug|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Debug|x86.Build.0 = Debug|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Release|Any CPU.Build.0 = Release|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Release|x64.ActiveCfg = Release|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Release|x64.Build.0 = Release|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Release|x86.ActiveCfg = Release|Any CPU + {C6833D95-14A8-499A-85E4-C09FA094DCE8}.Release|x86.Build.0 = Release|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Debug|x64.Build.0 = Debug|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Debug|x86.Build.0 = Debug|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Release|Any CPU.Build.0 = Release|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Release|x64.ActiveCfg = Release|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Release|x64.Build.0 = Release|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Release|x86.ActiveCfg = Release|Any CPU + {7DF67686-3F46-4569-935F-4A9E9903B575}.Release|x86.Build.0 = Release|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Debug|x64.ActiveCfg = Debug|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Debug|x64.Build.0 = Debug|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Debug|x86.ActiveCfg = Debug|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Debug|x86.Build.0 = Debug|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Release|Any CPU.Build.0 = Release|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Release|x64.ActiveCfg = Release|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Release|x64.Build.0 = Release|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Release|x86.ActiveCfg = Release|Any CPU + {11841F56-7603-40D9-BC06-10EBBE17D905}.Release|x86.Build.0 = Release|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Debug|x64.ActiveCfg = Debug|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Debug|x64.Build.0 = Debug|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Debug|x86.ActiveCfg = Debug|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Debug|x86.Build.0 = Debug|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Release|Any CPU.Build.0 = Release|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Release|x64.ActiveCfg = Release|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Release|x64.Build.0 = Release|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Release|x86.ActiveCfg = Release|Any CPU + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793}.Release|x86.Build.0 = Release|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Debug|x64.ActiveCfg = Debug|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Debug|x64.Build.0 = Debug|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Debug|x86.ActiveCfg = Debug|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Debug|x86.Build.0 = Debug|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Release|Any CPU.Build.0 = Release|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Release|x64.ActiveCfg = Release|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Release|x64.Build.0 = Release|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Release|x86.ActiveCfg = Release|Any CPU + {50F26B27-32B6-4D66-ADD5-CC9C38373B19}.Release|x86.Build.0 = Release|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Debug|x64.ActiveCfg = Debug|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Debug|x64.Build.0 = Debug|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Debug|x86.ActiveCfg = Debug|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Debug|x86.Build.0 = Debug|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Release|Any CPU.Build.0 = Release|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x64.ActiveCfg = Release|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x64.Build.0 = Release|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x86.ActiveCfg = Release|Any CPU + {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -146,6 +247,13 @@ Global {44CA62B1-EE9A-41BC-A23E-D0A2F3791D76} = {F07CABFC-DC6A-4B5B-BC56-B10EEC2C0BFA} {DB186ABF-C51D-432F-B9F3-A13B53B1BD07} = {F07CABFC-DC6A-4B5B-BC56-B10EEC2C0BFA} {CB8B28DE-456A-4B8E-85A6-2C50CEE08CA2} = {FD15E196-1C63-47D6-8AD5-64F015120B4B} + {EA623F04-DADA-4714-B2C5-44C82E211492} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {C6833D95-14A8-499A-85E4-C09FA094DCE8} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {7DF67686-3F46-4569-935F-4A9E9903B575} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {11841F56-7603-40D9-BC06-10EBBE17D905} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {50F26B27-32B6-4D66-ADD5-CC9C38373B19} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {46A89F89-77B3-4679-A82F-04A552545B16} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D} diff --git a/src/generators/.gitkeep b/src/generators/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/generators/README.md b/src/generators/README.md new file mode 100644 index 0000000000..ccd22d8147 --- /dev/null +++ b/src/generators/README.md @@ -0,0 +1,3 @@ +# SilkTouch + +This folder contains SilkTouch, our \ No newline at end of file diff --git a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj new file mode 100644 index 0000000000..58ef59a24a --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj @@ -0,0 +1,8 @@ + + + + net6.0 + enable + + + diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs new file mode 100644 index 0000000000..efdc4fcfd1 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Data; +using System.Text.Json; + +namespace Silk.NET.SilkTouch.Configuration +{ + public static class Config + { + /// + /// Loads the given SilkTouch JSON Configuration into a dictionary of per-project configurations. + /// + /// The SilkTouch JSON Configuration. + /// + /// + public static Dictionary Load(string json) + => JsonSerializer.Deserialize>(json) ?? + throw new DataException("JSON deserialization of SilkTouch Configuration yielded null."); + + public static string Save(Dictionary projects) + => JsonSerializer.Serialize(projects); + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs new file mode 100644 index 0000000000..7cec3a454f --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Silk.NET.SilkTouch.Configuration +{ + [Flags] + public enum FormFactors + { + Any = 0, + Roslyn = 1 << 0, + CommandLine = 1 << 1 + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs new file mode 100644 index 0000000000..af9954abc8 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.Configuration +{ + public struct ProjectConfiguration + { + + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs new file mode 100644 index 0000000000..d8b42d7bd3 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Silk.NET.SilkTouch.Configuration; + +namespace Silk.NET.SilkTouch.Generation +{ + public sealed class SilkTouchContext + { + // TODO make constructors because .NET Standard 2.0 is crap and we can't have init properties. + + // Public Properties + /// + /// The syntax trees contained within the current project. + /// + public IEnumerable SyntaxTrees { get; set; } = Enumerable.Empty(); + + /// + /// The + /// + public ProjectConfiguration Configuration { get; set; } + + // Internal Properties + internal List<(string FileNameHint, string Content)> Outputs { get; } = new(); + internal List Diagnostics { get; } = new(); + + // Public Methods + public void EmitOutput(string fileNameHint, string content) + => Outputs.Add((fileNameHint, content)); + + public void EmitDiagnostic(Diagnostic diagnostic) => Diagnostics.Add(diagnostic); + + public (List<(string FileNameHint, string Content)> Outputs, List) GetResult() + => (Outputs, Diagnostics); + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj new file mode 100644 index 0000000000..f3ea830dde --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + enable + Silk.NET.SilkTouch + true + + + + + + + + diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs new file mode 100644 index 0000000000..bab272af2e --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs @@ -0,0 +1,13 @@ +using System; +using Silk.NET.SilkTouch.Generation; + +namespace Silk.NET.SilkTouch.Emitter +{ + public static class EmitterGenerator + { + public static void Run(SilkTouchContext ctx) + { + // TODO implement + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj b/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj new file mode 100644 index 0000000000..aa99722b63 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + enable + + + + + + + diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs new file mode 100644 index 0000000000..50199e1530 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs @@ -0,0 +1,13 @@ +using System; +using Silk.NET.SilkTouch.Generation; + +namespace Silk.NET.SilkTouch.Overloader +{ + public static class OverloaderGenerator + { + public static void Run(SilkTouchContext ctx) + { + // TODO implement + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj b/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj new file mode 100644 index 0000000000..aa99722b63 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + enable + + + + + + + diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs new file mode 100644 index 0000000000..f725ec30dc --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.Roslyn +{ + public static class Constants + { + public const string ConfigFileEditorconfigOption = "silktouch_config_file"; + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs new file mode 100644 index 0000000000..82cc6e2a75 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace Silk.NET.SilkTouch.Roslyn +{ + public class Diagnostics + { + public static DiagnosticDescriptor MultipleConfigFiles { get; } = new + ( + id: "ST0005", + title: "Multiple Configuration Files Detected", + messageFormat: $"Multiple configuration files detected. Using \"{{0}}\" instead, to use \"{{1}}\" configure the \"{Constants.ConfigFileEditorconfigOption}\"", + category: "SilkTouch.Internal", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj b/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj new file mode 100644 index 0000000000..326d210a51 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + enable + + + + + + + + diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs new file mode 100644 index 0000000000..d598be5973 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Silk.NET.SilkTouch.Configuration; +using Silk.NET.SilkTouch.Emitter; +using Silk.NET.SilkTouch.Generation; +using Silk.NET.SilkTouch.Overloader; + +namespace Silk.NET.SilkTouch.Roslyn +{ + public class SilkTouchSourceGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + // TODO do we need to do initialization downstream? + } + + public void Execute(GeneratorExecutionContext context) + { + var configFileName = "silktouch.json"; + if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue + (Constants.ConfigFileEditorconfigOption, out var file)) + { + configFileName = file; + } + + string? configFilePath = null; + foreach (var additionalFile in context.AdditionalFiles) + { + if (additionalFile.Path == configFileName || Path.GetFileName(additionalFile.Path) == configFileName) + { + if (configFilePath is not null) + { + context.ReportDiagnostic + ( + Diagnostic.Create + ( + Diagnostics.MultipleConfigFiles, + Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default) + ) + ); + } + + configFilePath = additionalFile.Path; + } + } + + if (configFilePath is null) + { + // TODO report diagnostic that this isn't found. + } + + // TODO fix all the warnings in the morning because i'm tired and just wanna commit something. + var syntaxTrees = context.Compilation.SyntaxTrees.OfType().ToArray(); + var projectConfig = Config.Load(configFilePath)[context.Compilation.AssemblyName]; + + var ctx = new SilkTouchContext { SyntaxTrees = syntaxTrees, Configuration = projectConfig }; + + EmitterGenerator.Run(ctx); + var (emitterOutputs, emitterDiagnostics) = ctx.GetResult(); + + ctx = new() { SyntaxTrees = syntaxTrees, Configuration = projectConfig }; + + OverloaderGenerator.Run(ctx); + var (overloaderOutputs, overloaderDiagnostics) = ctx.GetResult(); + + // TODO add the outputs and the diagnostics to roslyn. seriously i'm tired and cba to finish the job. + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs new file mode 100644 index 0000000000..9f11284ae6 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -0,0 +1,13 @@ +using System; +using Silk.NET.SilkTouch.Generation; + +namespace Silk.NET.SilkTouch.Scraper +{ + public static class ScraperGenerator + { + public static void Run(SilkTouchContext ctx) + { + + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj new file mode 100644 index 0000000000..696d6973d4 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -0,0 +1,12 @@ + + + + net5.0 + enable + + + + + + + diff --git a/src/generators/SilkTouch/Program.cs b/src/generators/SilkTouch/Program.cs new file mode 100644 index 0000000000..69879b6552 --- /dev/null +++ b/src/generators/SilkTouch/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace SilkTouch +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/generators/SilkTouch/SilkTouch.csproj b/src/generators/SilkTouch/SilkTouch.csproj new file mode 100644 index 0000000000..7b96d01817 --- /dev/null +++ b/src/generators/SilkTouch/SilkTouch.csproj @@ -0,0 +1,8 @@ + + + + Exe + net6.0 + + + From 117d4a2c1b942696bf09e7b413bfe860bc423877 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Tue, 3 Aug 2021 00:04:27 +0100 Subject: [PATCH 02/10] Start work on the subagent --- Silk.NET.sln | 15 ++ .../for-contributors/generators/readme.md | 0 src/Directory.Build.props | 4 + .../XmlBindings.cs} | 4 +- .../XmlBindingsVisitor.cs | 10 ++ .../Configuration/Config.cs | 78 ++++++++- .../Configuration/ExclusionHint.cs | 24 +++ .../Configuration/FormFactors.cs | 2 +- .../Json/ExcludesJsonConverter.cs | 67 ++++++++ .../Json/FormFactorsJsonConverter.cs | 43 +++++ .../Constants.cs | 3 +- .../Silk.NET.SilkTouch.Common/Diagnostics.cs | 66 ++++++++ .../Generation/SilkTouchContext.cs | 25 ++- .../Silk.NET.SilkTouch.Common/Shims.cs | 17 ++ .../Silk.NET.SilkTouch.Common.csproj | 1 + .../EmitterGenerator.cs | 2 + .../OverloaderGenerator.cs | 2 + .../Silk.NET.SilkTouch.Roslyn/Diagnostics.cs | 23 --- .../SilkTouchSourceGenerator.cs | 64 ++++++-- .../ScraperGenerator.cs | 48 +++++- .../Silk.NET.SilkTouch.Scraper.csproj | 5 + .../Subagent/ClangSharpSubagent.cs | 10 ++ .../Subagent/ISubagentSpawner.cs | 20 +++ .../Subagent/SubagentOptions.cs | 125 ++++++++++++++ src/generators/SilkTouch/ClangSharpHandoff.cs | 154 ++++++++++++++++++ .../SilkTouch/CommandLineSubagentSpawner.cs | 54 ++++++ src/generators/SilkTouch/Program.cs | 27 ++- src/generators/SilkTouch/SilkTouch.csproj | 10 ++ .../Silk.NET.Core/Silk.NET.Core.csproj | 8 + 29 files changed, 860 insertions(+), 51 deletions(-) create mode 100644 documentation/for-contributors/generators/readme.md rename src/generators/{Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs => Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs} (66%) create mode 100644 src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs rename src/generators/{Silk.NET.SilkTouch.Roslyn => Silk.NET.SilkTouch.Common}/Constants.cs (73%) create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Shims.cs delete mode 100644 src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs create mode 100644 src/generators/SilkTouch/ClangSharpHandoff.cs create mode 100644 src/generators/SilkTouch/CommandLineSubagentSpawner.cs create mode 100644 src/libraries/Silk.NET.Core/Silk.NET.Core.csproj diff --git a/Silk.NET.sln b/Silk.NET.sln index 25033bc10e..638a643775 100644 --- a/Silk.NET.sln +++ b/Silk.NET.sln @@ -63,6 +63,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Emitter" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Overloader", "src\generators\Silk.NET.SilkTouch.Overloader\Silk.NET.SilkTouch.Overloader.csproj", "{46A89F89-77B3-4679-A82F-04A552545B16}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Core", "src\libraries\Silk.NET.Core\Silk.NET.Core.csproj", "{69CF4437-59F7-4304-9AE1-4B58E1A93367}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -229,6 +231,18 @@ Global {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x64.Build.0 = Release|Any CPU {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x86.ActiveCfg = Release|Any CPU {46A89F89-77B3-4679-A82F-04A552545B16}.Release|x86.Build.0 = Release|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Debug|x64.ActiveCfg = Debug|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Debug|x64.Build.0 = Debug|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Debug|x86.ActiveCfg = Debug|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Debug|x86.Build.0 = Debug|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|Any CPU.Build.0 = Release|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x64.ActiveCfg = Release|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x64.Build.0 = Release|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x86.ActiveCfg = Release|Any CPU + {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -254,6 +268,7 @@ Global {CD47A2B9-D3D6-40CF-AE0A-B571EF9F7793} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} {50F26B27-32B6-4D66-ADD5-CC9C38373B19} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} {46A89F89-77B3-4679-A82F-04A552545B16} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} + {69CF4437-59F7-4304-9AE1-4B58E1A93367} = {C9718C94-2F21-4E8D-B55D-8F0B1A131346} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D} diff --git a/documentation/for-contributors/generators/readme.md b/documentation/for-contributors/generators/readme.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 9378890ead..19c2f6358f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -14,6 +14,10 @@ $(MSBuildThisFileDirectory)../documentation $(MSBuildThisFileDirectory)../tests + + + enable + logo.png diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs similarity index 66% rename from src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs rename to src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs index af9954abc8..7612f9c132 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ProjectConfiguration.cs +++ b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Silk.NET.SilkTouch.Configuration +namespace Silk.NET.SilkTouch.ClangSharp.Xml { - public struct ProjectConfiguration + public class XmlBindings { } diff --git a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs new file mode 100644 index 0000000000..6c46203b86 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.ClangSharp.Xml +{ + public class XmlBindingsVisitor + { + + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index efdc4fcfd1..78c5ae2597 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -1,10 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Text.Json; +using System.Text.Json.Serialization; +using Silk.NET.SilkTouch.Configuration.Json; namespace Silk.NET.SilkTouch.Configuration { @@ -14,13 +16,83 @@ public static class Config /// Loads the given SilkTouch JSON Configuration into a dictionary of per-project configurations. /// /// The SilkTouch JSON Configuration. - /// - /// + /// A dictionary containing project-specific configuration. + /// If the data yielded a null configuration. public static Dictionary Load(string json) => JsonSerializer.Deserialize>(json) ?? throw new DataException("JSON deserialization of SilkTouch Configuration yielded null."); + /// + /// Saves the given per-project configuration dictionary into JSON. + /// + /// A dictionary containing per-project configuration. + /// public static string Save(Dictionary projects) => JsonSerializer.Serialize(projects); } + + public record RootConfiguration + ( + [property: JsonPropertyName("fileHeader")] string[]? FileHeaderLines, + [property: JsonPropertyName("projects")] Dictionary Projects + ); + + /// + /// The root project configuration structure. + /// + /// SilkTouch Emitter specific configuration for this project. + /// SilkTouch Overloader specific configuration for this project. + /// SilkTouch Scraper specific configuration for this project. + public record ProjectConfiguration + ( + [property: JsonPropertyName("emitter")] EmitterConfiguration Emitter, + [property: JsonPropertyName("overloader")] OverloaderConfiguration Overloader, + [property: JsonPropertyName("scraper")] ScraperConfiguration Scraper + ); + + /// + /// SilkTouch Emitter specific configuration. + /// + /// + /// The form factors in which the Emitter should run. If null, defaults to just Roslyn. + /// + public record EmitterConfiguration + ( + [property: JsonPropertyName("mode"), JsonConverter(typeof(FormFactorsJsonConverter))] + FormFactors? FormFactors + ); + + /// + /// SilkTouch Emitter specific configuration. + /// + /// + /// The form factors in which the Emitter should run. If null, defaults to just Roslyn. + /// + public record OverloaderConfiguration + ( + [property: JsonPropertyName("mode"), JsonConverter(typeof(FormFactorsJsonConverter))] + FormFactors? FormFactors + ); + + /// + /// SilkTouch Scraper specific configuration. + /// + public record ScraperConfiguration + ( + [property: JsonPropertyName("text")] string[]? HeaderText, + [property: JsonPropertyName("include")] string[]? IncludeDirectories, + [property: JsonPropertyName("traverse")] string[]? Traverse, + [property: JsonPropertyName("unixMode")] bool? UnixMode, + [property: JsonPropertyName("exclude")] string[]? Exclude, + [property: JsonPropertyName("mods")] string[]? Mods, + [property: JsonPropertyName("modOptions")] Dictionary? Properties, + [property: JsonPropertyName("libraryNames")] string[]? LibraryNames, + [property: JsonPropertyName("namespace")] string? Namespace + ); + + public record Excludes + ( + HashSet Identifiers, + ExclusionHint Hints + ); } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs new file mode 100644 index 0000000000..73dd992eaf --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Silk.NET.SilkTouch.Configuration +{ + [Flags] + public enum ExclusionHint + { + None = 0, + AnonymousFieldHelpers = 1 << 0, + ComProxies = 1 << 1, + DefaultRemappings = 1 << 2, + EmptyRecords = 1 << 3, + EnumOperators = 1 << 4, + FunctionsWithBodies = 1 << 5, + UsingStaticForEnums = 1 << 6, + AggressiveInlining = 1 << 7, + CppAttributes = 1 << 8, + MacroBindings = 1 << 9, + NativeInheritanceAttribute = 1 << 10 + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs index 7cec3a454f..ad714f86d0 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs @@ -10,6 +10,6 @@ public enum FormFactors { Any = 0, Roslyn = 1 << 0, - CommandLine = 1 << 1 + CLI = 1 << 1 } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs new file mode 100644 index 0000000000..8cb4db2f20 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Silk.NET.SilkTouch.Configuration.Json +{ + public class ExcludesJsonConverter : JsonConverter + { + public override Excludes? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var list = JsonSerializer.Deserialize(ref reader); + if (list is null) + { + return null; + } + + var identifiers = new HashSet(); + var hints = ExclusionHint.None; + foreach (var exclusion in list) + { + var kvp = exclusion.Split(Constants.SemicolonDelimited, StringSplitOptions.RemoveEmptyEntries); + // this is the ugliest piece of code I think I've ever wrote. basically, if it's a hint: + // - join everything past the "hint: " in the string into one big string + // - remove anything that isn't a letter (using NS20 APIs) + // - parse the resultant string as a hint + // given the number of calls to this code does not go up with the number of lines generated, + // the performance is horrible but it's not too much a cause for concern as it is a "fixed cost". + // definitely improve it in the future though as we do have access to new string(char*), meaning we can + // come up with a Span-based solution it'll just be very very very verbose. + if (kvp[0].ToLower().Trim() == "hint" && Enum.TryParse + ( + string.Join + ( + string.Empty, + kvp.Skip(1).Select + ( + static x => new string(x.Where(static y => char.IsUpper(y) || char.IsLower(y)).ToArray()) + ) + ), out ExclusionHint hint + )) + { + hints |= hint; + } + else if (kvp[0].ToLower().Trim() == "name") + { + identifiers.Add(string.Join(":", kvp.Skip(1))); + } + else + { + identifiers.Add(exclusion); + } + } + + return new(identifiers, hints); + } + + public override void Write(Utf8JsonWriter writer, Excludes? value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs new file mode 100644 index 0000000000..3f48d4996e --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Silk.NET.SilkTouch.Configuration.Json +{ + /// + /// A for . + /// + public class FormFactorsJsonConverter : JsonConverter + { + /// + public override FormFactors? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => StringToFormFactors(reader.GetString()); + + /// + public override void Write(Utf8JsonWriter writer, FormFactors? value, JsonSerializerOptions options) + => writer.WriteStringValue(value is not null ? FormFactorsToString(value.Value) : null); + + private static string FormFactorsToString(FormFactors ff) + => (((ff & FormFactors.Roslyn) == FormFactors.Roslyn ? "Roslyn;" : string.Empty) + + ((ff & FormFactors.CLI) == FormFactors.CLI ? "CLI;" : string.Empty)) + .TrimEnd(';'); + + private static FormFactors? StringToFormFactors(string? str) + => str? + .Split(Constants.SemicolonDelimited, StringSplitOptions.RemoveEmptyEntries) + .Aggregate + ( + FormFactors.Any, static(current, ff) => current | ff.Trim().ToLower() switch + { + "roslyn" => FormFactors.Roslyn, + "cli" => FormFactors.CLI, + "commandline" => FormFactors.CLI, + _ => FormFactors.Any + } + ); + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs b/src/generators/Silk.NET.SilkTouch.Common/Constants.cs similarity index 73% rename from src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs rename to src/generators/Silk.NET.SilkTouch.Common/Constants.cs index f725ec30dc..d96ec1ea95 100644 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/Constants.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Constants.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Silk.NET.SilkTouch.Roslyn +namespace Silk.NET.SilkTouch { public static class Constants { public const string ConfigFileEditorconfigOption = "silktouch_config_file"; + internal static readonly char[] SemicolonDelimited = {';'}; } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs new file mode 100644 index 0000000000..2a7e7c2d1b --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace Silk.NET.SilkTouch +{ + public class Diagnostics + { + public static DiagnosticDescriptor MultipleConfigFiles { get; } = new + ( + id: "ST0005", + title: "Multiple Configuration Files Detected", + messageFormat: "Multiple configuration files detected. Using \"{0}\", to use \"{1}\" instead configure " + + $"the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig option.", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor NoConfigFile { get; } = new + ( + id: "ST0006", + title: "No Configuration File", + messageFormat: "No configuration file, SilkTouch will not run.. To configure a path to a SilkTouch JSON " + + $"Configuration file, use the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig " + + "option.", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: false, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor NoAssemblyName { get; } = new + ( + id: "ST0007", + title: "Couldn't Determine Assembly Name", + messageFormat: "Couldn't determine \"AssemblyName\", SilkTouch will not run.", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor NoLibraryName { get; } = new + ( + id: "ST0008", + title: "No Library Name", + messageFormat: "Specify at least one library name " + + "(\"projects\" > \"{0}\" > \"scraper\" > \"libraryNames\")", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs index d8b42d7bd3..3a23d30565 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Silk.NET.SilkTouch.Configuration; @@ -11,18 +10,30 @@ namespace Silk.NET.SilkTouch.Generation { public sealed class SilkTouchContext { - // TODO make constructors because .NET Standard 2.0 is crap and we can't have init properties. + /// + /// Creates a with the given parameters. + /// + /// The assembly name for this compilation. + /// The syntax trees in the compilation. + /// + public SilkTouchContext(string assemblyName, IEnumerable syntaxTrees, ProjectConfiguration config) + => (AssemblyName, SyntaxTrees, Configuration) = (assemblyName, syntaxTrees, config); // Public Properties + /// + /// The assembly name for the project represented by this context. + /// + public string AssemblyName { get; } + /// /// The syntax trees contained within the current project. /// - public IEnumerable SyntaxTrees { get; set; } = Enumerable.Empty(); + public IEnumerable SyntaxTrees { get; } /// - /// The + /// The SilkTouch Configuration for this project. /// - public ProjectConfiguration Configuration { get; set; } + public ProjectConfiguration Configuration { get; } // Internal Properties internal List<(string FileNameHint, string Content)> Outputs { get; } = new(); @@ -34,6 +45,10 @@ public void EmitOutput(string fileNameHint, string content) public void EmitDiagnostic(Diagnostic diagnostic) => Diagnostics.Add(diagnostic); + /// + /// Gets the outputs and diagnostics generated by the generator using this context. + /// + /// Outputs and diagnostics generated by the generator using this context. public (List<(string FileNameHint, string Content)> Outputs, List) GetResult() => (Outputs, Diagnostics); } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Shims.cs b/src/generators/Silk.NET.SilkTouch.Common/Shims.cs new file mode 100644 index 0000000000..b74d53a4a6 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Shims.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj index f3ea830dde..9b7139ec80 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj +++ b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj @@ -10,6 +10,7 @@ + diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs index bab272af2e..2300854978 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs @@ -1,10 +1,12 @@ using System; +using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; namespace Silk.NET.SilkTouch.Emitter { public static class EmitterGenerator { + public const FormFactors DefaultFormFactors = FormFactors.Roslyn; public static void Run(SilkTouchContext ctx) { // TODO implement diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs index 50199e1530..d65da3bd6e 100644 --- a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs @@ -1,10 +1,12 @@ using System; +using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; namespace Silk.NET.SilkTouch.Overloader { public static class OverloaderGenerator { + public const FormFactors DefaultFormFactors = FormFactors.Roslyn; public static void Run(SilkTouchContext ctx) { // TODO implement diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs deleted file mode 100644 index 82cc6e2a75..0000000000 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/Diagnostics.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Silk.NET.SilkTouch.Roslyn -{ - public class Diagnostics - { - public static DiagnosticDescriptor MultipleConfigFiles { get; } = new - ( - id: "ST0005", - title: "Multiple Configuration Files Detected", - messageFormat: $"Multiple configuration files detected. Using \"{{0}}\" instead, to use \"{{1}}\" configure the \"{Constants.ConfigFileEditorconfigOption}\"", - category: "SilkTouch.Internal", - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: null, - helpLinkUri: null, - customTags: WellKnownDiagnosticTags.AnalyzerException - ); - } -} diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs index d598be5973..512be05f40 100644 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs @@ -12,6 +12,7 @@ using Silk.NET.SilkTouch.Emitter; using Silk.NET.SilkTouch.Generation; using Silk.NET.SilkTouch.Overloader; +using Ultz.Extensions.Logging; namespace Silk.NET.SilkTouch.Roslyn { @@ -24,6 +25,7 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext context) { + // Get the config file name. Uses silktouch.json unless overridden in .editorconfig. var configFileName = "silktouch.json"; if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue (Constants.ConfigFileEditorconfigOption, out var file)) @@ -31,6 +33,7 @@ public void Execute(GeneratorExecutionContext context) configFileName = file; } + // Try and find an AdditionalFiles entry for the SilkTouch config. string? configFilePath = null; foreach (var additionalFile in context.AdditionalFiles) { @@ -43,35 +46,74 @@ public void Execute(GeneratorExecutionContext context) Diagnostic.Create ( Diagnostics.MultipleConfigFiles, - Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default) + Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default), + configFilePath, + additionalFile.Path ) ); + + continue; } configFilePath = additionalFile.Path; } } + + // Prevent debug logs from being output by forcing the LoggerProvider to null - we don't want this in Roslyn + Log.LoggerProvider = null; if (configFilePath is null) { - // TODO report diagnostic that this isn't found. + context.ReportDiagnostic(Diagnostic.Create(Diagnostics.NoConfigFile, Location.None)); + return; + } + + if (context.Compilation.AssemblyName is null) + { + context.ReportDiagnostic(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); + return; } - // TODO fix all the warnings in the morning because i'm tired and just wanna commit something. + // prepare our context data var syntaxTrees = context.Compilation.SyntaxTrees.OfType().ToArray(); var projectConfig = Config.Load(configFilePath)[context.Compilation.AssemblyName]; - var ctx = new SilkTouchContext { SyntaxTrees = syntaxTrees, Configuration = projectConfig }; + // run the emitter if the config indicates we should. + if (((projectConfig.Emitter.FormFactors ?? EmitterGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) + { + var ctx = new SilkTouchContext(syntaxTrees, projectConfig); + EmitterGenerator.Run(ctx); + var (outputs, diagnostics) = ctx.GetResult(); + Copy(context, outputs, diagnostics); + } - EmitterGenerator.Run(ctx); - var (emitterOutputs, emitterDiagnostics) = ctx.GetResult(); + // run the overloader if the config indicates we should. + if (((projectConfig.Overloader.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & FormFactors.Roslyn) + != 0) + { + var ctx = new SilkTouchContext(syntaxTrees, projectConfig); + OverloaderGenerator.Run(ctx); + var (outputs, diagnostics) = ctx.GetResult(); + Copy(context, outputs, diagnostics); + } + } - ctx = new() { SyntaxTrees = syntaxTrees, Configuration = projectConfig }; + private static void Copy + ( + GeneratorExecutionContext context, + IEnumerable<(string FileNameHint, string Content)> outputs, + IEnumerable diagnostics + ) + { + foreach (var (fileNameHint, content) in outputs) + { + context.AddSource(fileNameHint, content); + } - OverloaderGenerator.Run(ctx); - var (overloaderOutputs, overloaderDiagnostics) = ctx.GetResult(); - - // TODO add the outputs and the diagnostics to roslyn. seriously i'm tired and cba to finish the job. + foreach (var diagnostic in diagnostics) + { + context.ReportDiagnostic(diagnostic); + } } } } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index 9f11284ae6..25ada14e2c 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -1,13 +1,57 @@ using System; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; +using Silk.NET.SilkTouch.Scraper.Subagent; +using Ultz.Extensions.Logging; namespace Silk.NET.SilkTouch.Scraper { public static class ScraperGenerator { - public static void Run(SilkTouchContext ctx) + public const FormFactors DefaultFormFactors = FormFactors.CLI; + public static void Run(SilkTouchContext ctx) where T:ISubagentSpawner, new() { - + var subagentSpawner = new T(); + var error = false; + if ((ctx.Configuration.Scraper.LibraryNames?.Length ?? 0) == 0) + { + ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoLibraryName, Location.None, ctx.AssemblyName)); + error = true; + } + + if (error) + { + return; + } + + var workFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(workFolder); + + var includes = ctx.Configuration.Scraper.IncludeDirectories?.ToList() ?? new(); + for (int i = 0; i < includes.Count; i++) + { + var include = includes[i]; + if (include == "win10-sdk") + { + + } + else if (include.StartsWith("win10-sdk-")) + { + + } + } + + Log.Information($"Processing \"{ctx.AssemblyName}\"..."); + var options = new SubagentOptions + ( + ctx.Configuration.Scraper.LibraryNames[0], + ctx.Configuration.Scraper.Namespace ?? ctx.AssemblyName, + workFolder, + workFolder, + ); } } } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj index 696d6973d4..d591c1f6bb 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -6,7 +6,12 @@ + + + + + diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs new file mode 100644 index 0000000000..b0c0a9c1da --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.Scraper.Subagent +{ + public class ClangSharpSubagent + { + + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs new file mode 100644 index 0000000000..f329f00d5b --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; + +namespace Silk.NET.SilkTouch.Scraper.Subagent +{ + /// + /// A class capable of creating ClangSharp subprocesses/subagents. + /// + public interface ISubagentSpawner + { + /// + /// Runs a ClangSharp subprocess with the given options. + /// + /// The options to run the subagent with. + /// An asynchronous task which, when awaited, yields the exit code of the subprocess. + Task RunClangSharpAsync(SubagentOptions opts); + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs new file mode 100644 index 0000000000..d753b90640 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using ClangSharp; + +namespace Silk.NET.SilkTouch.Scraper.Subagent +{ + /// + /// Encapsulates ClangSharp options in a serializable record. + /// + /// + /// The only reason this exists is because doesn't play nicely with JSON + /// serialization, and we use JSON serialization to communicate the options to the subprocesses. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The file containing the for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + /// + /// The for ClangSharp to use in XML generation. + /// + public record SubagentOptions + ( + string LibraryPath, + string NamespaceName, + string OutputLocation, + string TestOutputLocation, + string[] IncludeDirectories, + PInvokeGeneratorOutputMode OutputMode = PInvokeGeneratorOutputMode.CSharp, + PInvokeGeneratorConfigurationOptions Options = PInvokeGeneratorConfigurationOptions.None, + string Language = "c++", + string Standard = "c++17", + string[]? AdditionalArgs = null, + string[]? DefineMacros = null, + string[]? ExcludedNames = null, + string? HeaderFile = null, + string? MethodClassName = null, + string? MethodPrefixToStrip = null, + IReadOnlyDictionary? RemappedNames = null, + string[]? TraversalNames = null, + IReadOnlyDictionary>? WithAttributes = null, + IReadOnlyDictionary? WithCallConvs = null, + IReadOnlyDictionary? WithLibraryPaths = null, + string[]? WithSetLastErrors = null, + IReadOnlyDictionary? WithTypes = null, + IReadOnlyDictionary>? WithUsings = null + ) + { + /// + /// Converts the given to a ClangSharp-consumable + /// . + /// + /// The options to convert. + /// The converted options. + public static implicit operator PInvokeGeneratorConfiguration(SubagentOptions opts) => new + ( + opts.LibraryPath, + opts.NamespaceName, + opts.OutputLocation, + opts.TestOutputLocation, + opts.OutputMode, + opts.Options, + opts.ExcludedNames, + opts.HeaderFile, + opts.MethodClassName, + opts.MethodPrefixToStrip, + opts.RemappedNames, + opts.TraversalNames, + opts.WithAttributes, + opts.WithCallConvs, + opts.WithLibraryPaths, + opts.WithSetLastErrors, + opts.WithTypes, + opts.WithUsings + ); + } +} diff --git a/src/generators/SilkTouch/ClangSharpHandoff.cs b/src/generators/SilkTouch/ClangSharpHandoff.cs new file mode 100644 index 0000000000..ab477d5772 --- /dev/null +++ b/src/generators/SilkTouch/ClangSharpHandoff.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.Json; +using ClangSharp; +using ClangSharp.Interop; +using Silk.NET.SilkTouch.Scraper.Subagent; + +namespace SilkTouch +{ + internal static class ClangSharpHandoff + { + [SuppressMessage("ReSharper", "BitwiseOperatorOnEnumWithoutFlags")] + public static int RunClangSharp(string[] args) + { + SubagentOptions? options; + if (args.Length != 2 || (options = JsonSerializer.Deserialize(args[1])) is null) + { + Console.WriteLine + ( + "E:The \"silktouch clangsharp\" command is an implementation detail and should not be used. " + + "To use ClangSharp, visit https://github.com/microsoft/clangsharp." + ); + + return 1; + } + + // the code below is based off ClangSharp + // Copyright (c) Microsoft and Contributors. All rights reserved. + // Licensed under the University of Illinois/NCSA Open Source License. + // https://github.com/microsoft/ClangSharp/blob/main/LICENSE.txt + var clangCommandLineArgs = new List + { + $"--language={options.Language}", // Treat subsequent input files as having type + $"--std={options.Standard}", // Language standard to compile for + "-Wno-pragma-once-outside-header" // We are processing files which may be header files + }; + + clangCommandLineArgs.AddRange(options.IncludeDirectories.Select(x => "--include-directory=" + x)); + clangCommandLineArgs.AddRange(options.IncludeDirectories.Select(x => "--define-macro=" + x)); + clangCommandLineArgs.AddRange(options.AdditionalArgs ?? Enumerable.Empty()); + + var translationFlags = CXTranslationUnit_Flags.CXTranslationUnit_None; + + translationFlags |= + CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes; // Include attributed types in CXType + translationFlags |= + CXTranslationUnit_Flags.CXTranslationUnit_VisitImplicitAttributes; // Implicit attrs should be visited + + var config = (PInvokeGeneratorConfiguration) options; + + if (config.GenerateMacroBindings) + { + translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_DetailedPreprocessingRecord; + } + + var exitCode = 0; + + var finishedCommandLineArgs = clangCommandLineArgs.ToArray(); + using (var pinvokeGenerator = new PInvokeGenerator(config)) + { + var translationUnitError = CXTranslationUnit.TryParse + ( + pinvokeGenerator.IndexHandle, options.HeaderFile, finishedCommandLineArgs, + Array.Empty(), translationFlags, out var handle + ); + var skipProcessing = false; + + if (translationUnitError != CXErrorCode.CXError_Success) + { + Console.WriteLine($"E:Parsing failed due to '{translationUnitError}'."); + skipProcessing = true; + } + else if (handle.NumDiagnostics != 0) + { + for (uint i = 0; i < handle.NumDiagnostics; ++i) + { + using var diagnostic = handle.GetDiagnostic(i); + + Console.Write + ( + diagnostic.Severity switch + { + CXDiagnosticSeverity.CXDiagnostic_Error => "E:", + CXDiagnosticSeverity.CXDiagnostic_Fatal => "E:", + CXDiagnosticSeverity.CXDiagnostic_Warning => "W:", + CXDiagnosticSeverity.CXDiagnostic_Ignored => "T:", + CXDiagnosticSeverity.CXDiagnostic_Note => "T:", + _ => "T:" + } + ); + + Console.WriteLine(diagnostic.Format(CXDiagnostic.DefaultDisplayOptions).ToString()); + + skipProcessing |= diagnostic.Severity == CXDiagnosticSeverity.CXDiagnostic_Error; + skipProcessing |= diagnostic.Severity == CXDiagnosticSeverity.CXDiagnostic_Fatal; + } + } + + if (skipProcessing) + { + Console.WriteLine("E:One or more errors during parsing."); + return 2; + } + + try + { + using var translationUnit = TranslationUnit.GetOrCreate(handle); + Console.WriteLine("I:Processing..."); + + pinvokeGenerator.GenerateBindings + (translationUnit, options.HeaderFile, finishedCommandLineArgs, translationFlags); + } + catch (Exception e) + { + Console.WriteLine("E:" + e); + } + + if (pinvokeGenerator.Diagnostics.Count != 0) + { + foreach (var diagnostic in pinvokeGenerator.Diagnostics) + { + Console.Write + ( + diagnostic.Level switch + { + DiagnosticLevel.Info => "T:", + DiagnosticLevel.Warning => "W:", + DiagnosticLevel.Error => "E:", + _ => "T:" + } + ); + Console.WriteLine(diagnostic); + if (diagnostic.Level == DiagnosticLevel.Error) + { + exitCode--; + } + } + } + + if (exitCode < 0) + { + Console.WriteLine("E:One or more errors during bindings generation."); + } + } + + return exitCode; + } + } +} diff --git a/src/generators/SilkTouch/CommandLineSubagentSpawner.cs b/src/generators/SilkTouch/CommandLineSubagentSpawner.cs new file mode 100644 index 0000000000..876e4ccea2 --- /dev/null +++ b/src/generators/SilkTouch/CommandLineSubagentSpawner.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Silk.NET.SilkTouch.Scraper.Subagent; + +namespace SilkTouch +{ + /// + /// Spawns ClangSharp in a subprocess. This allows for parallelism (Clang doesn't like threads) + /// + internal class CommandLineSubagentSpawner : ISubagentSpawner + { + /// + public async Task RunClangSharpAsync(SubagentOptions opts) + { + // get the command line arguments this process was started with + // using ArraySegment (lesser span) here instead of Span because it's enumerable. + ArraySegment args = Environment.GetCommandLineArgs(); + + // trim off the arguments *we* received + args = args[..^Program.Args.Length]; + + // serialize the options and escape the quotes to send on the command line. + var optsStr = JsonSerializer.Serialize(opts).Replace("\"", "\\\""); + + // the remainder is what we use to start the subprocesses. + using var proc = new Process + { + StartInfo = new + ( + args[0], + $"\"{string.Join("\" \"", args[1..].Select(x => x.Replace("\"", "\\\"")))}\" \"{optsStr}\"" + ) + { + WorkingDirectory = Environment.CurrentDirectory + } + }; + + if (!proc.Start()) + { + return 3; + } + + // run the subprocess. + await proc.WaitForExitAsync(); + return proc.ExitCode; + } + } +} diff --git a/src/generators/SilkTouch/Program.cs b/src/generators/SilkTouch/Program.cs index 69879b6552..519ca5edf1 100644 --- a/src/generators/SilkTouch/Program.cs +++ b/src/generators/SilkTouch/Program.cs @@ -1,12 +1,33 @@ using System; +using System.Diagnostics; +using System.Text.Json; +using Silk.NET.SilkTouch.Scraper.Subagent; namespace SilkTouch { - class Program + internal class Program { - static void Main(string[] args) + public static string[] Args { get; internal set; } + static int Main(string[] args) { - Console.WriteLine("Hello World!"); + Args = args; + if (args.Length > 0 && args[0].ToLower() == "clangsharp") + { + return ClangSharpHandoff.RunClangSharp(args); + } + + Console.WriteLine + ( + "Silk.NET SilkTouch - " + + $"v{typeof(Program).Assembly.GetName().Version?.ToString(3)} - " + + "Copyright (c) .NET Foundation and Contributors" + ); + + var sw = new Stopwatch(); + + sw.Stop(); + Console.WriteLine($"Concluded after {sw.Elapsed.TotalSeconds} seconds."); + return 0; } } } diff --git a/src/generators/SilkTouch/SilkTouch.csproj b/src/generators/SilkTouch/SilkTouch.csproj index 7b96d01817..c61214fd36 100644 --- a/src/generators/SilkTouch/SilkTouch.csproj +++ b/src/generators/SilkTouch/SilkTouch.csproj @@ -3,6 +3,16 @@ Exe net6.0 + true + silktouch + + + + + + + + diff --git a/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj b/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj new file mode 100644 index 0000000000..58ef59a24a --- /dev/null +++ b/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj @@ -0,0 +1,8 @@ + + + + net6.0 + enable + + + From 4e26e3d00e97a7fb84be7330a6d04d3b9997c805 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Wed, 4 Aug 2021 00:25:45 +0100 Subject: [PATCH 03/10] Spent far too long working on a Windows SDK/MSVS resolver and not tested --- .../Configuration/Config.cs | 22 +- .../ScraperGenerator.cs | 11 +- .../Silk.NET.SilkTouch.Scraper.csproj | 7 + .../Subagent/VisualStudioResolver.cs | 205 ++++++++++++++++++ .../Subagent/VisualStudioVarPrint.bat | 8 + 5 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.bat diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index 78c5ae2597..ed19fab685 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -13,20 +13,20 @@ namespace Silk.NET.SilkTouch.Configuration public static class Config { /// - /// Loads the given SilkTouch JSON Configuration into a dictionary of per-project configurations. + /// Loads the given SilkTouch JSON Configuration as a record. /// /// The SilkTouch JSON Configuration. - /// A dictionary containing project-specific configuration. + /// The record representation of the JSON. /// If the data yielded a null configuration. - public static Dictionary Load(string json) - => JsonSerializer.Deserialize>(json) ?? + public static RootConfiguration Load(string json) + => JsonSerializer.Deserialize(json) ?? throw new DataException("JSON deserialization of SilkTouch Configuration yielded null."); /// - /// Saves the given per-project configuration dictionary into JSON. + /// Saves the given record into JSON. /// - /// A dictionary containing per-project configuration. - /// + /// The record representation of the configuration. + /// The JSON representation of the projects. public static string Save(Dictionary projects) => JsonSerializer.Serialize(projects); } @@ -34,7 +34,7 @@ public static string Save(Dictionary projects) public record RootConfiguration ( [property: JsonPropertyName("fileHeader")] string[]? FileHeaderLines, - [property: JsonPropertyName("projects")] Dictionary Projects + [property: JsonPropertyName("projects")] Dictionary? Projects ); /// @@ -45,9 +45,9 @@ public record RootConfiguration /// SilkTouch Scraper specific configuration for this project. public record ProjectConfiguration ( - [property: JsonPropertyName("emitter")] EmitterConfiguration Emitter, - [property: JsonPropertyName("overloader")] OverloaderConfiguration Overloader, - [property: JsonPropertyName("scraper")] ScraperConfiguration Scraper + [property: JsonPropertyName("emitter")] EmitterConfiguration? Emitter, + [property: JsonPropertyName("overloader")] OverloaderConfiguration? Overloader, + [property: JsonPropertyName("scraper")] ScraperConfiguration? Scraper ); /// diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index 25ada14e2c..a5691b5246 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -16,7 +16,8 @@ public static class ScraperGenerator { var subagentSpawner = new T(); var error = false; - if ((ctx.Configuration.Scraper.LibraryNames?.Length ?? 0) == 0) + var libraryNames = ctx.Configuration.Scraper?.LibraryNames; + if ((libraryNames?.Length ?? 0) == 0) { ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoLibraryName, Location.None, ctx.AssemblyName)); error = true; @@ -30,7 +31,7 @@ public static class ScraperGenerator var workFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(workFolder); - var includes = ctx.Configuration.Scraper.IncludeDirectories?.ToList() ?? new(); + var includes = ctx.Configuration.Scraper?.IncludeDirectories?.ToList() ?? new(); for (int i = 0; i < includes.Count; i++) { var include = includes[i]; @@ -44,11 +45,11 @@ public static class ScraperGenerator } } - Log.Information($"Processing \"{ctx.AssemblyName}\"..."); + Log.Information($"Starting ClangSharp subagent for \"{ctx.AssemblyName}\"..."); var options = new SubagentOptions ( - ctx.Configuration.Scraper.LibraryNames[0], - ctx.Configuration.Scraper.Namespace ?? ctx.AssemblyName, + libraryNames![0], // we know libraryNames isn't null here, but the compiler disagrees. + ctx.Configuration.Scraper?.Namespace ?? ctx.AssemblyName, workFolder, workFolder, ); diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj index d591c1f6bb..7adb5e7bb0 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -7,6 +7,8 @@ + + @@ -14,4 +16,9 @@ + + + + + diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs new file mode 100644 index 0000000000..682d448b08 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Management; +using System.Net; +using System.Runtime.Versioning; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; +using Ultz.Extensions.Logging; + +namespace Silk.NET.SilkTouch.Scraper.Subagent +{ + public record VisualStudioInfo + ( + string Name, + string InstallationBaseFolder, + string UcrtSdkDir, + string[] UcrtIncludes, + Version UcrtVersion, + string MsvcToolsFolder, + string[] MsvcToolsIncludes + ); + + internal record VisualStudioCandidate + ( + string Name, + string InstallationPath + ); + + [SupportedOSPlatform("windows")] + public static class VisualStudioResolver + { + internal static readonly string VarPrintScriptNamespace = + $"{typeof(VisualStudioResolver).Namespace}.VisualStudioVarPrint.bat"; + + internal const string MsvcInstallDirVar = "VCToolsInstallDir"; + internal static readonly string[] MsvcIncludeSubDirs = { "include" }; + + internal const string WinSdkUcrtSdkDirVar = "UniversalCRTSdkDir"; + internal const string WinSdkUcrtVersionVar = "UCRTVersion"; + internal static readonly string[] WinSdkUcrtIncludeSubDirs = + { + "Include/{0}/um", + "Include/{0}/ucrt", + "Include/{0}/shared" + }; + + public static bool TryGetVisualStudioInfo(bool allowPrerelease, out VisualStudioInfo? info) + { + ManagementObjectCollection mcCollection; + + try + { + using ManagementClass mc = new("MSFT_VSInstance"); + mcCollection = mc.GetInstances(); + } + catch (Exception e) + { + Log.Debug($"Failed to get \"MSFT_VSInstance\" info with exception {e}"); + info = null; + return false; + } + + //We want to buildtools if they have it installec, we'll use VS installs if needed + var visualStudios = new VisualStudioCandidate[mcCollection.Count]; + var i = 0; + foreach (var result in mcCollection) + { + var name = Convert.ToString(result["Name"]); + var path = Convert.ToString(result["InstallLocation"]); + if (name is null || path is null) + { + continue; + } + + visualStudios[i++] = new(name, path); + } + + // extract the varprint script + var varPrint = Path.Combine(Path.GetTempPath(), $"{Path.GetRandomFileName()}.bat"); + using var varPrintRes = typeof(VisualStudioResolver).Assembly + .GetManifestResourceStream(VarPrintScriptNamespace); + if (varPrintRes is null) + { + Log.Debug($"Couldn't extract \"{VarPrintScriptNamespace}\" from assembly."); + Log.Warning("Internal error."); + info = null; + return false; + } + + using var varPrintOut = File.OpenWrite(varPrint); + varPrintRes.CopyTo(varPrintOut); + + // cycle through candidate installations, try and find everything we're looking for. + string? msvcInstallDir = null; + string? winSdkUcrtSdkDir = null; + string? winSdkUcrtVersion = null; + var listening = true; + foreach (var (name, installationPath) in visualStudios) + { + Log.Debug($"Testing \"{name}\" at \"{installationPath}\"..."); + using var varPrintProc = new Process + { + StartInfo = new() + { + FileName = "cmd", + Arguments = $"/c \"{varPrint}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + varPrintProc.Start(); + while (!varPrintProc.StandardOutput.EndOfStream) + { + var line = varPrintProc.StandardOutput.ReadLine(); + if (line is null) + { + break; + } + + Log.Debug(line); + + if (line == "***VARSTART***") + { + listening = true; + continue; + } + + if (line == "***VAREND***") + { + listening = false; + break; + } + + if (listening) + { + var split = line.Split('='); + var _ = split[0] switch + { + MsvcInstallDirVar => msvcInstallDir = split[1], + WinSdkUcrtSdkDirVar => winSdkUcrtSdkDir = split[1], + WinSdkUcrtVersionVar => winSdkUcrtVersion = split[1], + _ => string.Empty + }; + } + } + + Log.Debug($"msvcInstallDir = \"{msvcInstallDir}\""); + Log.Debug($"winSdkUcrtSdkDir = \"{winSdkUcrtSdkDir}\""); + Log.Debug($"winSdkUcrtVersion = \"{winSdkUcrtVersion}\""); + if (msvcInstallDir is null || winSdkUcrtSdkDir is null || winSdkUcrtVersion is null) + { + Log.Trace($"\"{name}\" is not a viable candidate."); + continue; + } + + var ucrtDirs = WinSdkUcrtIncludeSubDirs + .Select(x => Path.Combine(winSdkUcrtSdkDir, string.Format(x, winSdkUcrtVersion))) + .Where(Directory.Exists) + .ToArray(); + + var msvcDirs = MsvcIncludeSubDirs + .Select(x => Path.Combine(msvcInstallDir, x)) + .Where(Directory.Exists) + .ToArray(); + + info = new + ( + name, + installationPath, + winSdkUcrtSdkDir, + ucrtDirs, + Version.Parse(winSdkUcrtVersion), + msvcInstallDir, + msvcDirs + ); + + Log.Information + ( + $"Using \"{name}\" (in \"{installationPath}\") with Windows SDK v{info.UcrtVersion.ToString(4)} " + + $"({ucrtDirs.Length} include(s)) and MSVC ({msvcDirs.Length} include(s))" + ); + + return true; + } + + // if any of it's still null, we couldn't find a candidate. + Log.Warning + ( + "Couldn't find a viable Visual Studio installation - ensure you have the Windows 10 SDK and C++ " + + "tools installed." + ); + + info = null; + return false; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.bat b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.bat new file mode 100644 index 0000000000..da6b778876 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.bat @@ -0,0 +1,8 @@ +:: Licensed to the .NET Foundation under one or more agreements. +:: The .NET Foundation licenses this file to you under the MIT license. + +echo off +.\Common7\Tools\VsDevCmd.bat >NUL +echo ***VARSTART*** +set +echo ***VAREND*** \ No newline at end of file From 8e073172ccb606d3824068c48591f44ab7db4d53 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Thu, 5 Aug 2021 00:45:43 +0100 Subject: [PATCH 04/10] Scraper (up to XML gen) nearly ready&joint CLI/roslyn iface nearly ready --- .../Configuration/Config.cs | 98 ++++++++- .../Configuration/ExclusionHint.cs | 4 +- .../Configuration/FormFactors.cs | 2 +- .../Json/ExcludesJsonConverter.cs | 4 +- .../Json/FormFactorsJsonConverter.cs | 38 ++-- .../Silk.NET.SilkTouch.Common/Diagnostics.cs | 96 ++++++++- .../Generation/GeneratorBase.cs | 17 ++ .../Generation/SilkTouchContext.cs | 38 +--- .../Silk.NET.SilkTouch.Common/Shims.cs | 2 + .../SilkTouchSourceGenerator.cs | 102 ++++++---- .../ACKNOWLEDGEMENTS.md | 21 ++ .../ClangSharpSubagent.cs => Mods/XmlMods.cs} | 4 +- .../ScraperGenerator.cs | 125 +++++++++++- .../Silk.NET.SilkTouch.Scraper.csproj | 7 +- .../{ISubagentSpawner.cs => ISubagent.cs} | 7 +- .../Subagent/SubagentOptions.cs | 7 +- .../Subagent/SubagentUtils.cs | 90 +++++++++ .../Subagent/VisualStudioResolver.cs | 180 +++++++---------- .../Subagent/VisualStudioVarPrint.cs | 105 ++++++++++ .../Transformation/XmlToCSharpTransformer.cs | 10 + src/generators/SilkTouch/ClangSharpHandoff.cs | 149 +++++++------- ...bagentSpawner.cs => ClangSharpSubagent.cs} | 55 ++++- src/generators/SilkTouch/ExitCodes.cs | 14 ++ src/generators/SilkTouch/GeneratorHandoff.cs | 168 ++++++++++++++++ src/generators/SilkTouch/LogMode.cs | 13 ++ src/generators/SilkTouch/Program.cs | 188 +++++++++++++++++- src/generators/SilkTouch/SilkTouch.csproj | 4 + 27 files changed, 1230 insertions(+), 318 deletions(-) create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/ACKNOWLEDGEMENTS.md rename src/generators/Silk.NET.SilkTouch.Scraper/{Subagent/ClangSharpSubagent.cs => Mods/XmlMods.cs} (66%) rename src/generators/Silk.NET.SilkTouch.Scraper/Subagent/{ISubagentSpawner.cs => ISubagent.cs} (69%) create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/Transformation/XmlToCSharpTransformer.cs rename src/generators/SilkTouch/{CommandLineSubagentSpawner.cs => ClangSharpSubagent.cs} (51%) create mode 100644 src/generators/SilkTouch/ExitCodes.cs create mode 100644 src/generators/SilkTouch/GeneratorHandoff.cs create mode 100644 src/generators/SilkTouch/LogMode.cs diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index ed19fab685..4c4bc9f593 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -2,10 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel; using System.Data; +using System.IO; using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; using Silk.NET.SilkTouch.Configuration.Json; namespace Silk.NET.SilkTouch.Configuration @@ -22,21 +27,87 @@ public static RootConfiguration Load(string json) => JsonSerializer.Deserialize(json) ?? throw new DataException("JSON deserialization of SilkTouch Configuration yielded null."); + public static Diagnostic? Load + ( + AnalyzerConfigOptionsProvider provider, + ImmutableArray additionalFiles, + out RootConfiguration? config, + out AdditionalText? usedText + ) + { + // Get the config file name. Uses silktouch.json unless overridden in .editorconfig. + var configFileName = "silktouch.json"; + if (provider.GlobalOptions + .TryGetValue(Constants.ConfigFileEditorconfigOption, out var file)) + { + configFileName = file; + } + + // Try and find an AdditionalFiles entry for the SilkTouch config. + usedText = null; + foreach (var additionalFile in additionalFiles) + { + if (additionalFile.Path == configFileName || Path.GetFileName(additionalFile.Path) == configFileName) + { + if (usedText is not null) + { + config = null; + var ret = Diagnostic.Create + ( + Diagnostics.MultipleConfigFiles, + Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default), + usedText.Path, + additionalFile.Path + ); + + usedText = null; + return ret; + } + + usedText = additionalFile; + } + } + + if (usedText is null) + { + config = null; + usedText = null; + return Diagnostic.Create(Diagnostics.NoConfigFile, Location.None); + } + + config = Load(File.ReadAllText(usedText.Path)); // was gonna use usedText.GetText() until I saw their code. + return null; + } + /// /// Saves the given record into JSON. /// - /// The record representation of the configuration. + /// The record representation of the configuration. /// The JSON representation of the projects. - public static string Save(Dictionary projects) - => JsonSerializer.Serialize(projects); + public static string Save(RootConfiguration config) + => JsonSerializer.Serialize(config); } + /// + /// The root configuration structure. + /// + /// + /// public record RootConfiguration ( - [property: JsonPropertyName("fileHeader")] string[]? FileHeaderLines, + [property: JsonPropertyName("global")] GlobalConfiguration Global, [property: JsonPropertyName("projects")] Dictionary? Projects ); + /// + /// Common configuration across all projects. + /// + /// The file header to add to all files. + public record GlobalConfiguration + ( + [property: JsonPropertyName("fileHeader")] string[]? FileHeaderLines + ); + /// /// The root project configuration structure. /// @@ -47,7 +118,8 @@ public record ProjectConfiguration ( [property: JsonPropertyName("emitter")] EmitterConfiguration? Emitter, [property: JsonPropertyName("overloader")] OverloaderConfiguration? Overloader, - [property: JsonPropertyName("scraper")] ScraperConfiguration? Scraper + [property: JsonPropertyName("scraper")] ScraperConfiguration? Scraper, + [property: JsonPropertyName("skipIf")] string[]? SkipIf ); /// @@ -79,20 +151,28 @@ public record OverloaderConfiguration /// public record ScraperConfiguration ( - [property: JsonPropertyName("text")] string[]? HeaderText, + [property: JsonPropertyName("headerText")] string[]? HeaderText, [property: JsonPropertyName("include")] string[]? IncludeDirectories, [property: JsonPropertyName("traverse")] string[]? Traverse, [property: JsonPropertyName("unixMode")] bool? UnixMode, - [property: JsonPropertyName("exclude")] string[]? Exclude, + [property: JsonPropertyName("exclude"), JsonConverter(typeof(ExcludesJsonConverter))] Excludes? Exclude, [property: JsonPropertyName("mods")] string[]? Mods, [property: JsonPropertyName("modOptions")] Dictionary? Properties, [property: JsonPropertyName("libraryNames")] string[]? LibraryNames, - [property: JsonPropertyName("namespace")] string? Namespace + [property: JsonPropertyName("namespace")] string? Namespace, + [property: JsonPropertyName("language")] string? Language, + [property: JsonPropertyName("std")] string? Standard, + [property: JsonPropertyName("clangArgs")] string[]? AdditionalClangArguments, + [property: JsonPropertyName("define")] string[]? DefineMacros, + [property: JsonPropertyName("className")] string? ClassName, + [property: JsonPropertyName("methodPrefix")] string? MethodPrefixToStrip, + [property: JsonPropertyName("remappingFiles")] string[]? RemappingFiles, + [property: JsonPropertyName("conventions")] Dictionary? CallingConventions ); public record Excludes ( - HashSet Identifiers, + string[] Identifiers, ExclusionHint Hints ); } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs index 73dd992eaf..503d11ed47 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs @@ -19,6 +19,8 @@ public enum ExclusionHint AggressiveInlining = 1 << 7, CppAttributes = 1 << 8, MacroBindings = 1 << 9, - NativeInheritanceAttribute = 1 << 10 + NativeInheritanceAttribute = 1 << 10, + TemplateBindings = 1 << 11, + VTableIndexAttribute = 1 << 12 } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs index ad714f86d0..0512159b19 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs @@ -8,7 +8,7 @@ namespace Silk.NET.SilkTouch.Configuration [Flags] public enum FormFactors { - Any = 0, + None = 0, Roslyn = 1 << 0, CLI = 1 << 1 } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs index 8cb4db2f20..d52c5563c5 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs @@ -19,7 +19,7 @@ public class ExcludesJsonConverter : JsonConverter return null; } - var identifiers = new HashSet(); + var identifiers = new List(list.Length); var hints = ExclusionHint.None; foreach (var exclusion in list) { @@ -56,7 +56,7 @@ public class ExcludesJsonConverter : JsonConverter } } - return new(identifiers, hints); + return new(identifiers.ToArray(), hints); } public override void Write(Utf8JsonWriter writer, Excludes? value, JsonSerializerOptions options) diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs index 3f48d4996e..e21d403c4d 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/FormFactorsJsonConverter.cs @@ -15,29 +15,25 @@ public class FormFactorsJsonConverter : JsonConverter { /// public override FormFactors? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => StringToFormFactors(reader.GetString()); + => JsonSerializer.Deserialize(ref reader)?.Aggregate(FormFactors.None, ParseFormFactorString); + + private static FormFactors ParseFormFactorString(FormFactors existing, string incoming) + => existing |= incoming.ToLower().Trim() switch + { + "roslyn" => FormFactors.Roslyn, + "cli" => FormFactors.CLI, + _ => FormFactors.None + }; /// public override void Write(Utf8JsonWriter writer, FormFactors? value, JsonSerializerOptions options) - => writer.WriteStringValue(value is not null ? FormFactorsToString(value.Value) : null); - - private static string FormFactorsToString(FormFactors ff) - => (((ff & FormFactors.Roslyn) == FormFactors.Roslyn ? "Roslyn;" : string.Empty) + - ((ff & FormFactors.CLI) == FormFactors.CLI ? "CLI;" : string.Empty)) - .TrimEnd(';'); - - private static FormFactors? StringToFormFactors(string? str) - => str? - .Split(Constants.SemicolonDelimited, StringSplitOptions.RemoveEmptyEntries) - .Aggregate - ( - FormFactors.Any, static(current, ff) => current | ff.Trim().ToLower() switch - { - "roslyn" => FormFactors.Roslyn, - "cli" => FormFactors.CLI, - "commandline" => FormFactors.CLI, - _ => FormFactors.Any - } - ); + => JsonSerializer.Serialize + ( + writer, + Enum.GetValues(typeof(FormFactors)) + .OfType() + .Where(x => (x & value) != 0) + .Select(static x => Enum.GetName(typeof(FormFactors), x)) + ); } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs index 2a7e7c2d1b..7d2b0c80ee 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; namespace Silk.NET.SilkTouch @@ -14,7 +16,7 @@ public class Diagnostics messageFormat: "Multiple configuration files detected. Using \"{0}\", to use \"{1}\" instead configure " + $"the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig option.", category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Warning, + defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, helpLinkUri: null, @@ -29,7 +31,7 @@ public class Diagnostics $"Configuration file, use the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig " + "option.", category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Info, + defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: false, description: null, helpLinkUri: null, @@ -42,7 +44,7 @@ public class Diagnostics title: "Couldn't Determine Assembly Name", messageFormat: "Couldn't determine \"AssemblyName\", SilkTouch will not run.", category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Info, + defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, description: null, helpLinkUri: null, @@ -53,10 +55,94 @@ public class Diagnostics ( id: "ST0008", title: "No Library Name", - messageFormat: "Specify at least one library name " + + messageFormat: "Specify at least one library name " + "(\"projects\" > \"{0}\" > \"scraper\" > \"libraryNames\")", category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Error, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor NoWindowsSdk { get; } = new + ( + id: "ST0009", + title: "Windows-Specific SDK Not Found", + messageFormat: !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "Include \"{0}\" could not be resolved, generation may fail. Consider " + + "adding a skipIf condition to prevent generation on non-Windows platforms " + + "(\"projects\" > \"{1}\" > \"skipIf\": [\"linux\", \"macos\"])" + : "Include \"{0}\" could not be resolved, generation may fail.", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor NoHeaderText { get; } = new + ( + id: "ST0010", + title: "No Input Header Text", + messageFormat: "Input C/C++ header source code is required to use SilkTouch Scraper. (\"projects\" > " + + "\"{0}\" > \"scraper\" > \"headerText\": [\"#include \\\"example.h\\\"\"]", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor NoProjectConfigInFile { get; } = new + ( + id: "ST0011", + title: "No Configuration For This Project", + messageFormat: "\"{0}\" does not contain any configuration for project \"{1}\" (\"projects\" > " + + "\"{1}\": {...})", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor ClangSharpNonZeroExitCode { get; } = new + ( + id: "ST0012", + title: "ClangSharp Exited With Non-Zero Error Code", + messageFormat: "ClangSharp exited with a non-zero exit code: {0}", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor ClangSharpError { get; } = new + ( + id: "ST0013", + title: "ClangSharp Subagent Error Diagnostic", + messageFormat: "{0}", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor GeneralError { get; } = new + ( + id: "ST0014", + title: "General SilkTouch Error", + messageFormat: "SilkTouch failed to execute due to a general error: \"{0}\"", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: null, helpLinkUri: null, diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs new file mode 100644 index 0000000000..e246dc7c35 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Silk.NET.SilkTouch.Configuration; + +namespace Silk.NET.SilkTouch.Generation +{ + public abstract class GeneratorBase + { + protected GeneratorBase(FormFactors formFactor) + { + + } + + // TODO remove duplicate code between SilkTouchSourceGenerator and GeneratorHandoff in this class + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs index 3a23d30565..c49a01e8d3 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs @@ -8,41 +8,23 @@ namespace Silk.NET.SilkTouch.Generation { - public sealed class SilkTouchContext + public sealed record SilkTouchContext + ( + string AssemblyName, + IEnumerable SyntaxTrees, + ProjectConfiguration Configuration, + GlobalConfiguration? GlobalConfiguration, + string BaseDirectory + ) { - /// - /// Creates a with the given parameters. - /// - /// The assembly name for this compilation. - /// The syntax trees in the compilation. - /// - public SilkTouchContext(string assemblyName, IEnumerable syntaxTrees, ProjectConfiguration config) - => (AssemblyName, SyntaxTrees, Configuration) = (assemblyName, syntaxTrees, config); - - // Public Properties - /// - /// The assembly name for the project represented by this context. - /// - public string AssemblyName { get; } - - /// - /// The syntax trees contained within the current project. - /// - public IEnumerable SyntaxTrees { get; } - - /// - /// The SilkTouch Configuration for this project. - /// - public ProjectConfiguration Configuration { get; } - // Internal Properties internal List<(string FileNameHint, string Content)> Outputs { get; } = new(); internal List Diagnostics { get; } = new(); - + // Public Methods public void EmitOutput(string fileNameHint, string content) => Outputs.Add((fileNameHint, content)); - + public void EmitDiagnostic(Diagnostic diagnostic) => Diagnostics.Add(diagnostic); /// diff --git a/src/generators/Silk.NET.SilkTouch.Common/Shims.cs b/src/generators/Silk.NET.SilkTouch.Common/Shims.cs index b74d53a4a6..3e72d610a9 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Shims.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Shims.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NETSTANDARD2_0 using System.ComponentModel; // ReSharper disable once CheckNamespace @@ -15,3 +16,4 @@ internal static class IsExternalInit { } } +#endif diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs index 512be05f40..ae151d239c 100644 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs @@ -25,49 +25,16 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext context) { - // Get the config file name. Uses silktouch.json unless overridden in .editorconfig. - var configFileName = "silktouch.json"; - if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue - (Constants.ConfigFileEditorconfigOption, out var file)) + var loadDiag = Config.Load(context.AnalyzerConfigOptions, context.AdditionalFiles, out var config, out var usedText); + if (loadDiag is not null) { - configFileName = file; + context.ReportDiagnostic(loadDiag); + return; } - // Try and find an AdditionalFiles entry for the SilkTouch config. - string? configFilePath = null; - foreach (var additionalFile in context.AdditionalFiles) - { - if (additionalFile.Path == configFileName || Path.GetFileName(additionalFile.Path) == configFileName) - { - if (configFilePath is not null) - { - context.ReportDiagnostic - ( - Diagnostic.Create - ( - Diagnostics.MultipleConfigFiles, - Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default), - configFilePath, - additionalFile.Path - ) - ); - - continue; - } - - configFilePath = additionalFile.Path; - } - } - // Prevent debug logs from being output by forcing the LoggerProvider to null - we don't want this in Roslyn Log.LoggerProvider = null; - if (configFilePath is null) - { - context.ReportDiagnostic(Diagnostic.Create(Diagnostics.NoConfigFile, Location.None)); - return; - } - if (context.Compilation.AssemblyName is null) { context.ReportDiagnostic(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); @@ -75,23 +42,74 @@ public void Execute(GeneratorExecutionContext context) } // prepare our context data + ProjectConfiguration? projectConfig = null; var syntaxTrees = context.Compilation.SyntaxTrees.OfType().ToArray(); - var projectConfig = Config.Load(configFilePath)[context.Compilation.AssemblyName]; + var (global, projects) = config!; + if ((!projects?.TryGetValue(context.Compilation.AssemblyName, out projectConfig) ?? false) || + projectConfig is null) + { + context.ReportDiagnostic + ( + Diagnostic.Create + ( + Diagnostics.NoProjectConfigInFile, + Location.None, + usedText!.Path, + context.Compilation.AssemblyName + ) + ); + + return; + } + + var baseDir = Path.GetDirectoryName(usedText!.Path); + if (baseDir is null) + { + context.ReportDiagnostic + ( + Diagnostic.Create + ( + Diagnostics.GeneralError, + Location.None, + "Couldn't determine directory name for configuration file." + ) + ); + + return; + } // run the emitter if the config indicates we should. - if (((projectConfig.Emitter.FormFactors ?? EmitterGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) + if (projectConfig?.Emitter is null || + ((projectConfig.Emitter.FormFactors ?? EmitterGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) { - var ctx = new SilkTouchContext(syntaxTrees, projectConfig); + var ctx = new SilkTouchContext + ( + context.Compilation.AssemblyName, + syntaxTrees, + projectConfig!, + global, + baseDir + ); + EmitterGenerator.Run(ctx); var (outputs, diagnostics) = ctx.GetResult(); Copy(context, outputs, diagnostics); } // run the overloader if the config indicates we should. - if (((projectConfig.Overloader.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & FormFactors.Roslyn) + if (projectConfig?.Overloader is null || + ((projectConfig.Overloader.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) { - var ctx = new SilkTouchContext(syntaxTrees, projectConfig); + var ctx = new SilkTouchContext + ( + context.Compilation.AssemblyName, + syntaxTrees, + projectConfig!, + global, + baseDir + ); + OverloaderGenerator.Run(ctx); var (outputs, diagnostics) = ctx.GetResult(); Copy(context, outputs, diagnostics); diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ACKNOWLEDGEMENTS.md b/src/generators/Silk.NET.SilkTouch.Scraper/ACKNOWLEDGEMENTS.md new file mode 100644 index 0000000000..eb856138ca --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ACKNOWLEDGEMENTS.md @@ -0,0 +1,21 @@ +# License Acknowledgements +## ClangSharp + +ClangSharp Subagent code is based on the code from `ClangSharpPInvokeGenerator`, sourced under the following license: +``` +University of Illinois/NCSA Open Source License +Copyright (c) Microsoft and Contributors +All rights reserved. + +Developed by: Microsoft and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. +Neither the names of Microsoft, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.``` +``` + +Original Code Used: +- https://github.com/microsoft/ClangSharp/blob/main/sources/ClangSharpPInvokeGenerator/Program.cs diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs similarity index 66% rename from src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs rename to src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs index b0c0a9c1da..9fb87a7a79 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ClangSharpSubagent.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Silk.NET.SilkTouch.Scraper.Subagent +namespace Silk.NET.SilkTouch.Scraper.Mods { - public class ClangSharpSubagent + public class XmlMods { } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index a5691b5246..20278c859f 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -1,18 +1,23 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using ClangSharp; using Microsoft.CodeAnalysis; using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; using Silk.NET.SilkTouch.Scraper.Subagent; using Ultz.Extensions.Logging; +using Diagnostic = Microsoft.CodeAnalysis.Diagnostic; namespace Silk.NET.SilkTouch.Scraper { public static class ScraperGenerator { public const FormFactors DefaultFormFactors = FormFactors.CLI; - public static void Run(SilkTouchContext ctx) where T:ISubagentSpawner, new() + public static async Task RunAsync(SilkTouchContext ctx) where T:ISubagent, new() { var subagentSpawner = new T(); var error = false; @@ -23,36 +28,136 @@ public static class ScraperGenerator error = true; } + if (ctx.Configuration.Scraper?.HeaderText is null) + { + ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoHeaderText, Location.None, ctx.AssemblyName)); + error = true; + } + if (error) { return; } var workFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + var @in = Path.Combine(workFolder, "in.h"); + var @out = Path.Combine(workFolder, "out.xml"); + string? licenseHeaderFile = null; Directory.CreateDirectory(workFolder); + File.WriteAllLines(@in, ctx.Configuration.Scraper?.HeaderText ?? Array.Empty()); + + if (ctx.GlobalConfiguration?.FileHeaderLines is not null) + { + licenseHeaderFile = Path.Combine(workFolder, "licenseHeader.txt"); + await File.WriteAllTextAsync(licenseHeaderFile, licenseHeaderFile); + } + + VisualStudioResolver.TryGetVisualStudioInfo(out var vs); var includes = ctx.Configuration.Scraper?.IncludeDirectories?.ToList() ?? new(); - for (int i = 0; i < includes.Count; i++) + for (var i = 0; i < includes.Count; i++) { var include = includes[i]; - if (include == "win10-sdk") - { - - } - else if (include.StartsWith("win10-sdk-")) + if (include == "win-sdk") { - + includes.RemoveAt(i); + if (vs is null) + { + ctx.EmitDiagnostic + ( + Diagnostic.Create(Diagnostics.NoWindowsSdk, Location.None, "win-sdk", ctx.AssemblyName) + ); + + continue; + } + + includes.InsertRange(i, vs.UcrtIncludes); } } + Log.Debug($"Using work folder \"{workFolder}\""); Log.Information($"Starting ClangSharp subagent for \"{ctx.AssemblyName}\"..."); var options = new SubagentOptions ( - libraryNames![0], // we know libraryNames isn't null here, but the compiler disagrees. + @in, + "__SILKTOUCH", // TODO replace this in transformation, our library path logic will be more lenient ctx.Configuration.Scraper?.Namespace ?? ctx.AssemblyName, + @out, workFolder, - workFolder, + includes.ToArray(), + PInvokeGeneratorOutputMode.Xml, + SubagentUtils.GetOptions + ( + ctx.Configuration.Scraper?.Exclude?.Hints ?? ExclusionHint.None, + ctx.Configuration.Scraper?.UnixMode ?? !OperatingSystem.IsWindows() + ), + ctx.Configuration.Scraper?.Language ?? "c++", + ctx.Configuration.Scraper?.Standard ?? "c++17", + ctx.Configuration.Scraper?.AdditionalClangArguments, + ctx.Configuration.Scraper?.DefineMacros, + ctx.Configuration.Scraper?.Exclude?.Identifiers, + licenseHeaderFile, + ctx.Configuration.Scraper?.ClassName ?? "Interop", + ctx.Configuration.Scraper?.MethodPrefixToStrip, + await OpenRemappingFilesAsync(ctx.Configuration.Scraper?.RemappingFiles, ctx.BaseDirectory), + ctx.Configuration.Scraper?.Traverse, + WithAttributes: null, // will be done by mods TODO do attributes in the mods + ctx.Configuration.Scraper?.CallingConventions, + WithLibraryPaths: null, // again, library path stuff is done in transformation + WithSetLastErrors: null, // the current design of SilkTouch doesn't support SetLastError + null, // currently no need to support enum type overrides + null // the scraper will add predefined using directives during transformation ); + + var clangSharpErrors = new List(); + var exitCode = await subagentSpawner.RunClangSharpAsync(options, clangSharpErrors); + + if (exitCode != 0) + { + ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.ClangSharpNonZeroExitCode, Location.None, exitCode)); + foreach (var theError in clangSharpErrors) + { + ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.ClangSharpError, Location.None, theError)); + } + + return; + } + + // TODO mods + // TODO transformation + ctx.EmitOutput + } + + private static async ValueTask?> OpenRemappingFilesAsync + ( + string[]? files, + string baseDir + ) + { + if (files is null) + { + return null; + } + + var ret = new Dictionary(); + foreach (var file in files) + { + var text = await File.ReadAllTextAsync(Path.GetFullPath(file, baseDir)); + var dict = JsonSerializer.Deserialize>(text); + if (dict is null) + { + continue; + } + + foreach (var (key, value) in dict) + { + if (!ret.TryAdd(key, value)) + { + // TODO idk log that there's a duplicate remapping maybe? + } + } + } + + return ret; } } } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj index 7adb5e7bb0..3a70209f18 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -7,15 +7,14 @@ + + + - - - - diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs similarity index 69% rename from src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs rename to src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs index f329f00d5b..934ba3d5d3 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagentSpawner.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Silk.NET.SilkTouch.Scraper.Subagent @@ -8,13 +10,14 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent /// /// A class capable of creating ClangSharp subprocesses/subagents. /// - public interface ISubagentSpawner + public interface ISubagent { /// /// Runs a ClangSharp subprocess with the given options. /// /// The options to run the subagent with. + /// A list in which, if provided, errors posted by the subagent will be stored. /// An asynchronous task which, when awaited, yields the exit code of the subprocess. - Task RunClangSharpAsync(SubagentOptions opts); + Task RunClangSharpAsync(SubagentOptions opts, List? errors = null); } } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs index d753b90640..463f68bf68 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs @@ -34,7 +34,7 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent /// /// The for ClangSharp to use in XML generation. /// - /// + /// /// The file containing the for ClangSharp to use in XML generation. /// /// @@ -69,6 +69,7 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent /// public record SubagentOptions ( + string HeaderFile, string LibraryPath, string NamespaceName, string OutputLocation, @@ -81,7 +82,7 @@ public record SubagentOptions string[]? AdditionalArgs = null, string[]? DefineMacros = null, string[]? ExcludedNames = null, - string? HeaderFile = null, + string? LicenseHeaderFile = null, string? MethodClassName = null, string? MethodPrefixToStrip = null, IReadOnlyDictionary? RemappedNames = null, @@ -109,7 +110,7 @@ public record SubagentOptions opts.OutputMode, opts.Options, opts.ExcludedNames, - opts.HeaderFile, + opts.LicenseHeaderFile, opts.MethodClassName, opts.MethodPrefixToStrip, opts.RemappedNames, diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs new file mode 100644 index 0000000000..53ac3220c6 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ClangSharp; +using Silk.NET.SilkTouch.Configuration; + +namespace Silk.NET.SilkTouch.Scraper.Subagent +{ + internal static class SubagentUtils + { + public static PInvokeGeneratorConfigurationOptions GetOptions(ExclusionHint hints, bool unixMode) + { + var opts = PInvokeGeneratorConfigurationOptions.None; + + // If these hints are defined, define the corresponding ClangSharp exclusion option. + if ((hints & ExclusionHint.ComProxies) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.ExcludeComProxies; + } + + if ((hints & ExclusionHint.DefaultRemappings) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.NoDefaultRemappings; + } + + if ((hints & ExclusionHint.EmptyRecords) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.ExcludeEmptyRecords; + } + + if ((hints & ExclusionHint.EnumOperators) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.ExcludeEnumOperators; + } + + if ((hints & ExclusionHint.AnonymousFieldHelpers) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.ExcludeAnonymousFieldHelpers; + } + + if ((hints & ExclusionHint.FunctionsWithBodies) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.ExcludeFunctionsWithBody; + } + + if ((hints & ExclusionHint.UsingStaticForEnums) != 0) + { + opts |= PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums; + } + + // If these hints are not defined, define the corresponding ClangSharp inclusion option. + if ((hints & ExclusionHint.MacroBindings) == 0) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateMacroBindings; + } + + if (((hints & ExclusionHint.AggressiveInlining) == 0)) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateAggressiveInlining; + } + + if ((hints & ExclusionHint.CppAttributes) == 0) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateCppAttributes; + } + + if ((hints & ExclusionHint.NativeInheritanceAttribute) == 0) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateNativeInheritanceAttribute; + } + + if ((hints & ExclusionHint.TemplateBindings) == 0) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateTemplateBindings; + } + + if ((hints & ExclusionHint.VTableIndexAttribute) == 0) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateVtblIndexAttribute; + } + + if (unixMode) + { + opts |= PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; + } + + return opts; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs index 682d448b08..a7eb017af7 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Management; @@ -10,6 +12,7 @@ using System.Runtime.Versioning; using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.Build.Locator; using Microsoft.Extensions.Logging; using Ultz.Extensions.Logging; @@ -23,21 +26,14 @@ public record VisualStudioInfo string[] UcrtIncludes, Version UcrtVersion, string MsvcToolsFolder, - string[] MsvcToolsIncludes + string[] MsvcToolsIncludes, + Version Version, + Dictionary Variables, + string MSBuildPath ); - internal record VisualStudioCandidate - ( - string Name, - string InstallationPath - ); - - [SupportedOSPlatform("windows")] public static class VisualStudioResolver { - internal static readonly string VarPrintScriptNamespace = - $"{typeof(VisualStudioResolver).Namespace}.VisualStudioVarPrint.bat"; - internal const string MsvcInstallDirVar = "VCToolsInstallDir"; internal static readonly string[] MsvcIncludeSubDirs = { "include" }; @@ -50,116 +46,86 @@ public static class VisualStudioResolver "Include/{0}/shared" }; - public static bool TryGetVisualStudioInfo(bool allowPrerelease, out VisualStudioInfo? info) - { - ManagementObjectCollection mcCollection; + private static VisualStudioInfo? _cachedInfo; + private static VisualStudioInstance? _cachedInstance; - try + public static bool TryGetMSBuildInfo + ( + [NotNullWhen(true)] out VisualStudioInstance? instance + ) + { + if (TryGetVisualStudioInfo(out _)) { - using ManagementClass mc = new("MSFT_VSInstance"); - mcCollection = mc.GetInstances(); + instance = _cachedInstance; + return instance is not null; } - catch (Exception e) + + var use = MSBuildLocator.QueryVisualStudioInstances() + .OrderBy(x => x.DiscoveryType) + .ThenBy(x => x.Version) + .FirstOrDefault(); + + if (use is null) { - Log.Debug($"Failed to get \"MSFT_VSInstance\" info with exception {e}"); - info = null; + instance = null; return false; } - //We want to buildtools if they have it installec, we'll use VS installs if needed - var visualStudios = new VisualStudioCandidate[mcCollection.Count]; - var i = 0; - foreach (var result in mcCollection) - { - var name = Convert.ToString(result["Name"]); - var path = Convert.ToString(result["InstallLocation"]); - if (name is null || path is null) - { - continue; - } - - visualStudios[i++] = new(name, path); - } + Log.Information($"Using MSBuild at \"{use.MSBuildPath}\" (from \"{use.Name}\" v{use.Version.ToString(3)}"); + instance = use; + return true; + } - // extract the varprint script - var varPrint = Path.Combine(Path.GetTempPath(), $"{Path.GetRandomFileName()}.bat"); - using var varPrintRes = typeof(VisualStudioResolver).Assembly - .GetManifestResourceStream(VarPrintScriptNamespace); - if (varPrintRes is null) + public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioInfo? info) + { + // if we're not on windows, this is never gonna work + if (!OperatingSystem.IsWindowsVersionAtLeast(10)) { - Log.Debug($"Couldn't extract \"{VarPrintScriptNamespace}\" from assembly."); - Log.Warning("Internal error."); info = null; return false; } - using var varPrintOut = File.OpenWrite(varPrint); - varPrintRes.CopyTo(varPrintOut); + // if we've cached some info + if (_cachedInfo is not null) + { + info = _cachedInfo; + return true; + } + + // use MSBuildLocator to find Visual Studio instances + var visualStudios = MSBuildLocator + .QueryVisualStudioInstances() + .Where(x => (x.DiscoveryType & (DiscoveryType.DeveloperConsole | DiscoveryType.VisualStudioSetup)) != 0) + .OrderBy(x => x.DiscoveryType) + .ThenBy(x => x.Version); // cycle through candidate installations, try and find everything we're looking for. - string? msvcInstallDir = null; - string? winSdkUcrtSdkDir = null; - string? winSdkUcrtVersion = null; - var listening = true; - foreach (var (name, installationPath) in visualStudios) + foreach (var visualStudio in visualStudios) { - Log.Debug($"Testing \"{name}\" at \"{installationPath}\"..."); - using var varPrintProc = new Process + Log.Debug + ( + $"Testing \"{visualStudio.Name}\" v{visualStudio.Version.ToString(3)} at " + + $"\"{visualStudio.VisualStudioRootPath}\"..." + ); + + if (!VisualStudioVarPrint.TryRun(visualStudio.VisualStudioRootPath, out var vars)) { - StartInfo = new() - { - FileName = "cmd", - Arguments = $"/c \"{varPrint}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - CreateNoWindow = true - } - }; - - varPrintProc.Start(); - while (!varPrintProc.StandardOutput.EndOfStream) + Log.Warning("Internal error"); + info = null; + return false; + } + + if (!vars.TryGetValue(MsvcInstallDirVar, out var msvcInstallDir) || + !vars.TryGetValue(WinSdkUcrtSdkDirVar, out var winSdkUcrtSdkDir) || + !vars.TryGetValue(WinSdkUcrtVersionVar, out var winSdkUcrtVersion)) { - var line = varPrintProc.StandardOutput.ReadLine(); - if (line is null) - { - break; - } - - Log.Debug(line); - - if (line == "***VARSTART***") - { - listening = true; - continue; - } - - if (line == "***VAREND***") - { - listening = false; - break; - } - - if (listening) - { - var split = line.Split('='); - var _ = split[0] switch - { - MsvcInstallDirVar => msvcInstallDir = split[1], - WinSdkUcrtSdkDirVar => winSdkUcrtSdkDir = split[1], - WinSdkUcrtVersionVar => winSdkUcrtVersion = split[1], - _ => string.Empty - }; - } + Log.Trace($"\"{visualStudio.Name}\" is not a viable candidate."); + continue; } Log.Debug($"msvcInstallDir = \"{msvcInstallDir}\""); Log.Debug($"winSdkUcrtSdkDir = \"{winSdkUcrtSdkDir}\""); Log.Debug($"winSdkUcrtVersion = \"{winSdkUcrtVersion}\""); - if (msvcInstallDir is null || winSdkUcrtSdkDir is null || winSdkUcrtVersion is null) - { - Log.Trace($"\"{name}\" is not a viable candidate."); - continue; - } var ucrtDirs = WinSdkUcrtIncludeSubDirs .Select(x => Path.Combine(winSdkUcrtSdkDir, string.Format(x, winSdkUcrtVersion))) @@ -171,20 +137,25 @@ public static bool TryGetVisualStudioInfo(bool allowPrerelease, out VisualStudio .Where(Directory.Exists) .ToArray(); - info = new + _cachedInstance = visualStudio; + _cachedInfo = info = new ( - name, - installationPath, + visualStudio.Name, + visualStudio.VisualStudioRootPath, winSdkUcrtSdkDir, ucrtDirs, Version.Parse(winSdkUcrtVersion), msvcInstallDir, - msvcDirs + msvcDirs, + visualStudio.Version, + vars, + visualStudio.MSBuildPath ); Log.Information ( - $"Using \"{name}\" (in \"{installationPath}\") with Windows SDK v{info.UcrtVersion.ToString(4)} " + + $"Using \"{visualStudio.Name}\" v{visualStudio.Version.ToString(3)} " + + $"(in \"{visualStudio.VisualStudioRootPath}\") with Windows SDK v{info.UcrtVersion.ToString(4)} " + $"({ucrtDirs.Length} include(s)) and MSVC ({msvcDirs.Length} include(s))" ); @@ -195,7 +166,8 @@ public static bool TryGetVisualStudioInfo(bool allowPrerelease, out VisualStudio Log.Warning ( "Couldn't find a viable Visual Studio installation - ensure you have the Windows 10 SDK and C++ " + - "tools installed." + "tools installed. SilkTouch Scraper may not function correctly without Visual Studio or Visual " + + "Studio Build Tools with these workloads." ); info = null; diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs new file mode 100644 index 0000000000..0c9e9644ad --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Ultz.Extensions.Logging; + +namespace Silk.NET.SilkTouch.Scraper.Subagent +{ + internal static class VisualStudioVarPrint + { + private static readonly string _varPrintScriptNamespace = $"{typeof(VisualStudioVarPrint).FullName}.bat"; + + private static string? _cachedPath; + public static bool TryExtractIfNeeded([NotNullWhen(true)] out string? scriptPath) + { + if (_cachedPath is not null) + { + scriptPath = _cachedPath; + return true; + } + + // extract the varprint script + var varPrint = Path.Combine(Path.GetTempPath(), $"{Path.GetRandomFileName()}.bat"); + using var varPrintRes = typeof(VisualStudioResolver).Assembly + .GetManifestResourceStream(_varPrintScriptNamespace); + if (varPrintRes is null) + { + Log.Debug($"Couldn't extract \"{_varPrintScriptNamespace}\" from assembly."); + scriptPath = null; + return false; + } + + using var varPrintOut = File.OpenWrite(varPrint); + varPrintRes.CopyTo(varPrintOut); + + scriptPath = _cachedPath = varPrint; + return true; + } + + public static bool TryRun(string vsPath, [NotNullWhen(true)] out Dictionary? vars) + { + if (!TryExtractIfNeeded(out var varPrint)) + { + vars = null; + return false; + } + + using var varPrintProc = new Process + { + StartInfo = new() + { + FileName = "cmd", + Arguments = $"/c \"{varPrint}\"", + WorkingDirectory = vsPath, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + vars = null; + varPrintProc.Start(); + while (!varPrintProc.StandardOutput.EndOfStream) + { + var line = varPrintProc.StandardOutput.ReadLine(); + if (line is null) + { + continue; + } + + Log.Debug(line); + + switch (line) + { + case "***VARSTART***": + { + vars = new(); + continue; + } + case "***VAREND***": + { + return true; + } + default: + { + if (vars is not null) + { + var split = line.Split('='); + vars.TryAdd(split[0], split[1]); + } + + break; + } + } + } + + varPrintProc.WaitForExit(); + vars = null; + return false; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Transformation/XmlToCSharpTransformer.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Transformation/XmlToCSharpTransformer.cs new file mode 100644 index 0000000000..5a7096bd68 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Transformation/XmlToCSharpTransformer.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.Scraper.Transformation +{ + public class XmlToCSharpTransformer + { + + } +} diff --git a/src/generators/SilkTouch/ClangSharpHandoff.cs b/src/generators/SilkTouch/ClangSharpHandoff.cs index ab477d5772..7c6204cb63 100644 --- a/src/generators/SilkTouch/ClangSharpHandoff.cs +++ b/src/generators/SilkTouch/ClangSharpHandoff.cs @@ -26,13 +26,18 @@ public static int RunClangSharp(string[] args) "To use ClangSharp, visit https://github.com/microsoft/clangsharp." ); - return 1; + return (int) ExitCodes.SubagentBadArgs; } // the code below is based off ClangSharp // Copyright (c) Microsoft and Contributors. All rights reserved. // Licensed under the University of Illinois/NCSA Open Source License. // https://github.com/microsoft/ClangSharp/blob/main/LICENSE.txt + + // I wish we could use the actual ClangSharpPInvokeGenerator executable, but it's a dotnet tool which means + // we can't reference it. Luckily, this executable is just a shim for calling into the + // ClangSharp.PInvokeGenerator library so we can just replicate what the executable does in here. + var clangCommandLineArgs = new List { $"--language={options.Language}", // Treat subsequent input files as having type @@ -61,91 +66,89 @@ public static int RunClangSharp(string[] args) var exitCode = 0; var finishedCommandLineArgs = clangCommandLineArgs.ToArray(); - using (var pinvokeGenerator = new PInvokeGenerator(config)) + using var pinvokeGenerator = new PInvokeGenerator(config); + var translationUnitError = CXTranslationUnit.TryParse + ( + pinvokeGenerator.IndexHandle, options.LicenseHeaderFile, finishedCommandLineArgs, + Array.Empty(), translationFlags, out var handle + ); + var skipProcessing = false; + + if (translationUnitError != CXErrorCode.CXError_Success) { - var translationUnitError = CXTranslationUnit.TryParse - ( - pinvokeGenerator.IndexHandle, options.HeaderFile, finishedCommandLineArgs, - Array.Empty(), translationFlags, out var handle - ); - var skipProcessing = false; - - if (translationUnitError != CXErrorCode.CXError_Success) - { - Console.WriteLine($"E:Parsing failed due to '{translationUnitError}'."); - skipProcessing = true; - } - else if (handle.NumDiagnostics != 0) + Console.WriteLine($"E:Parsing failed due to '{translationUnitError}'."); + skipProcessing = true; + } + else if (handle.NumDiagnostics != 0) + { + for (uint i = 0; i < handle.NumDiagnostics; ++i) { - for (uint i = 0; i < handle.NumDiagnostics; ++i) - { - using var diagnostic = handle.GetDiagnostic(i); - - Console.Write - ( - diagnostic.Severity switch - { - CXDiagnosticSeverity.CXDiagnostic_Error => "E:", - CXDiagnosticSeverity.CXDiagnostic_Fatal => "E:", - CXDiagnosticSeverity.CXDiagnostic_Warning => "W:", - CXDiagnosticSeverity.CXDiagnostic_Ignored => "T:", - CXDiagnosticSeverity.CXDiagnostic_Note => "T:", - _ => "T:" - } - ); - - Console.WriteLine(diagnostic.Format(CXDiagnostic.DefaultDisplayOptions).ToString()); - - skipProcessing |= diagnostic.Severity == CXDiagnosticSeverity.CXDiagnostic_Error; - skipProcessing |= diagnostic.Severity == CXDiagnosticSeverity.CXDiagnostic_Fatal; - } - } + using var diagnostic = handle.GetDiagnostic(i); - if (skipProcessing) - { - Console.WriteLine("E:One or more errors during parsing."); - return 2; - } + Console.Write + ( + diagnostic.Severity switch + { + CXDiagnosticSeverity.CXDiagnostic_Error => "E:", + CXDiagnosticSeverity.CXDiagnostic_Fatal => "E:", + CXDiagnosticSeverity.CXDiagnostic_Warning => "W:", + CXDiagnosticSeverity.CXDiagnostic_Ignored => "T:", + CXDiagnosticSeverity.CXDiagnostic_Note => "T:", + _ => "T:" + } + ); - try - { - using var translationUnit = TranslationUnit.GetOrCreate(handle); - Console.WriteLine("I:Processing..."); + Console.WriteLine(diagnostic.Format(CXDiagnostic.DefaultDisplayOptions).ToString()); - pinvokeGenerator.GenerateBindings - (translationUnit, options.HeaderFile, finishedCommandLineArgs, translationFlags); - } - catch (Exception e) - { - Console.WriteLine("E:" + e); + skipProcessing |= diagnostic.Severity == CXDiagnosticSeverity.CXDiagnostic_Error; + skipProcessing |= diagnostic.Severity == CXDiagnosticSeverity.CXDiagnostic_Fatal; } + } - if (pinvokeGenerator.Diagnostics.Count != 0) + if (skipProcessing) + { + Console.WriteLine("E:One or more errors during parsing."); + return (int) ExitCodes.SubagentFailedToParse; + } + + try + { + using var translationUnit = TranslationUnit.GetOrCreate(handle); + Console.WriteLine("I:Processing..."); + + pinvokeGenerator.GenerateBindings + (translationUnit, options.LicenseHeaderFile, finishedCommandLineArgs, translationFlags); + } + catch (Exception e) + { + Console.WriteLine("E:" + e); + } + + if (pinvokeGenerator.Diagnostics.Count != 0) + { + foreach (var diagnostic in pinvokeGenerator.Diagnostics) { - foreach (var diagnostic in pinvokeGenerator.Diagnostics) - { - Console.Write - ( - diagnostic.Level switch - { - DiagnosticLevel.Info => "T:", - DiagnosticLevel.Warning => "W:", - DiagnosticLevel.Error => "E:", - _ => "T:" - } - ); - Console.WriteLine(diagnostic); - if (diagnostic.Level == DiagnosticLevel.Error) + Console.Write + ( + diagnostic.Level switch { - exitCode--; + DiagnosticLevel.Info => "T:", + DiagnosticLevel.Warning => "W:", + DiagnosticLevel.Error => "E:", + _ => "T:" } + ); + Console.WriteLine(diagnostic); + if (diagnostic.Level == DiagnosticLevel.Error) + { + exitCode--; } } + } - if (exitCode < 0) - { - Console.WriteLine("E:One or more errors during bindings generation."); - } + if (exitCode < 0) + { + Console.WriteLine("E:One or more errors during bindings generation."); } return exitCode; diff --git a/src/generators/SilkTouch/CommandLineSubagentSpawner.cs b/src/generators/SilkTouch/ClangSharpSubagent.cs similarity index 51% rename from src/generators/SilkTouch/CommandLineSubagentSpawner.cs rename to src/generators/SilkTouch/ClangSharpSubagent.cs index 876e4ccea2..823ea1a181 100644 --- a/src/generators/SilkTouch/CommandLineSubagentSpawner.cs +++ b/src/generators/SilkTouch/ClangSharpSubagent.cs @@ -2,21 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Silk.NET.SilkTouch.Scraper.Subagent; +using Ultz.Extensions.Logging; namespace SilkTouch { /// /// Spawns ClangSharp in a subprocess. This allows for parallelism (Clang doesn't like threads) /// - internal class CommandLineSubagentSpawner : ISubagentSpawner + internal class ClangSharpSubagent : ISubagent { /// - public async Task RunClangSharpAsync(SubagentOptions opts) + public async Task RunClangSharpAsync(SubagentOptions opts, List? errors = null) { // get the command line arguments this process was started with // using ArraySegment (lesser span) here instead of Span because it's enumerable. @@ -37,16 +39,59 @@ public async Task RunClangSharpAsync(SubagentOptions opts) $"\"{string.Join("\" \"", args[1..].Select(x => x.Replace("\"", "\\\"")))}\" \"{optsStr}\"" ) { - WorkingDirectory = Environment.CurrentDirectory + WorkingDirectory = Environment.CurrentDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true } }; + // run the subprocess. if (!proc.Start()) { - return 3; + return (int) ExitCodes.SubagentFailedToStart; } - // run the subprocess. + // log its logs to the log + while (!proc.StandardOutput.EndOfStream) + { + var line = await proc.StandardOutput.ReadLineAsync(); + if (line is null) + { + continue; + } + + switch (line[..2]) + { + case "I:": + { + Log.Information($"{opts.NamespaceName}: {line[2..]}"); + break; + } + case "W:": + { + Log.Warning($"{opts.NamespaceName}: {line[2..]}"); + break; + } + case "T:": + { + Log.Trace($"{opts.NamespaceName}: {line[2..]}"); + break; + } + case "E:": + { + Log.Error($"{opts.NamespaceName}: {line[2..]}"); + errors?.Add(line[2..]); + break; + } + default: + { + Log.Debug($"{opts.NamespaceName}: {line}"); + break; + } + } + } + await proc.WaitForExitAsync(); return proc.ExitCode; } diff --git a/src/generators/SilkTouch/ExitCodes.cs b/src/generators/SilkTouch/ExitCodes.cs new file mode 100644 index 0000000000..c4bfca7601 --- /dev/null +++ b/src/generators/SilkTouch/ExitCodes.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace SilkTouch +{ + internal enum ExitCodes + { + Ok, + SubagentBadArgs, + SubagentFailedToParse, + SubagentFailedToStart, + FailedToGetMSBuild + } +} diff --git a/src/generators/SilkTouch/GeneratorHandoff.cs b/src/generators/SilkTouch/GeneratorHandoff.cs new file mode 100644 index 0000000000..55d352a002 --- /dev/null +++ b/src/generators/SilkTouch/GeneratorHandoff.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Logging; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.Logging; +using Silk.NET.SilkTouch; +using Silk.NET.SilkTouch.Configuration; +using Silk.NET.SilkTouch.Emitter; +using Silk.NET.SilkTouch.Generation; +using Silk.NET.SilkTouch.Overloader; +using Ultz.Extensions.Logging; + +namespace SilkTouch +{ + internal static class GeneratorHandoff + { + public static int ExitCode { get; private set; } + public static async ValueTask HandleProjectAsync(Project project, CancellationToken cancellationToken) + { + var loadDiag = Config.Load + ( + project.AnalyzerOptions.AnalyzerConfigOptionsProvider, + project.AnalyzerOptions.AdditionalFiles, + out var config, + out var usedText + ); + + if (loadDiag is not null) + { + WriteDiagnostics(loadDiag); + return; + } + + if (!project.TryGetCompilation(out var compilation)) + { + WriteDiagnostics(); + return; + } + + if (compilation.AssemblyName is null) + { + WriteDiagnostics(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); + return; + } + + // prepare our context data + ProjectConfiguration? projectConfig = null; + var syntaxTrees = compilation.SyntaxTrees.OfType().ToArray(); + var (global, projects) = config!; + if ((!projects?.TryGetValue(compilation.AssemblyName, out projectConfig) ?? false) || + projectConfig is null) + { + WriteDiagnostics + ( + Diagnostic.Create + ( + Diagnostics.NoProjectConfigInFile, + Location.None, + usedText!.Path, + compilation.AssemblyName + ) + ); + } + + var baseDir = Path.GetDirectoryName(usedText!.Path); + if (baseDir is null) + { + WriteDiagnostics + ( + Diagnostic.Create + ( + Diagnostics.GeneralError, + Location.None, + "Couldn't determine directory name for configuration file." + ) + ); + + return; + } + + // run the emitter if the config indicates we should. + if (projectConfig?.Emitter is null || + ((projectConfig.Emitter.FormFactors ?? EmitterGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) + { + var ctx = new SilkTouchContext + ( + compilation.AssemblyName, + syntaxTrees, + projectConfig!, + global, + baseDir + ); + + EmitterGenerator.Run(ctx); + var (outputs, diagnostics) = ctx.GetResult(); + Copy(project, outputs, diagnostics); + } + + // run the overloader if the config indicates we should. + if (projectConfig?.Overloader is null || + ((projectConfig.Overloader.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & FormFactors.Roslyn) + != 0) + { + var ctx = new SilkTouchContext + ( + compilation.AssemblyName, + syntaxTrees, + projectConfig!, + global, + baseDir + ); + + OverloaderGenerator.Run(ctx); + var (outputs, diagnostics) = ctx.GetResult(); + Copy(project, outputs, diagnostics); + } + } + + private static void Copy(Project project, List<(string FileNameHint, string Content)> outputs, List diagnostics) + { + // TODO IMPLEMENT THIS SHIT + throw new NotImplementedException(); + } + + private static void WriteDiagnostics(params Diagnostic[] diagnostics) + => WriteDiagnostics((IEnumerable) diagnostics); + + private static void WriteDiagnostics(IEnumerable diagnostics) + { + foreach (var diagnostic in diagnostics) + { + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (diagnostic.Severity) + { + case DiagnosticSeverity.Hidden: + { + Log.Trace(diagnostic.ToString()); + break; + } + case DiagnosticSeverity.Info: + { + Log.Information(diagnostic.ToString()); + break; + } + case DiagnosticSeverity.Warning: + { + Log.Warning(diagnostic.ToString()); + break; + } + case DiagnosticSeverity.Error: + { + Log.Error(diagnostic.ToString()); + ExitCode--; + break; + } + } + } + } + } +} diff --git a/src/generators/SilkTouch/LogMode.cs b/src/generators/SilkTouch/LogMode.cs new file mode 100644 index 0000000000..d5b6d9d3f9 --- /dev/null +++ b/src/generators/SilkTouch/LogMode.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace SilkTouch +{ + public enum LogMode + { + Standard, + Silent, + Verbose, + VVerbose + } +} diff --git a/src/generators/SilkTouch/Program.cs b/src/generators/SilkTouch/Program.cs index 519ca5edf1..0d4868ecc0 100644 --- a/src/generators/SilkTouch/Program.cs +++ b/src/generators/SilkTouch/Program.cs @@ -1,33 +1,209 @@ using System; +using System.CommandLine; +using System.CommandLine.Invocation; using System.Diagnostics; +using System.IO; +using System.Linq; using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.Extensions.Logging; using Silk.NET.SilkTouch.Scraper.Subagent; +using Ultz.Extensions.Logging; namespace SilkTouch { internal class Program { public static string[] Args { get; internal set; } - static int Main(string[] args) + + static async Task Main(string[] args) { Args = args; if (args.Length > 0 && args[0].ToLower() == "clangsharp") { return ClangSharpHandoff.RunClangSharp(args); } - + Console.WriteLine ( - "Silk.NET SilkTouch - " - + $"v{typeof(Program).Assembly.GetName().Version?.ToString(3)} - " + + "Silk.NET SilkTouch - " + + $"v{typeof(Program).Assembly.GetName().Version?.ToString(3)} - " + "Copyright (c) .NET Foundation and Contributors" ); + var slnOrProjInCwd = ResolveProjectOrSolutionInCwd(); + var rootCommand = new RootCommand + { + slnOrProjInCwd is null ? new Option + ( + new[] { "--project", "-p" }, + "The input solution or project file to generate for." + ) : new + ( + new[] { "--project", "-p" }, + () => slnOrProjInCwd, // add a default value factory if we've found an individual project or solution + "The input solution or project file to generate for." + ), + new Option + ( + new[] {"--logging", "-l"}, + () => LogMode.Standard, + "The debug logging verbosity." + ) + }; + + rootCommand.Handler = CommandHandler.Create(RunSilkTouchAsync); + return await rootCommand.InvokeAsync(args); + } + + private static async Task RunSilkTouchAsync(FileInfo projectOrSolution, LogMode logMode) + { + if (logMode != LogMode.Silent) + { + Console.WriteLine + ( + "Silk.NET SilkTouch - " + + $"v{typeof(Program).Assembly.GetName().Version?.ToString(3)} - " + + "Copyright (c) .NET Foundation and Contributors" + ); + } + var sw = new Stopwatch(); + + // Configure the logger + var loggerProvider = new UltzLoggerProvider(); + loggerProvider.LogLevels.Clear(); + + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (logMode) + { + case LogMode.Silent: + { + loggerProvider.LogLevels.Add(LogLevel.Error); + loggerProvider.LogLevels.Add(LogLevel.Critical); + break; + } + case LogMode.Standard: + { + loggerProvider.LogLevels.Add(LogLevel.Information); + loggerProvider.LogLevels.Add(LogLevel.Warning); + break; + } + case LogMode.Verbose: + { + loggerProvider.LogLevels.Add(LogLevel.Trace); + break; + } + case LogMode.VVerbose: + { + loggerProvider.LogLevels.Add(LogLevel.Debug); + break; + } + } + + Log.LoggerProvider = loggerProvider; + // Select MSBuild + if (!VisualStudioResolver.TryGetMSBuildInfo(out var instance)) + { + Log.Error("Failed to get MSBuild info."); + return 4; + } + + MSBuildLocator.RegisterInstance(instance); + + // Create Workspace + using var workspace = MSBuildWorkspace.Create(); + using var projectOrSolutionReader = projectOrSolution.OpenText(); + string? line; + var isSolution = false; + while ((line = await projectOrSolutionReader.ReadLineAsync()) is not null) + { + if (line.Trim().StartsWith("VisualStudioVersion")) + { + // good indicator it's a solution file + isSolution = true; + break; + } + + if (line.Contains(".Logger.LogError(e.Diagnostic.Message); + break; + } + case WorkspaceDiagnosticKind.Warning: + { + Log.Logger.LogWarning(e.Diagnostic.Message); + break; + } + } + } + + private static FileInfo? ResolveProjectOrSolutionInCwd() + { + var slnOrProj = Directory.GetFiles(Environment.CurrentDirectory, "*.sln") + .Concat(Directory.GetFiles(Environment.CurrentDirectory, "*.csproj")); + string? candidateFile = null; + foreach (var theFile in slnOrProj) + { + if (candidateFile is null) + { + candidateFile = theFile; + } + else + { + // multiple files detected! we can't safely decide which one to use... + return null; + } + } + + if (candidateFile is null) + { + return null; + } + + return new(candidateFile); } } } diff --git a/src/generators/SilkTouch/SilkTouch.csproj b/src/generators/SilkTouch/SilkTouch.csproj index c61214fd36..4521127663 100644 --- a/src/generators/SilkTouch/SilkTouch.csproj +++ b/src/generators/SilkTouch/SilkTouch.csproj @@ -8,10 +8,14 @@ + + + + From cac80896c1ff9309c579cb6e98ccc4df15d785c8 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Thu, 5 Aug 2021 18:31:15 +0100 Subject: [PATCH 05/10] Fine Kai --- Silk.NET.sln | 19 ++ src/bindings/Directory.Build.targets | 6 + .../Silk.NET.Vulkan/Silk.NET.Vulkan.csproj | 8 + src/bindings/Silk.NET.Vulkan/Vk.cs | 10 + src/bindings/silktouch.json | 12 ++ .../Configuration/Config.cs | 8 +- .../Silk.NET.SilkTouch.Common/Diagnostics.cs | 9 +- .../Generation/GeneratorBase.cs | 17 -- .../Generation/SilkTouchGenerator.cs | 149 ++++++++++++++ .../Silk.NET.SilkTouch.Common.csproj | 2 +- .../EmitterGenerationExtensions.cs | 47 +++++ .../EmitterGenerator.cs | 3 +- .../OverloaderGenerationExtensions.cs | 47 +++++ .../OverloaderGenerator.cs | 3 +- .../SilkTouchSourceGenerator.cs | 100 ++-------- .../ScraperGenerationExtensions.cs | 49 +++++ .../ScraperGenerator.cs | 1 + .../Silk.NET.SilkTouch.Scraper.csproj | 4 - .../Subagent/ISubagent.cs | 1 - .../Subagent/VisualStudioResolver.cs | 20 +- src/generators/SilkTouch/GeneratorHandoff.cs | 181 ++++++------------ src/generators/SilkTouch/Program.cs | 86 ++++----- src/generators/SilkTouch/SilkTouch.csproj | 1 + 23 files changed, 482 insertions(+), 301 deletions(-) create mode 100644 src/bindings/Directory.Build.targets create mode 100644 src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj create mode 100644 src/bindings/Silk.NET.Vulkan/Vk.cs create mode 100644 src/bindings/silktouch.json delete mode 100644 src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs create mode 100644 src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs diff --git a/Silk.NET.sln b/Silk.NET.sln index 638a643775..9619111262 100644 --- a/Silk.NET.sln +++ b/Silk.NET.sln @@ -33,6 +33,10 @@ ProjectSection(SolutionItems) = preProject EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bindings", "bindings", "{9020C7C6-C366-4BD3-8C8A-F81394EC7174}" +ProjectSection(SolutionItems) = preProject + src\bindings\silktouch.json = src\bindings\silktouch.json + src\bindings\Directory.Build.targets = src\bindings\Directory.Build.targets +EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generators", "generators", "{8238D9F3-E158-4633-8017-B29AA3AD61F7}" ProjectSection(SolutionItems) = preProject @@ -65,6 +69,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.SilkTouch.Overload EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Core", "src\libraries\Silk.NET.Core\Silk.NET.Core.csproj", "{69CF4437-59F7-4304-9AE1-4B58E1A93367}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Vulkan", "src\bindings\Silk.NET.Vulkan\Silk.NET.Vulkan.csproj", "{AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -243,6 +249,18 @@ Global {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x64.Build.0 = Release|Any CPU {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x86.ActiveCfg = Release|Any CPU {69CF4437-59F7-4304-9AE1-4B58E1A93367}.Release|x86.Build.0 = Release|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Debug|x64.Build.0 = Debug|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Debug|x86.Build.0 = Debug|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Release|Any CPU.Build.0 = Release|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Release|x64.ActiveCfg = Release|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Release|x64.Build.0 = Release|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Release|x86.ActiveCfg = Release|Any CPU + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -269,6 +287,7 @@ Global {50F26B27-32B6-4D66-ADD5-CC9C38373B19} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} {46A89F89-77B3-4679-A82F-04A552545B16} = {8238D9F3-E158-4633-8017-B29AA3AD61F7} {69CF4437-59F7-4304-9AE1-4B58E1A93367} = {C9718C94-2F21-4E8D-B55D-8F0B1A131346} + {AF6E05E0-9C51-4D52-AC7E-056714CAC5F4} = {9020C7C6-C366-4BD3-8C8A-F81394EC7174} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D} diff --git a/src/bindings/Directory.Build.targets b/src/bindings/Directory.Build.targets new file mode 100644 index 0000000000..e640b36b0c --- /dev/null +++ b/src/bindings/Directory.Build.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj b/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj new file mode 100644 index 0000000000..58ef59a24a --- /dev/null +++ b/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj @@ -0,0 +1,8 @@ + + + + net6.0 + enable + + + diff --git a/src/bindings/Silk.NET.Vulkan/Vk.cs b/src/bindings/Silk.NET.Vulkan/Vk.cs new file mode 100644 index 0000000000..37da444f83 --- /dev/null +++ b/src/bindings/Silk.NET.Vulkan/Vk.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.Vulkan +{ + public class Vk + { + + } +} diff --git a/src/bindings/silktouch.json b/src/bindings/silktouch.json new file mode 100644 index 0000000000..4dd3003af0 --- /dev/null +++ b/src/bindings/silktouch.json @@ -0,0 +1,12 @@ +{ + "global": { + + }, + "projects": { + "Silk.NET.Vulkan": { + "scraper": { + + } + } + } +} \ No newline at end of file diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index 4c4bc9f593..e7c80a93ed 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel; using System.Data; using System.IO; using System.Text.Json; @@ -12,6 +11,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; using Silk.NET.SilkTouch.Configuration.Json; +using Ultz.Extensions.Logging; namespace Silk.NET.SilkTouch.Configuration { @@ -40,6 +40,7 @@ out AdditionalText? usedText if (provider.GlobalOptions .TryGetValue(Constants.ConfigFileEditorconfigOption, out var file)) { + Log.Debug($"User has overriden \"{configFileName}\" to \"{file}\""); configFileName = file; } @@ -47,10 +48,13 @@ out AdditionalText? usedText usedText = null; foreach (var additionalFile in additionalFiles) { + Log.Debug($"Testing \"{additionalFile.Path}\" (expecting \"{configFileName}\")..."); if (additionalFile.Path == configFileName || Path.GetFileName(additionalFile.Path) == configFileName) { + Log.Debug($"\"{additionalFile.Path}\" is a good match."); if (usedText is not null) { + Log.Debug($"We've already found \"{usedText.Path}\" though!"); config = null; var ret = Diagnostic.Create ( @@ -70,11 +74,13 @@ out AdditionalText? usedText if (usedText is null) { + Log.Debug("No config."); config = null; usedText = null; return Diagnostic.Create(Diagnostics.NoConfigFile, Location.None); } + Log.Debug("Good config."); config = Load(File.ReadAllText(usedText.Path)); // was gonna use usedText.GetText() until I saw their code. return null; } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs index 7d2b0c80ee..c97a615ab0 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; @@ -27,7 +26,7 @@ public class Diagnostics ( id: "ST0006", title: "No Configuration File", - messageFormat: "No configuration file, SilkTouch will not run.. To configure a path to a SilkTouch JSON " + + messageFormat: "No configuration file, SilkTouch will not run. To configure a path to a SilkTouch JSON " + $"Configuration file, use the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig " + "option.", category: "SilkTouch", @@ -100,11 +99,11 @@ public class Diagnostics ( id: "ST0011", title: "No Configuration For This Project", - messageFormat: "\"{0}\" does not contain any configuration for project \"{1}\" (\"projects\" > " + - "\"{1}\": {...})", + messageFormat: "\"{0}\" does not contain any configuration for project \"{1}\", SilkTouch will not run " + + "(\"projects\" > \"{1}\": {{...}})", category: "SilkTouch", defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: null, customTags: WellKnownDiagnosticTags.AnalyzerException diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs deleted file mode 100644 index e246dc7c35..0000000000 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/GeneratorBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Silk.NET.SilkTouch.Configuration; - -namespace Silk.NET.SilkTouch.Generation -{ - public abstract class GeneratorBase - { - protected GeneratorBase(FormFactors formFactor) - { - - } - - // TODO remove duplicate code between SilkTouchSourceGenerator and GeneratorHandoff in this class - } -} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs new file mode 100644 index 0000000000..a235aa55e8 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Silk.NET.SilkTouch.Configuration; +using Ultz.Extensions.Logging; + +namespace Silk.NET.SilkTouch.Generation +{ + public sealed class SilkTouchGenerator + { + public SilkTouchGenerator(FormFactors formFactor) => FormFactor = formFactor; + + // Public Properties + public FormFactors FormFactor { get; } + public bool IsActive { get; private set; } + public Compilation? Compilation { get; private set; } + public string? AssemblyName { get; private set; } + public RootConfiguration? AllConfiguration { get; private set; } + public ProjectConfiguration? ThisConfiguration { get; private set; } + public string? BaseDirectory { get; private set; } + public ImmutableArray? SyntaxTrees { get; private set; } + + // Public Events + public event Action? DiagnosticRaised; + public event Action<(string FileNameHint, string Content)>? OutputGenerated; + + // TODO remove duplicate code between SilkTouchSourceGenerator and GeneratorHandoff in this class + public bool Begin + ( + Compilation? compilation, + AnalyzerConfigOptionsProvider editorConfig, + ImmutableArray additionalFiles, + string? assemblyName = null + ) + { + var loadDiag = Config.Load + ( + editorConfig, + additionalFiles, + out var config, + out var usedText + ); + + if (loadDiag is not null) + { + DiagnosticRaised?.Invoke(loadDiag); + return false; + } + + assemblyName ??= compilation?.AssemblyName; + if (assemblyName is null) + { + DiagnosticRaised?.Invoke(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); + return false; + } + + // prepare our context data + ProjectConfiguration? projectConfig = null; + var (_, projects) = config!; + if ((!projects?.TryGetValue(assemblyName, out projectConfig) ?? false) || + projectConfig is null) + { + Log.Trace + ( + $"No project-specific config for \"{assemblyName}\" found - this is treated as \"don't run " + + "SilkTouch\"" + ); + + DiagnosticRaised?.Invoke + ( + Diagnostic.Create + ( + Diagnostics.NoProjectConfigInFile, + Location.None, + usedText!.Path, + assemblyName + ) + ); + + return false; + } + + if (compilation is null) + { + // we can't proceed further without a compilation. + DiagnosticRaised?.Invoke(Diagnostic.Create(Diagnostics.GeneralError, Location.None, "No compilation.")); + return false; + } + + var syntaxTrees = compilation.SyntaxTrees.OfType().ToImmutableArray(); + var baseDir = Path.GetDirectoryName(usedText!.Path); + if (baseDir is null) + { + DiagnosticRaised?.Invoke + ( + Diagnostic.Create + ( + Diagnostics.GeneralError, + Location.None, + "Couldn't determine directory name for configuration file." + ) + ); + + return false; + } + + IsActive = true; + Compilation = compilation; + AssemblyName = assemblyName; + AllConfiguration = config; + ThisConfiguration = projectConfig; + BaseDirectory = baseDir; + SyntaxTrees = syntaxTrees; + return true; + } + + public void IngestContext(SilkTouchContext ctx) + { + var (outputs, diagnostics) = ctx.GetResult(); + foreach (var diagnostic in diagnostics) + { + DiagnosticRaised?.Invoke(diagnostic); + } + + foreach (var (fileNameHint, content) in outputs) + { + OutputGenerated?.Invoke((fileNameHint, content)); + } + } + + public void End() + { + IsActive = false; + Compilation = null; + AssemblyName = null; + AllConfiguration = null; + ThisConfiguration = null; + BaseDirectory = null; + SyntaxTrees = null; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj index 9b7139ec80..28d9c3dad3 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj +++ b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs new file mode 100644 index 0000000000..ebf6bb1f67 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Silk.NET.SilkTouch.Generation; +using Ultz.Extensions.Logging; + +namespace Silk.NET.SilkTouch.Emitter +{ + public static class EmitterGenerationExtensions + { + public static bool RunEmitter(this SilkTouchGenerator generator) + { + if (!generator.IsActive || + generator.AssemblyName is null || + generator.SyntaxTrees is null || + generator.ThisConfiguration is null || + generator.BaseDirectory is null) + { + // generator is not active, move along... + Log.Debug("Generator is not active."); + return false; + } + + // run the emitter if the config indicates we should. + if (generator.ThisConfiguration?.Emitter is null || + ((generator.ThisConfiguration?.Emitter?.FormFactors ?? EmitterGenerator.DefaultFormFactors) & + generator.FormFactor) == 0) + { + Log.Trace("Emitter is not configured to run."); + return false; + } + + var ctx = new SilkTouchContext + ( + generator.AssemblyName, + generator.SyntaxTrees, + generator.ThisConfiguration!, + generator.AllConfiguration?.Global, + generator.BaseDirectory + ); + + EmitterGenerator.Run(ctx); + generator.IngestContext(ctx); + return true; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs index 2300854978..8b6022f37b 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs @@ -1,5 +1,4 @@ -using System; -using Silk.NET.SilkTouch.Configuration; +using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; namespace Silk.NET.SilkTouch.Emitter diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs new file mode 100644 index 0000000000..d434ac7473 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Silk.NET.SilkTouch.Generation; +using Ultz.Extensions.Logging; + +namespace Silk.NET.SilkTouch.Overloader +{ + public static class OverloaderGenerationExtensions + { + public static bool RunOverloader(this SilkTouchGenerator generator) + { + if (!generator.IsActive || + generator.AssemblyName is null || + generator.SyntaxTrees is null || + generator.ThisConfiguration is null || + generator.BaseDirectory is null) + { + // generator is not active, move along... + Log.Debug("Generator is not active."); + return false; + } + + // run the overloader if the config indicates we should. + if (generator.ThisConfiguration?.Overloader is null || + ((generator.ThisConfiguration?.Overloader?.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & + generator.FormFactor) == 0) + { + Log.Trace("Overloader is not configured to run."); + return false; + } + + var ctx = new SilkTouchContext + ( + generator.AssemblyName, + generator.SyntaxTrees, + generator.ThisConfiguration!, + generator.AllConfiguration?.Global, + generator.BaseDirectory + ); + + OverloaderGenerator.Run(ctx); + generator.IngestContext(ctx); + return true; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs index d65da3bd6e..cdad11d192 100644 --- a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs @@ -1,5 +1,4 @@ -using System; -using Silk.NET.SilkTouch.Configuration; +using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; namespace Silk.NET.SilkTouch.Overloader diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs index ae151d239c..fb4b301c10 100644 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs @@ -2,12 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Text; using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Emitter; using Silk.NET.SilkTouch.Generation; @@ -25,95 +20,22 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext context) { - var loadDiag = Config.Load(context.AnalyzerConfigOptions, context.AdditionalFiles, out var config, out var usedText); - if (loadDiag is not null) - { - context.ReportDiagnostic(loadDiag); - return; - } - // Prevent debug logs from being output by forcing the LoggerProvider to null - we don't want this in Roslyn Log.LoggerProvider = null; - - if (context.Compilation.AssemblyName is null) - { - context.ReportDiagnostic(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); + + // Create the generator + var generator = new SilkTouchGenerator(FormFactors.Roslyn); + generator.DiagnosticRaised += context.ReportDiagnostic; + generator.OutputGenerated += x => context.AddSource(x.FileNameHint, x.Content); + if (!generator.Begin(context.Compilation, context.AnalyzerConfigOptions, context.AdditionalFiles)) + { + // diagnostics already raised, just quit. return; } - // prepare our context data - ProjectConfiguration? projectConfig = null; - var syntaxTrees = context.Compilation.SyntaxTrees.OfType().ToArray(); - var (global, projects) = config!; - if ((!projects?.TryGetValue(context.Compilation.AssemblyName, out projectConfig) ?? false) || - projectConfig is null) - { - context.ReportDiagnostic - ( - Diagnostic.Create - ( - Diagnostics.NoProjectConfigInFile, - Location.None, - usedText!.Path, - context.Compilation.AssemblyName - ) - ); - - return; - } - - var baseDir = Path.GetDirectoryName(usedText!.Path); - if (baseDir is null) - { - context.ReportDiagnostic - ( - Diagnostic.Create - ( - Diagnostics.GeneralError, - Location.None, - "Couldn't determine directory name for configuration file." - ) - ); - - return; - } - - // run the emitter if the config indicates we should. - if (projectConfig?.Emitter is null || - ((projectConfig.Emitter.FormFactors ?? EmitterGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) - { - var ctx = new SilkTouchContext - ( - context.Compilation.AssemblyName, - syntaxTrees, - projectConfig!, - global, - baseDir - ); - - EmitterGenerator.Run(ctx); - var (outputs, diagnostics) = ctx.GetResult(); - Copy(context, outputs, diagnostics); - } - - // run the overloader if the config indicates we should. - if (projectConfig?.Overloader is null || - ((projectConfig.Overloader.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & FormFactors.Roslyn) - != 0) - { - var ctx = new SilkTouchContext - ( - context.Compilation.AssemblyName, - syntaxTrees, - projectConfig!, - global, - baseDir - ); - - OverloaderGenerator.Run(ctx); - var (outputs, diagnostics) = ctx.GetResult(); - Copy(context, outputs, diagnostics); - } + generator.RunEmitter(); + generator.RunOverloader(); + generator.End(); } private static void Copy diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs new file mode 100644 index 0000000000..6f1849b5eb --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using Silk.NET.SilkTouch.Generation; +using Silk.NET.SilkTouch.Scraper.Subagent; +using Ultz.Extensions.Logging; + +namespace Silk.NET.SilkTouch.Scraper +{ + public static class ScraperGenerationExtensions + { + public static async ValueTask RunScraperAsync(this SilkTouchGenerator generator) + where T : ISubagent, new() + { + if (!generator.IsActive || + generator.AssemblyName is null || + generator.SyntaxTrees is null || + generator.ThisConfiguration is null || + generator.BaseDirectory is null) + { + // generator is not active, move along... + Log.Debug("Generator is not active."); + return false; + } + + // run the scarper if the config indicates we should. + if (generator.ThisConfiguration?.Scraper is null || + (ScraperGenerator.DefaultFormFactors & generator.FormFactor) == 0) + { + Log.Trace("Scraper is not configured to run."); + return false; + } + + var ctx = new SilkTouchContext + ( + generator.AssemblyName, + generator.SyntaxTrees, + generator.ThisConfiguration!, + generator.AllConfiguration?.Global, + generator.BaseDirectory + ); + + await ScraperGenerator.RunAsync(ctx); + generator.IngestContext(ctx); + return true; + } + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index 20278c859f..87764dad9c 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -19,6 +19,7 @@ public static class ScraperGenerator public const FormFactors DefaultFormFactors = FormFactors.CLI; public static async Task RunAsync(SilkTouchContext ctx) where T:ISubagent, new() { + Log.Information("Scraper started."); var subagentSpawner = new T(); var error = false; var libraryNames = ctx.Configuration.Scraper?.LibraryNames; diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj index 3a70209f18..f8bc245593 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -7,11 +7,7 @@ - - - - diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs index 934ba3d5d3..4d2e237242 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/ISubagent.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs index a7eb017af7..c1ac95f34a 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs @@ -3,17 +3,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Management; -using System.Net; -using System.Runtime.Versioning; -using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.Build.Locator; -using Microsoft.Extensions.Logging; using Ultz.Extensions.Logging; namespace Silk.NET.SilkTouch.Scraper.Subagent @@ -62,7 +55,7 @@ public static bool TryGetMSBuildInfo var use = MSBuildLocator.QueryVisualStudioInstances() .OrderBy(x => x.DiscoveryType) - .ThenBy(x => x.Version) + .ThenByDescending(x => x.Version) .FirstOrDefault(); if (use is null) @@ -71,7 +64,7 @@ public static bool TryGetMSBuildInfo return false; } - Log.Information($"Using MSBuild at \"{use.MSBuildPath}\" (from \"{use.Name}\" v{use.Version.ToString(3)}"); + Log.Information($"Using MSBuild at \"{use.MSBuildPath}\" (from \"{use.Name}\" v{use.Version.ToString(3)})"); instance = use; return true; } @@ -97,11 +90,13 @@ public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioIn .QueryVisualStudioInstances() .Where(x => (x.DiscoveryType & (DiscoveryType.DeveloperConsole | DiscoveryType.VisualStudioSetup)) != 0) .OrderBy(x => x.DiscoveryType) - .ThenBy(x => x.Version); + .ThenByDescending(x => x.Version); // cycle through candidate installations, try and find everything we're looking for. + var hasVs = false; foreach (var visualStudio in visualStudios) { + hasVs = true; Log.Debug ( $"Testing \"{visualStudio.Name}\" v{visualStudio.Version.ToString(3)} at " + @@ -161,6 +156,11 @@ public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioIn return true; } + + if (!hasVs) + { + Log.Trace("No instance of Visual Studio found whatsoever."); + } // if any of it's still null, we couldn't find a candidate. Log.Warning diff --git a/src/generators/SilkTouch/GeneratorHandoff.cs b/src/generators/SilkTouch/GeneratorHandoff.cs index 55d352a002..225e38abb8 100644 --- a/src/generators/SilkTouch/GeneratorHandoff.cs +++ b/src/generators/SilkTouch/GeneratorHandoff.cs @@ -2,20 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; using System.Threading.Tasks; -using Microsoft.Build.Logging; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.Extensions.Logging; -using Silk.NET.SilkTouch; +using Microsoft.CodeAnalysis.MSBuild; using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Emitter; using Silk.NET.SilkTouch.Generation; using Silk.NET.SilkTouch.Overloader; +using Silk.NET.SilkTouch.Scraper; using Ultz.Extensions.Logging; namespace SilkTouch @@ -23,146 +17,81 @@ namespace SilkTouch internal static class GeneratorHandoff { public static int ExitCode { get; private set; } - public static async ValueTask HandleProjectAsync(Project project, CancellationToken cancellationToken) + + public static async ValueTask HandleProjectAsync(MSBuildWorkspace workspace, Project project) { - var loadDiag = Config.Load + // Create the generator + var generator = new SilkTouchGenerator(FormFactors.CLI); + var raiseDiagnostic = LogDiagnostic(project.AssemblyName); + generator.DiagnosticRaised += raiseDiagnostic; + generator.OutputGenerated += x => + { + // TODO implement this, we need to either add our files to the workspace or come up with some other hack + throw new NotImplementedException(); + }; + + if (!generator.Begin ( + await project.GetCompilationAsync(), project.AnalyzerOptions.AnalyzerConfigOptionsProvider, project.AnalyzerOptions.AdditionalFiles, - out var config, - out var usedText - ); - - if (loadDiag is not null) - { - WriteDiagnostics(loadDiag); - return; - } - - if (!project.TryGetCompilation(out var compilation)) - { - WriteDiagnostics(); - return; - } - - if (compilation.AssemblyName is null) - { - WriteDiagnostics(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); - return; - } - - // prepare our context data - ProjectConfiguration? projectConfig = null; - var syntaxTrees = compilation.SyntaxTrees.OfType().ToArray(); - var (global, projects) = config!; - if ((!projects?.TryGetValue(compilation.AssemblyName, out projectConfig) ?? false) || - projectConfig is null) + project.AssemblyName + )) { - WriteDiagnostics - ( - Diagnostic.Create - ( - Diagnostics.NoProjectConfigInFile, - Location.None, - usedText!.Path, - compilation.AssemblyName - ) - ); - } - - var baseDir = Path.GetDirectoryName(usedText!.Path); - if (baseDir is null) - { - WriteDiagnostics - ( - Diagnostic.Create - ( - Diagnostics.GeneralError, - Location.None, - "Couldn't determine directory name for configuration file." - ) - ); - + // diagnostics already raised, just quit. + Log.Debug($"generator.Begin failed - {project.AssemblyName}"); return; } - // run the emitter if the config indicates we should. - if (projectConfig?.Emitter is null || - ((projectConfig.Emitter.FormFactors ?? EmitterGenerator.DefaultFormFactors) & FormFactors.Roslyn) != 0) - { - var ctx = new SilkTouchContext - ( - compilation.AssemblyName, - syntaxTrees, - projectConfig!, - global, - baseDir - ); - - EmitterGenerator.Run(ctx); - var (outputs, diagnostics) = ctx.GetResult(); - Copy(project, outputs, diagnostics); - } - - // run the overloader if the config indicates we should. - if (projectConfig?.Overloader is null || - ((projectConfig.Overloader.FormFactors ?? OverloaderGenerator.DefaultFormFactors) & FormFactors.Roslyn) - != 0) - { - var ctx = new SilkTouchContext - ( - compilation.AssemblyName, - syntaxTrees, - projectConfig!, - global, - baseDir - ); - - OverloaderGenerator.Run(ctx); - var (outputs, diagnostics) = ctx.GetResult(); - Copy(project, outputs, diagnostics); - } + await generator.RunScraperAsync(); + await Task.WhenAll(Task.Run(generator.RunEmitter), Task.Run(generator.RunOverloader)); + generator.End(); } - private static void Copy(Project project, List<(string FileNameHint, string Content)> outputs, List diagnostics) - { - // TODO IMPLEMENT THIS SHIT - throw new NotImplementedException(); - } - - private static void WriteDiagnostics(params Diagnostic[] diagnostics) - => WriteDiagnostics((IEnumerable) diagnostics); - - private static void WriteDiagnostics(IEnumerable diagnostics) + private static Action LogDiagnostic(string proj) => diagnostic => { - foreach (var diagnostic in diagnostics) + var diagnosticString = $"{diagnostic} ({proj})"; + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (diagnostic.Severity) { - // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (diagnostic.Severity) + case DiagnosticSeverity.Hidden: { - case DiagnosticSeverity.Hidden: + Log.Trace(diagnosticString); + break; + } + case DiagnosticSeverity.Info: + { + if (diagnostic.Descriptor.IsEnabledByDefault) { - Log.Trace(diagnostic.ToString()); - break; + Log.Information(diagnosticString); } - case DiagnosticSeverity.Info: + else { - Log.Information(diagnostic.ToString()); - break; + Log.Trace(diagnosticString); } - case DiagnosticSeverity.Warning: + + break; + } + case DiagnosticSeverity.Warning: + { + if (diagnostic.Descriptor.IsEnabledByDefault) { - Log.Warning(diagnostic.ToString()); - break; + Log.Warning(diagnosticString); } - case DiagnosticSeverity.Error: + else { - Log.Error(diagnostic.ToString()); - ExitCode--; - break; + Log.Trace(diagnosticString); } + + break; + } + case DiagnosticSeverity.Error: + { + Log.Error(diagnosticString); + ExitCode--; + break; } } - } + }; } } diff --git a/src/generators/SilkTouch/Program.cs b/src/generators/SilkTouch/Program.cs index 0d4868ecc0..451d27a723 100644 --- a/src/generators/SilkTouch/Program.cs +++ b/src/generators/SilkTouch/Program.cs @@ -4,10 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Text.Json; -using System.Threading; using System.Threading.Tasks; -using Microsoft.Build.Evaluation; using Microsoft.Build.Locator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.MSBuild; @@ -29,41 +26,39 @@ static async Task Main(string[] args) return ClangSharpHandoff.RunClangSharp(args); } - Console.WriteLine - ( - "Silk.NET SilkTouch - " + - $"v{typeof(Program).Assembly.GetName().Version?.ToString(3)} - " + - "Copyright (c) .NET Foundation and Contributors" - ); - var slnOrProjInCwd = ResolveProjectOrSolutionInCwd(); var rootCommand = new RootCommand { - slnOrProjInCwd is null ? new Option - ( - new[] { "--project", "-p" }, - "The input solution or project file to generate for." - ) : new - ( - new[] { "--project", "-p" }, - () => slnOrProjInCwd, // add a default value factory if we've found an individual project or solution - "The input solution or project file to generate for." - ), + slnOrProjInCwd is null + ? new Option + ( + new[] { "--project", "-p" }, + "The input solution or project file to generate for." + ) + { + IsRequired = true + } + : new + ( + new[] { "--project", "-p" }, + () => slnOrProjInCwd, // add a default value factory if we've found an individual project or solution + "The input solution or project file to generate for." + ), new Option ( - new[] {"--logging", "-l"}, - () => LogMode.Standard, + new[] { "--logging", "-l" }, + () => Debugger.IsAttached ? LogMode.VVerbose : LogMode.Standard, "The debug logging verbosity." ) }; - + rootCommand.Handler = CommandHandler.Create(RunSilkTouchAsync); return await rootCommand.InvokeAsync(args); } - private static async Task RunSilkTouchAsync(FileInfo projectOrSolution, LogMode logMode) + private static async Task RunSilkTouchAsync(FileInfo project, LogMode logging) { - if (logMode != LogMode.Silent) + if (logging != LogMode.Silent) { Console.WriteLine ( @@ -73,14 +68,14 @@ private static async Task RunSilkTouchAsync(FileInfo projectOrSolution, Log ); } - var sw = new Stopwatch(); - // Configure the logger var loggerProvider = new UltzLoggerProvider(); loggerProvider.LogLevels.Clear(); - + + var sw = Stopwatch.StartNew(); + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (logMode) + switch (logging) { case LogMode.Silent: { @@ -92,22 +87,22 @@ private static async Task RunSilkTouchAsync(FileInfo projectOrSolution, Log { loggerProvider.LogLevels.Add(LogLevel.Information); loggerProvider.LogLevels.Add(LogLevel.Warning); - break; + goto case LogMode.Silent; } case LogMode.Verbose: { loggerProvider.LogLevels.Add(LogLevel.Trace); - break; + goto case LogMode.Standard; } case LogMode.VVerbose: { loggerProvider.LogLevels.Add(LogLevel.Debug); - break; + goto case LogMode.Verbose; } } - + Log.LoggerProvider = loggerProvider; - + // Select MSBuild if (!VisualStudioResolver.TryGetMSBuildInfo(out var instance)) { @@ -116,10 +111,10 @@ private static async Task RunSilkTouchAsync(FileInfo projectOrSolution, Log } MSBuildLocator.RegisterInstance(instance); - + // Create Workspace using var workspace = MSBuildWorkspace.Create(); - using var projectOrSolutionReader = projectOrSolution.OpenText(); + using var projectOrSolutionReader = project.OpenText(); string? line; var isSolution = false; while ((line = await projectOrSolutionReader.ReadLineAsync()) is not null) @@ -145,24 +140,29 @@ private static async Task RunSilkTouchAsync(FileInfo projectOrSolution, Log // Load the workspace and handoff to the generators if (isSolution) { - var sln = await workspace.OpenSolutionAsync(projectOrSolution.FullName); - await Parallel.ForEachAsync(sln.Projects, GeneratorHandoff.HandleProjectAsync); + var sln = await workspace.OpenSolutionAsync(project.FullName); + await Parallel.ForEachAsync(sln.Projects, (x, _) => GeneratorHandoff.HandleProjectAsync(workspace, x)); } else { await GeneratorHandoff.HandleProjectAsync ( - await workspace.OpenProjectAsync(projectOrSolution.FullName), - CancellationToken.None + workspace, + await workspace.OpenProjectAsync(project.FullName) ); } - sw.Stop(); - Log.Information($"Concluded after {sw.Elapsed.TotalSeconds} seconds."); + Log.Information + ( + GeneratorHandoff.ExitCode == 0 + ? $"Finished after {sw.Elapsed.TotalSeconds} seconds." + : $"Failed after {sw.Elapsed.TotalSeconds} seconds." + ); + return GeneratorHandoff.ExitCode; } - private static void LogWorkspaceFailure(object? sender, WorkspaceDiagnosticEventArgs e) + private static void LogWorkspaceFailure(object? s, WorkspaceDiagnosticEventArgs e) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (e.Diagnostic.Kind) diff --git a/src/generators/SilkTouch/SilkTouch.csproj b/src/generators/SilkTouch/SilkTouch.csproj index 4521127663..b585173f49 100644 --- a/src/generators/SilkTouch/SilkTouch.csproj +++ b/src/generators/SilkTouch/SilkTouch.csproj @@ -14,6 +14,7 @@ + From f40802d82828b65064d5f19ee36a051ff4afcbcb Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Sat, 7 Aug 2021 17:34:03 +0100 Subject: [PATCH 06/10] Lots of changes, some review fixes, very almost generating XML now. --- .gitmodules | 3 + src/bindings/Directory.Build.targets | 3 +- .../Silk.NET.Vulkan/Silk.NET.Vulkan.csproj | 1 - src/bindings/Silk.NET.Vulkan/Vk.cs | 2 +- src/bindings/Silk.NET.Vulkan/silktouch.json | 51 ++++++++++ src/bindings/silktouch.json | 14 +-- .../Silk.NET.SilkTouch.ClangSharp.Xml.csproj | 1 - .../Configuration/Config.cs | 79 +++++++++------ .../Json/AllowSeparateFileJsonConverter.cs | 10 ++ .../Json/ExcludesJsonConverter.cs | 4 +- .../Silk.NET.SilkTouch.Common/Diagnostics.cs | 60 +++++------- .../Generation/SilkTouchContext.cs | 2 +- .../Generation/SilkTouchGenerator.cs | 47 +++------ .../Silk.NET.SilkTouch.Common.csproj | 1 - .../EmitterGenerationExtensions.cs | 6 +- .../Silk.NET.SilkTouch.Emitter.csproj | 1 - .../OverloaderGenerationExtensions.cs | 6 +- .../Silk.NET.SilkTouch.Overloader.csproj | 1 - .../Silk.NET.SilkTouch.Roslyn.csproj | 1 - .../ScraperGenerationExtensions.cs | 8 +- .../ScraperGenerator.cs | 95 ++++++++++++++----- .../Silk.NET.SilkTouch.Scraper.csproj | 4 +- src/generators/SilkTouch/ClangSharpHandoff.cs | 4 +- .../SilkTouch/ClangSharpSubagent.cs | 31 +++++- .../Silk.NET.Core/Silk.NET.Core.csproj | 1 - src/submodules/Vulkan-Headers | 1 + 26 files changed, 268 insertions(+), 169 deletions(-) create mode 100644 .gitmodules create mode 100644 src/bindings/Silk.NET.Vulkan/silktouch.json create mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs create mode 160000 src/submodules/Vulkan-Headers diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..ffa1ad097d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/submodules/Vulkan-Headers"] + path = src/submodules/Vulkan-Headers + url = https://github.com/KhronosGroup/Vulkan-Headers.git diff --git a/src/bindings/Directory.Build.targets b/src/bindings/Directory.Build.targets index e640b36b0c..1ae9c4774c 100644 --- a/src/bindings/Directory.Build.targets +++ b/src/bindings/Directory.Build.targets @@ -1,6 +1,7 @@  - + + \ No newline at end of file diff --git a/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj b/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj index 58ef59a24a..4f444d8c8b 100644 --- a/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj +++ b/src/bindings/Silk.NET.Vulkan/Silk.NET.Vulkan.csproj @@ -2,7 +2,6 @@ net6.0 - enable diff --git a/src/bindings/Silk.NET.Vulkan/Vk.cs b/src/bindings/Silk.NET.Vulkan/Vk.cs index 37da444f83..9f6477811a 100644 --- a/src/bindings/Silk.NET.Vulkan/Vk.cs +++ b/src/bindings/Silk.NET.Vulkan/Vk.cs @@ -3,7 +3,7 @@ namespace Silk.NET.Vulkan { - public class Vk + public partial class Vk { } diff --git a/src/bindings/Silk.NET.Vulkan/silktouch.json b/src/bindings/Silk.NET.Vulkan/silktouch.json new file mode 100644 index 0000000000..862d19b636 --- /dev/null +++ b/src/bindings/Silk.NET.Vulkan/silktouch.json @@ -0,0 +1,51 @@ +{ + "globalFile": "../silktouch.json", + "scraper": { + "jobs": [ + { + "headerText": [ + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include " + ], + "include": [ + "../../submodules/Vulkan-Headers/include" + ], + "exclude": [ + ], + "traverse": [ + "../../submodules/Vulkan-Headers/include/vk_video/*.h" + ], + "unixMode": false, + "mods": [ + ], + "modOptions": { + }, + "libraryNames": ["vulkan"], + "namespace": "Silk.NET.Vulkan.Video", + "clangArgs": [ + "-m32", + "-Wno-expansion-to-defined", + "-Wno-extern-initializer", + "-Wno-extra-tokens", + "-Wno-ignored-attributes", + "-Wno-ignored-pragma-intrinsic", + "-Wno-macro-redefined", + "-Wno-nonportable-include-path", + "-Wno-pragma-pack" + ], + "define": [], + "className": "VulkanVideo", + "methodPrefix": "vk", + "remappingFiles": [], + "conventions": { + "*": "winapi" + } + } + ] + } +} \ No newline at end of file diff --git a/src/bindings/silktouch.json b/src/bindings/silktouch.json index 4dd3003af0..75ca2e5567 100644 --- a/src/bindings/silktouch.json +++ b/src/bindings/silktouch.json @@ -1,12 +1,6 @@ { - "global": { - - }, - "projects": { - "Silk.NET.Vulkan": { - "scraper": { - - } - } - } + "fileHeader": [ + "Licensed to the .NET Foundation under one or more agreements.", + "The .NET Foundation licenses this file to you under the MIT license." + ] } \ No newline at end of file diff --git a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj index 58ef59a24a..4f444d8c8b 100644 --- a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj +++ b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/Silk.NET.SilkTouch.ClangSharp.Xml.csproj @@ -2,7 +2,6 @@ net6.0 - enable diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index e7c80a93ed..aedcea6bce 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Data; @@ -18,21 +19,22 @@ namespace Silk.NET.SilkTouch.Configuration public static class Config { /// - /// Loads the given SilkTouch JSON Configuration as a record. + /// Loads the given SilkTouch JSON Configuration as a record. /// /// The SilkTouch JSON Configuration. - /// The record representation of the JSON. + /// The record representation of the JSON. /// If the data yielded a null configuration. - public static RootConfiguration Load(string json) - => JsonSerializer.Deserialize(json) ?? + public static ProjectConfiguration Load(string json) + => JsonSerializer.Deserialize(json) ?? throw new DataException("JSON deserialization of SilkTouch Configuration yielded null."); - public static Diagnostic? Load + public static bool TryLoad ( AnalyzerConfigOptionsProvider provider, ImmutableArray additionalFiles, - out RootConfiguration? config, - out AdditionalText? usedText + out ProjectConfiguration? config, + out AdditionalText? usedText, + out Diagnostic? diagnostic ) { // Get the config file name. Uses silktouch.json unless overridden in .editorconfig. @@ -54,18 +56,17 @@ out AdditionalText? usedText Log.Debug($"\"{additionalFile.Path}\" is a good match."); if (usedText is not null) { - Log.Debug($"We've already found \"{usedText.Path}\" though!"); + Log.Debug($"We've already found \"{usedText.Path}\" though - using that instead!"); config = null; - var ret = Diagnostic.Create + diagnostic = Diagnostic.Create ( Diagnostics.MultipleConfigFiles, Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default), usedText.Path, additionalFile.Path ); - - usedText = null; - return ret; + + continue; } usedText = additionalFile; @@ -77,34 +78,25 @@ out AdditionalText? usedText Log.Debug("No config."); config = null; usedText = null; - return Diagnostic.Create(Diagnostics.NoConfigFile, Location.None); + diagnostic = Diagnostic.Create(Diagnostics.NoConfigFile, Location.None); + return false; } Log.Debug("Good config."); config = Load(File.ReadAllText(usedText.Path)); // was gonna use usedText.GetText() until I saw their code. - return null; + diagnostic = null; + return true; } /// - /// Saves the given record into JSON. + /// Saves the given record into JSON. /// - /// The record representation of the configuration. + /// The record representation of the configuration. /// The JSON representation of the projects. - public static string Save(RootConfiguration config) + public static string Save(ProjectConfiguration config) => JsonSerializer.Serialize(config); } - /// - /// The root configuration structure. - /// - /// - /// - public record RootConfiguration - ( - [property: JsonPropertyName("global")] GlobalConfiguration Global, - [property: JsonPropertyName("projects")] Dictionary? Projects - ); - /// /// Common configuration across all projects. /// @@ -122,11 +114,33 @@ public record GlobalConfiguration /// SilkTouch Scraper specific configuration for this project. public record ProjectConfiguration ( + [property: JsonPropertyName("globalFile")] string? GlobalConfigFile, [property: JsonPropertyName("emitter")] EmitterConfiguration? Emitter, [property: JsonPropertyName("overloader")] OverloaderConfiguration? Overloader, [property: JsonPropertyName("scraper")] ScraperConfiguration? Scraper, - [property: JsonPropertyName("skipIf")] string[]? SkipIf - ); + [property: JsonPropertyName("cliSkipIf")] string[]? CommandLineSkipIf + ) + { + /// Gets the global config stored in if provided. + /// The base directory to search for files from. + /// The global config. + public GlobalConfiguration? GetGlobalConfiguration(string baseDir) + { + if (GlobalConfigFile is null) + { + return null; + } + + var path = GlobalConfigFile; + // Poor man's way of doing Path.GetFullPath(a, b) which is unavailable on NS20 + if (Path.GetFullPath(GlobalConfigFile) != GlobalConfigFile) + { + path = Path.Combine(baseDir, GlobalConfigFile); + } + + return JsonSerializer.Deserialize(File.ReadAllText(path)); + } + } /// /// SilkTouch Emitter specific configuration. @@ -156,6 +170,11 @@ public record OverloaderConfiguration /// SilkTouch Scraper specific configuration. /// public record ScraperConfiguration + ( + [property: JsonPropertyName("jobs")] ScraperJobConfiguration[]? Jobs + ); + + public record ScraperJobConfiguration ( [property: JsonPropertyName("headerText")] string[]? HeaderText, [property: JsonPropertyName("include")] string[]? IncludeDirectories, diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs new file mode 100644 index 0000000000..13d84c7bd2 --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.Configuration.Json +{ + public class AllowSeparateFileJsonConverter + { + + } +} diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs index d52c5563c5..e0b0459692 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs @@ -32,7 +32,7 @@ public class ExcludesJsonConverter : JsonConverter // the performance is horrible but it's not too much a cause for concern as it is a "fixed cost". // definitely improve it in the future though as we do have access to new string(char*), meaning we can // come up with a Span-based solution it'll just be very very very verbose. - if (kvp[0].ToLower().Trim() == "hint" && Enum.TryParse + if (string.Equals(kvp[0].Trim(), "hint", StringComparison.OrdinalIgnoreCase) && Enum.TryParse ( string.Join ( @@ -46,7 +46,7 @@ public class ExcludesJsonConverter : JsonConverter { hints |= hint; } - else if (kvp[0].ToLower().Trim() == "name") + else if (string.Equals(kvp[0].Trim(), "name", StringComparison.OrdinalIgnoreCase)) { identifiers.Add(string.Join(":", kvp.Skip(1))); } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs index c97a615ab0..36a234d907 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs @@ -8,9 +8,22 @@ namespace Silk.NET.SilkTouch { public class Diagnostics { - public static DiagnosticDescriptor MultipleConfigFiles { get; } = new + public static DiagnosticDescriptor GeneralError { get; } = new ( id: "ST0005", + title: "General SilkTouch Error", + messageFormat: "SilkTouch failed to execute due to a general error: \"{0}\"", + category: "SilkTouch", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: null, + helpLinkUri: null, + customTags: WellKnownDiagnosticTags.AnalyzerException + ); + + public static DiagnosticDescriptor MultipleConfigFiles { get; } = new + ( + id: "ST0006", title: "Multiple Configuration Files Detected", messageFormat: "Multiple configuration files detected. Using \"{0}\", to use \"{1}\" instead configure " + $"the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig option.", @@ -24,7 +37,7 @@ public class Diagnostics public static DiagnosticDescriptor NoConfigFile { get; } = new ( - id: "ST0006", + id: "ST0007", title: "No Configuration File", messageFormat: "No configuration file, SilkTouch will not run. To configure a path to a SilkTouch JSON " + $"Configuration file, use the \"{Constants.ConfigFileEditorconfigOption}\" editorconfig " + @@ -39,7 +52,7 @@ public class Diagnostics public static DiagnosticDescriptor NoAssemblyName { get; } = new ( - id: "ST0007", + id: "ST0008", title: "Couldn't Determine Assembly Name", messageFormat: "Couldn't determine \"AssemblyName\", SilkTouch will not run.", category: "SilkTouch", @@ -52,10 +65,10 @@ public class Diagnostics public static DiagnosticDescriptor NoLibraryName { get; } = new ( - id: "ST0008", + id: "ST0009", title: "No Library Name", messageFormat: "Specify at least one library name " + - "(\"projects\" > \"{0}\" > \"scraper\" > \"libraryNames\")", + "(\"scraper\" > \"jobs\" > [{0}] > \"libraryNames\": [\"MyLibrary.dll\"])", category: "SilkTouch", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, @@ -66,12 +79,12 @@ public class Diagnostics public static DiagnosticDescriptor NoWindowsSdk { get; } = new ( - id: "ST0009", + id: "ST0010", title: "Windows-Specific SDK Not Found", messageFormat: !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Include \"{0}\" could not be resolved, generation may fail. Consider " + "adding a skipIf condition to prevent generation on non-Windows platforms " + - "(\"projects\" > \"{1}\" > \"skipIf\": [\"linux\", \"macos\"])" + "(\"cliSkipIf\": [\"linux\", \"macos\"])" : "Include \"{0}\" could not be resolved, generation may fail.", category: "SilkTouch", defaultSeverity: DiagnosticSeverity.Warning, @@ -83,10 +96,10 @@ public class Diagnostics public static DiagnosticDescriptor NoHeaderText { get; } = new ( - id: "ST0010", + id: "ST0011", title: "No Input Header Text", - messageFormat: "Input C/C++ header source code is required to use SilkTouch Scraper. (\"projects\" > " + - "\"{0}\" > \"scraper\" > \"headerText\": [\"#include \\\"example.h\\\"\"]", + messageFormat: "Input C/C++ header source code is required to use SilkTouch Scraper. " + + "(\"scraper\" > \"jobs\" > [{0}] > \"headerText\": [\"#include \\\"example.h\\\"\"])", category: "SilkTouch", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, @@ -95,20 +108,6 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); - public static DiagnosticDescriptor NoProjectConfigInFile { get; } = new - ( - id: "ST0011", - title: "No Configuration For This Project", - messageFormat: "\"{0}\" does not contain any configuration for project \"{1}\", SilkTouch will not run " + - "(\"projects\" > \"{1}\": {{...}})", - category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: false, - description: null, - helpLinkUri: null, - customTags: WellKnownDiagnosticTags.AnalyzerException - ); - public static DiagnosticDescriptor ClangSharpNonZeroExitCode { get; } = new ( id: "ST0012", @@ -134,18 +133,5 @@ public class Diagnostics helpLinkUri: null, customTags: WellKnownDiagnosticTags.AnalyzerException ); - - public static DiagnosticDescriptor GeneralError { get; } = new - ( - id: "ST0014", - title: "General SilkTouch Error", - messageFormat: "SilkTouch failed to execute due to a general error: \"{0}\"", - category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: null, - helpLinkUri: null, - customTags: WellKnownDiagnosticTags.AnalyzerException - ); } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs index c49a01e8d3..233fcdc724 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs @@ -11,7 +11,7 @@ namespace Silk.NET.SilkTouch.Generation public sealed record SilkTouchContext ( string AssemblyName, - IEnumerable SyntaxTrees, + Compilation Compilation, ProjectConfiguration Configuration, GlobalConfiguration? GlobalConfiguration, string BaseDirectory diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs index a235aa55e8..ea555fc255 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs @@ -22,10 +22,9 @@ public sealed class SilkTouchGenerator public bool IsActive { get; private set; } public Compilation? Compilation { get; private set; } public string? AssemblyName { get; private set; } - public RootConfiguration? AllConfiguration { get; private set; } + public GlobalConfiguration? GlobalConfiguration { get; private set; } public ProjectConfiguration? ThisConfiguration { get; private set; } public string? BaseDirectory { get; private set; } - public ImmutableArray? SyntaxTrees { get; private set; } // Public Events public event Action? DiagnosticRaised; @@ -40,12 +39,13 @@ public bool Begin string? assemblyName = null ) { - var loadDiag = Config.Load + var configLoadSuccess = Config.TryLoad ( editorConfig, additionalFiles, - out var config, - out var usedText + out var projectConfig, + out var usedText, + out var loadDiag ); if (loadDiag is not null) @@ -54,39 +54,19 @@ out var usedText return false; } - assemblyName ??= compilation?.AssemblyName; - if (assemblyName is null) + if (!configLoadSuccess) { - DiagnosticRaised?.Invoke(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); return false; } - // prepare our context data - ProjectConfiguration? projectConfig = null; - var (_, projects) = config!; - if ((!projects?.TryGetValue(assemblyName, out projectConfig) ?? false) || - projectConfig is null) + assemblyName ??= compilation?.AssemblyName; + if (assemblyName is null) { - Log.Trace - ( - $"No project-specific config for \"{assemblyName}\" found - this is treated as \"don't run " + - "SilkTouch\"" - ); - - DiagnosticRaised?.Invoke - ( - Diagnostic.Create - ( - Diagnostics.NoProjectConfigInFile, - Location.None, - usedText!.Path, - assemblyName - ) - ); - + DiagnosticRaised?.Invoke(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); return false; } + // prepare our context data if (compilation is null) { // we can't proceed further without a compilation. @@ -94,7 +74,6 @@ out var usedText return false; } - var syntaxTrees = compilation.SyntaxTrees.OfType().ToImmutableArray(); var baseDir = Path.GetDirectoryName(usedText!.Path); if (baseDir is null) { @@ -114,10 +93,9 @@ out var usedText IsActive = true; Compilation = compilation; AssemblyName = assemblyName; - AllConfiguration = config; + GlobalConfiguration = projectConfig?.GetGlobalConfiguration(baseDir); ThisConfiguration = projectConfig; BaseDirectory = baseDir; - SyntaxTrees = syntaxTrees; return true; } @@ -140,10 +118,9 @@ public void End() IsActive = false; Compilation = null; AssemblyName = null; - AllConfiguration = null; + GlobalConfiguration = null; ThisConfiguration = null; BaseDirectory = null; - SyntaxTrees = null; } } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj index 28d9c3dad3..5579cdc72d 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj +++ b/src/generators/Silk.NET.SilkTouch.Common/Silk.NET.SilkTouch.Common.csproj @@ -2,7 +2,6 @@ netstandard2.0 - enable Silk.NET.SilkTouch true diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs index ebf6bb1f67..45308bfb7f 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs @@ -12,7 +12,7 @@ public static bool RunEmitter(this SilkTouchGenerator generator) { if (!generator.IsActive || generator.AssemblyName is null || - generator.SyntaxTrees is null || + generator.Compilation is null || generator.ThisConfiguration is null || generator.BaseDirectory is null) { @@ -33,9 +33,9 @@ generator.ThisConfiguration is null || var ctx = new SilkTouchContext ( generator.AssemblyName, - generator.SyntaxTrees, + generator.Compilation, generator.ThisConfiguration!, - generator.AllConfiguration?.Global, + generator.GlobalConfiguration, generator.BaseDirectory ); diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj b/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj index aa99722b63..2df0ad1f71 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj +++ b/src/generators/Silk.NET.SilkTouch.Emitter/Silk.NET.SilkTouch.Emitter.csproj @@ -2,7 +2,6 @@ netstandard2.0 - enable diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs index d434ac7473..1fdbad7029 100644 --- a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs @@ -12,7 +12,7 @@ public static bool RunOverloader(this SilkTouchGenerator generator) { if (!generator.IsActive || generator.AssemblyName is null || - generator.SyntaxTrees is null || + generator.Compilation is null || generator.ThisConfiguration is null || generator.BaseDirectory is null) { @@ -33,9 +33,9 @@ generator.ThisConfiguration is null || var ctx = new SilkTouchContext ( generator.AssemblyName, - generator.SyntaxTrees, + generator.Compilation, generator.ThisConfiguration!, - generator.AllConfiguration?.Global, + generator.GlobalConfiguration, generator.BaseDirectory ); diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj b/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj index aa99722b63..2df0ad1f71 100644 --- a/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj +++ b/src/generators/Silk.NET.SilkTouch.Overloader/Silk.NET.SilkTouch.Overloader.csproj @@ -2,7 +2,6 @@ netstandard2.0 - enable diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj b/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj index 326d210a51..621aa85eb3 100644 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/Silk.NET.SilkTouch.Roslyn.csproj @@ -2,7 +2,6 @@ netstandard2.0 - enable diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs index 6f1849b5eb..6c88707020 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs @@ -10,12 +10,12 @@ namespace Silk.NET.SilkTouch.Scraper { public static class ScraperGenerationExtensions { - public static async ValueTask RunScraperAsync(this SilkTouchGenerator generator) + public static async Task RunScraperAsync(this SilkTouchGenerator generator) where T : ISubagent, new() { if (!generator.IsActive || generator.AssemblyName is null || - generator.SyntaxTrees is null || + generator.Compilation is null || generator.ThisConfiguration is null || generator.BaseDirectory is null) { @@ -35,9 +35,9 @@ generator.ThisConfiguration is null || var ctx = new SilkTouchContext ( generator.AssemblyName, - generator.SyntaxTrees, + generator.Compilation, generator.ThisConfiguration!, - generator.AllConfiguration?.Global, + generator.GlobalConfiguration, generator.BaseDirectory ); diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index 87764dad9c..eb7e3acba2 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using ClangSharp; using Microsoft.CodeAnalysis; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; using Silk.NET.SilkTouch.Configuration; using Silk.NET.SilkTouch.Generation; using Silk.NET.SilkTouch.Scraper.Subagent; @@ -19,43 +21,61 @@ public static class ScraperGenerator public const FormFactors DefaultFormFactors = FormFactors.CLI; public static async Task RunAsync(SilkTouchContext ctx) where T:ISubagent, new() { - Log.Information("Scraper started."); var subagentSpawner = new T(); + await Parallel.ForEachAsync + ( + ctx.Configuration.Scraper?.Jobs?.Select((x, i) => (x, i)) + ?? Enumerable.Empty<(ScraperJobConfiguration, int)>(), + async (x, _) => await RunAsync(ctx, subagentSpawner, x.x, x.i) + ); + } + + public static async Task RunAsync + ( + SilkTouchContext ctx, + ISubagent subagent, + ScraperJobConfiguration job, + int jobNumber + ) + { + // initial validation var error = false; - var libraryNames = ctx.Configuration.Scraper?.LibraryNames; + var libraryNames = job.LibraryNames; if ((libraryNames?.Length ?? 0) == 0) { - ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoLibraryName, Location.None, ctx.AssemblyName)); + ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoLibraryName, Location.None, jobNumber)); error = true; } - if (ctx.Configuration.Scraper?.HeaderText is null) + if (job.HeaderText is null) { - ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoHeaderText, Location.None, ctx.AssemblyName)); + ctx.EmitDiagnostic(Diagnostic.Create(Diagnostics.NoHeaderText, Location.None, jobNumber)); error = true; } + // if the config is bad, quit now. if (error) { return; } + // write some data to the disk for the subagent to see var workFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); var @in = Path.Combine(workFolder, "in.h"); var @out = Path.Combine(workFolder, "out.xml"); string? licenseHeaderFile = null; Directory.CreateDirectory(workFolder); - File.WriteAllLines(@in, ctx.Configuration.Scraper?.HeaderText ?? Array.Empty()); - + File.WriteAllLines(@in, job.HeaderText ?? Array.Empty()); if (ctx.GlobalConfiguration?.FileHeaderLines is not null) { licenseHeaderFile = Path.Combine(workFolder, "licenseHeader.txt"); await File.WriteAllTextAsync(licenseHeaderFile, licenseHeaderFile); } + // format our includes. this allows both relative paths like "../" which resolve relative to the + // "base directory" i.e. the directory in which the config JSON is stored, and SDK names like "win-sdk" VisualStudioResolver.TryGetVisualStudioInfo(out var vs); - - var includes = ctx.Configuration.Scraper?.IncludeDirectories?.ToList() ?? new(); + var includes = job.IncludeDirectories?.ToList() ?? new(); for (var i = 0; i < includes.Count; i++) { var include = includes[i]; @@ -66,7 +86,7 @@ public static class ScraperGenerator { ctx.EmitDiagnostic ( - Diagnostic.Create(Diagnostics.NoWindowsSdk, Location.None, "win-sdk", ctx.AssemblyName) + Diagnostic.Create(Diagnostics.NoWindowsSdk, Location.None, "win-sdk", jobNumber) ); continue; @@ -74,36 +94,59 @@ public static class ScraperGenerator includes.InsertRange(i, vs.UcrtIncludes); } + else + { + includes[i] = Path.GetFullPath(include, ctx.BaseDirectory); + } } + + // format our traversals. this will allow globbing relative to the "base directory". + var globber = new Matcher(); + for (var i = 0; i < (job.Traverse?.Length ?? 0); i++) + { + var traversal = job.Traverse![i]; + if (traversal[0] == '!') + { + globber.AddExclude(traversal[1..]); + } + else + { + globber.AddInclude(traversal); + } + } + + var traversals = globber.Execute(new DirectoryInfoWrapper(new(ctx.BaseDirectory))).Files + .Select(x => x.Path) + .ToArray(); - Log.Debug($"Using work folder \"{workFolder}\""); - Log.Information($"Starting ClangSharp subagent for \"{ctx.AssemblyName}\"..."); + Log.Debug($"Using work folder \"{workFolder}\" job {jobNumber}"); + Log.Information($"Starting ClangSharp subagent for \"{ctx.AssemblyName}\" job {jobNumber}..."); var options = new SubagentOptions ( @in, "__SILKTOUCH", // TODO replace this in transformation, our library path logic will be more lenient - ctx.Configuration.Scraper?.Namespace ?? ctx.AssemblyName, + job.Namespace ?? ctx.AssemblyName, @out, workFolder, includes.ToArray(), PInvokeGeneratorOutputMode.Xml, SubagentUtils.GetOptions ( - ctx.Configuration.Scraper?.Exclude?.Hints ?? ExclusionHint.None, - ctx.Configuration.Scraper?.UnixMode ?? !OperatingSystem.IsWindows() + job.Exclude?.Hints ?? ExclusionHint.None, + job.UnixMode ?? !OperatingSystem.IsWindows() ), - ctx.Configuration.Scraper?.Language ?? "c++", - ctx.Configuration.Scraper?.Standard ?? "c++17", - ctx.Configuration.Scraper?.AdditionalClangArguments, - ctx.Configuration.Scraper?.DefineMacros, - ctx.Configuration.Scraper?.Exclude?.Identifiers, + job.Language ?? "c++", + job.Standard ?? "c++17", + job.AdditionalClangArguments, + job.DefineMacros, + job.Exclude?.Identifiers, licenseHeaderFile, - ctx.Configuration.Scraper?.ClassName ?? "Interop", - ctx.Configuration.Scraper?.MethodPrefixToStrip, - await OpenRemappingFilesAsync(ctx.Configuration.Scraper?.RemappingFiles, ctx.BaseDirectory), - ctx.Configuration.Scraper?.Traverse, + job.ClassName ?? "Interop", + job.MethodPrefixToStrip, + await OpenRemappingFilesAsync(job.RemappingFiles, ctx.BaseDirectory), + traversals, WithAttributes: null, // will be done by mods TODO do attributes in the mods - ctx.Configuration.Scraper?.CallingConventions, + job.CallingConventions, WithLibraryPaths: null, // again, library path stuff is done in transformation WithSetLastErrors: null, // the current design of SilkTouch doesn't support SetLastError null, // currently no need to support enum type overrides @@ -111,7 +154,7 @@ await OpenRemappingFilesAsync(ctx.Configuration.Scraper?.RemappingFiles, ctx.Bas ); var clangSharpErrors = new List(); - var exitCode = await subagentSpawner.RunClangSharpAsync(options, clangSharpErrors); + var exitCode = await subagent.RunClangSharpAsync(options, clangSharpErrors); if (exitCode != 0) { diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj index f8bc245593..b95abba7c0 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -1,13 +1,13 @@ - net5.0 - enable + net6.0 + diff --git a/src/generators/SilkTouch/ClangSharpHandoff.cs b/src/generators/SilkTouch/ClangSharpHandoff.cs index 7c6204cb63..959d7fa17a 100644 --- a/src/generators/SilkTouch/ClangSharpHandoff.cs +++ b/src/generators/SilkTouch/ClangSharpHandoff.cs @@ -69,7 +69,7 @@ public static int RunClangSharp(string[] args) using var pinvokeGenerator = new PInvokeGenerator(config); var translationUnitError = CXTranslationUnit.TryParse ( - pinvokeGenerator.IndexHandle, options.LicenseHeaderFile, finishedCommandLineArgs, + pinvokeGenerator.IndexHandle, options.HeaderFile, finishedCommandLineArgs, Array.Empty(), translationFlags, out var handle ); var skipProcessing = false; @@ -117,7 +117,7 @@ public static int RunClangSharp(string[] args) Console.WriteLine("I:Processing..."); pinvokeGenerator.GenerateBindings - (translationUnit, options.LicenseHeaderFile, finishedCommandLineArgs, translationFlags); + (translationUnit, options.HeaderFile, finishedCommandLineArgs, translationFlags); } catch (Exception e) { diff --git a/src/generators/SilkTouch/ClangSharpSubagent.cs b/src/generators/SilkTouch/ClangSharpSubagent.cs index 823ea1a181..a6495a8150 100644 --- a/src/generators/SilkTouch/ClangSharpSubagent.cs +++ b/src/generators/SilkTouch/ClangSharpSubagent.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -22,10 +23,17 @@ public async Task RunClangSharpAsync(SubagentOptions opts, List? er { // get the command line arguments this process was started with // using ArraySegment (lesser span) here instead of Span because it's enumerable. - ArraySegment args = Environment.GetCommandLineArgs(); + var execPath = typeof(ClangSharpHandoff).Assembly.Location; + string candidate; - // trim off the arguments *we* received - args = args[..^Program.Args.Length]; + // use the executable (.exe on windows, no extension on unix) if available, otherwise just launch dotnet + execPath = Path.GetExtension(execPath) != ".dll" + ? execPath + : File.Exists(candidate = Path.GetFileNameWithoutExtension(execPath) + ".exe") + ? candidate + : File.Exists(candidate = candidate[..^4]) + ? candidate + : "dotnet"; // serialize the options and escape the quotes to send on the command line. var optsStr = JsonSerializer.Serialize(opts).Replace("\"", "\\\""); @@ -35,8 +43,10 @@ public async Task RunClangSharpAsync(SubagentOptions opts, List? er { StartInfo = new ( - args[0], - $"\"{string.Join("\" \"", args[1..].Select(x => x.Replace("\"", "\\\"")))}\" \"{optsStr}\"" + execPath, + execPath == "dotnet" + ? $"\"{typeof(ClangSharpHandoff).Assembly.Location}\" clangsharp \"{optsStr}\"" + : $"clangsharp \"{optsStr}\"" ) { WorkingDirectory = Environment.CurrentDirectory, @@ -45,6 +55,17 @@ public async Task RunClangSharpAsync(SubagentOptions opts, List? er CreateNoWindow = true } }; + + // get the env vars of a developer command prompt if we can + if (VisualStudioResolver.TryGetVisualStudioInfo(out var vsInfo)) + { + foreach (var (k, v) in vsInfo.Variables) + { + proc.StartInfo.Environment[k] = v; + } + } + + Log.Trace($"Running command \"{proc.StartInfo.FileName}\" {proc.StartInfo.Arguments}"); // run the subprocess. if (!proc.Start()) diff --git a/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj b/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj index 58ef59a24a..4f444d8c8b 100644 --- a/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj +++ b/src/libraries/Silk.NET.Core/Silk.NET.Core.csproj @@ -2,7 +2,6 @@ net6.0 - enable diff --git a/src/submodules/Vulkan-Headers b/src/submodules/Vulkan-Headers new file mode 160000 index 0000000000..9fe958cdab --- /dev/null +++ b/src/submodules/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit 9fe958cdabcaf87650a4517b27df1ec2034d051f From b0e80abb00920b0f842ec3c619b6bb1a5cea7e78 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Sat, 7 Aug 2021 17:47:22 +0100 Subject: [PATCH 07/10] More fixes --- .../Configuration/Config.cs | 24 +++++++++++++++---- .../Generation/SilkTouchGenerator.cs | 14 +++-------- .../ScraperGenerator.cs | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index aedcea6bce..2af40f41ce 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -16,6 +16,10 @@ namespace Silk.NET.SilkTouch.Configuration { + /// + /// A helper class for loading and saving configuration files. Basically just a thin wrapper around + /// to make your code neater. + /// public static class Config { /// @@ -28,13 +32,24 @@ public static ProjectConfiguration Load(string json) => JsonSerializer.Deserialize(json) ?? throw new DataException("JSON deserialization of SilkTouch Configuration yielded null."); + /// + /// Attempts to load a configuration file from Roslyn AdditionalFiles. + /// + /// The editorconfig options provider. + /// The AdditionalFiles. + /// The project-specific SilkTouch configuration loaded, or null if none loaded. + /// The used to load the config. + /// + /// An action which, when called, handles diagnostics generated as part of loading. + /// + /// Whether the overall configuration load was successful or not. public static bool TryLoad ( AnalyzerConfigOptionsProvider provider, ImmutableArray additionalFiles, out ProjectConfiguration? config, out AdditionalText? usedText, - out Diagnostic? diagnostic + Action? reportDiagnostic ) { // Get the config file name. Uses silktouch.json unless overridden in .editorconfig. @@ -58,13 +73,13 @@ out Diagnostic? diagnostic { Log.Debug($"We've already found \"{usedText.Path}\" though - using that instead!"); config = null; - diagnostic = Diagnostic.Create + reportDiagnostic?.Invoke(Diagnostic.Create ( Diagnostics.MultipleConfigFiles, Location.Create(additionalFile.Path, TextSpan.FromBounds(0, 0), default), usedText.Path, additionalFile.Path - ); + )); continue; } @@ -78,13 +93,12 @@ out Diagnostic? diagnostic Log.Debug("No config."); config = null; usedText = null; - diagnostic = Diagnostic.Create(Diagnostics.NoConfigFile, Location.None); + reportDiagnostic?.Invoke(Diagnostic.Create(Diagnostics.NoConfigFile, Location.None)); return false; } Log.Debug("Good config."); config = Load(File.ReadAllText(usedText.Path)); // was gonna use usedText.GetText() until I saw their code. - diagnostic = null; return true; } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs index ea555fc255..0090612146 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs @@ -39,22 +39,14 @@ public bool Begin string? assemblyName = null ) { - var configLoadSuccess = Config.TryLoad + if (!Config.TryLoad ( editorConfig, additionalFiles, out var projectConfig, out var usedText, - out var loadDiag - ); - - if (loadDiag is not null) - { - DiagnosticRaised?.Invoke(loadDiag); - return false; - } - - if (!configLoadSuccess) + DiagnosticRaised + )) { return false; } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index eb7e3acba2..b9022c79f3 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -69,7 +69,7 @@ int jobNumber if (ctx.GlobalConfiguration?.FileHeaderLines is not null) { licenseHeaderFile = Path.Combine(workFolder, "licenseHeader.txt"); - await File.WriteAllTextAsync(licenseHeaderFile, licenseHeaderFile); + await File.WriteAllLinesAsync(licenseHeaderFile, ctx.GlobalConfiguration.FileHeaderLines); } // format our includes. this allows both relative paths like "../" which resolve relative to the From 8b20fbfbd9ebc921b97ced435a17a8f498b749c7 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Sat, 7 Aug 2021 23:23:14 +0100 Subject: [PATCH 08/10] XML is being generated, ClangSharp bugs to fix uncovered. --- .../Json/AllowSeparateFileJsonConverter.cs | 10 ---------- .../ScraperGenerator.cs | 8 +++++--- .../Silk.NET.SilkTouch.Scraper.csproj | 2 +- .../Subagent/SubagentOptions.cs | 3 ++- .../Subagent/SubagentUtils.cs | 2 ++ .../Subagent/VisualStudioResolver.cs | 9 ++++++--- src/generators/SilkTouch/ClangSharpHandoff.cs | 10 +++++++++- src/generators/SilkTouch/ClangSharpSubagent.cs | 2 +- src/generators/SilkTouch/Program.cs | 15 +++++++++++++-- src/generators/SilkTouch/SilkTouch.csproj | 6 ++++-- 10 files changed, 43 insertions(+), 24 deletions(-) delete mode 100644 src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs deleted file mode 100644 index 13d84c7bd2..0000000000 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/AllowSeparateFileJsonConverter.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Silk.NET.SilkTouch.Configuration.Json -{ - public class AllowSeparateFileJsonConverter - { - - } -} diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index b9022c79f3..45adffead1 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; @@ -116,7 +117,7 @@ int jobNumber } var traversals = globber.Execute(new DirectoryInfoWrapper(new(ctx.BaseDirectory))).Files - .Select(x => x.Path) + .Select(x => Path.GetFullPath(x.Path.Replace('\\', '/'), ctx.BaseDirectory)) .ToArray(); Log.Debug($"Using work folder \"{workFolder}\" job {jobNumber}"); @@ -124,7 +125,7 @@ int jobNumber var options = new SubagentOptions ( @in, - "__SILKTOUCH", // TODO replace this in transformation, our library path logic will be more lenient + "__SILKTOUCH", // TODO replace this in transformation job.Namespace ?? ctx.AssemblyName, @out, workFolder, @@ -150,7 +151,8 @@ await OpenRemappingFilesAsync(job.RemappingFiles, ctx.BaseDirectory), WithLibraryPaths: null, // again, library path stuff is done in transformation WithSetLastErrors: null, // the current design of SilkTouch doesn't support SetLastError null, // currently no need to support enum type overrides - null // the scraper will add predefined using directives during transformation + null, // the scraper will add predefined using directives during transformation + Debugger.IsAttached ); var clangSharpErrors = new List(); diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj index b95abba7c0..469804b840 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Silk.NET.SilkTouch.Scraper.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs index 463f68bf68..0e7f016714 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs @@ -92,7 +92,8 @@ public record SubagentOptions IReadOnlyDictionary? WithLibraryPaths = null, string[]? WithSetLastErrors = null, IReadOnlyDictionary? WithTypes = null, - IReadOnlyDictionary>? WithUsings = null + IReadOnlyDictionary>? WithUsings = null, + bool DebugAttach = false ) { /// diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs index 53ac3220c6..34d44ab60c 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs @@ -84,6 +84,8 @@ public static PInvokeGeneratorConfigurationOptions GetOptions(ExclusionHint hint opts |= PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; } + opts |= PInvokeGeneratorConfigurationOptions.LogExclusions; + opts |= PInvokeGeneratorConfigurationOptions.LogVisitedFiles; return opts; } } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs index c1ac95f34a..bad4420a19 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs @@ -38,9 +38,11 @@ public static class VisualStudioResolver "Include/{0}/ucrt", "Include/{0}/shared" }; - + private static VisualStudioInfo? _cachedInfo; private static VisualStudioInstance? _cachedInstance; + + private static bool _vsInfoKnownError; public static bool TryGetMSBuildInfo ( @@ -79,10 +81,10 @@ public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioIn } // if we've cached some info - if (_cachedInfo is not null) + if (_cachedInfo is not null || _vsInfoKnownError) { info = _cachedInfo; - return true; + return !_vsInfoKnownError; } // use MSBuildLocator to find Visual Studio instances @@ -171,6 +173,7 @@ public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioIn ); info = null; + _vsInfoKnownError = true; return false; } } diff --git a/src/generators/SilkTouch/ClangSharpHandoff.cs b/src/generators/SilkTouch/ClangSharpHandoff.cs index 959d7fa17a..c7d5521d7e 100644 --- a/src/generators/SilkTouch/ClangSharpHandoff.cs +++ b/src/generators/SilkTouch/ClangSharpHandoff.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json; @@ -29,6 +30,13 @@ public static int RunClangSharp(string[] args) return (int) ExitCodes.SubagentBadArgs; } + if (options.DebugAttach && Environment.GetEnvironmentVariable("SilkTouch_NoNestedDebug") != "1") + { + Debugger.Launch(); + } + + Console.WriteLine($"T:Clang Version: {clang.getClangVersion()}"); + // the code below is based off ClangSharp // Copyright (c) Microsoft and Contributors. All rights reserved. // Licensed under the University of Illinois/NCSA Open Source License. @@ -46,7 +54,7 @@ public static int RunClangSharp(string[] args) }; clangCommandLineArgs.AddRange(options.IncludeDirectories.Select(x => "--include-directory=" + x)); - clangCommandLineArgs.AddRange(options.IncludeDirectories.Select(x => "--define-macro=" + x)); + clangCommandLineArgs.AddRange(options.DefineMacros?.Select(x => "--define-macro=" + x) ?? Enumerable.Empty()); clangCommandLineArgs.AddRange(options.AdditionalArgs ?? Enumerable.Empty()); var translationFlags = CXTranslationUnit_Flags.CXTranslationUnit_None; diff --git a/src/generators/SilkTouch/ClangSharpSubagent.cs b/src/generators/SilkTouch/ClangSharpSubagent.cs index a6495a8150..b21a2cf2b8 100644 --- a/src/generators/SilkTouch/ClangSharpSubagent.cs +++ b/src/generators/SilkTouch/ClangSharpSubagent.cs @@ -57,7 +57,7 @@ public async Task RunClangSharpAsync(SubagentOptions opts, List? er }; // get the env vars of a developer command prompt if we can - if (VisualStudioResolver.TryGetVisualStudioInfo(out var vsInfo)) + if (!Program.NoEnvironmentEmulation && VisualStudioResolver.TryGetVisualStudioInfo(out var vsInfo)) { foreach (var (k, v) in vsInfo.Variables) { diff --git a/src/generators/SilkTouch/Program.cs b/src/generators/SilkTouch/Program.cs index 451d27a723..b6d2296df5 100644 --- a/src/generators/SilkTouch/Program.cs +++ b/src/generators/SilkTouch/Program.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using ClangSharp.Interop; using Microsoft.Build.Locator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.MSBuild; @@ -17,6 +18,7 @@ namespace SilkTouch internal class Program { public static string[] Args { get; internal set; } + public static bool NoEnvironmentEmulation { get; internal set; } static async Task Main(string[] args) { @@ -49,15 +51,24 @@ slnOrProjInCwd is null new[] { "--logging", "-l" }, () => Debugger.IsAttached ? LogMode.VVerbose : LogMode.Standard, "The debug logging verbosity." + ), + new Option + ( + new[] { "--no-environment-emulation", "-E" }, + () => false, + "Disables environment emulation for subagents i.e. does not run subagents in a Visual Studio " + + "Developer Command Prompt. Disabling this is useful if your Visual Studio instance does not have " + + $"clang {typeof(clang).Assembly.GetName().Version?.Major} installed." ) }; - rootCommand.Handler = CommandHandler.Create(RunSilkTouchAsync); + rootCommand.Handler = CommandHandler.Create(RunSilkTouchAsync); return await rootCommand.InvokeAsync(args); } - private static async Task RunSilkTouchAsync(FileInfo project, LogMode logging) + private static async Task RunSilkTouchAsync(FileInfo project, LogMode logging, bool noEnvironmentEmulation) { + NoEnvironmentEmulation = noEnvironmentEmulation; if (logging != LogMode.Silent) { Console.WriteLine diff --git a/src/generators/SilkTouch/SilkTouch.csproj b/src/generators/SilkTouch/SilkTouch.csproj index b585173f49..230a1a2705 100644 --- a/src/generators/SilkTouch/SilkTouch.csproj +++ b/src/generators/SilkTouch/SilkTouch.csproj @@ -3,8 +3,10 @@ Exe net6.0 - true - silktouch + + + $(NETCoreSdkRuntimeIdentifier) From 4dbdff536ca0d217ffe8ebb1de3e2a077f679434 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Mon, 9 Aug 2021 21:51:01 +0100 Subject: [PATCH 09/10] Fully XML documented, markdown pending --- src/Directory.Build.props | 1 + src/Directory.Build.targets | 8 ++- .../Silk.NET.Maths.Benchmarks.csproj | 1 + src/bindings/Silk.NET.Vulkan/Vk.cs | 3 + .../XmlBindings.cs | 3 + .../XmlBindingsVisitor.cs | 3 + .../Configuration/Config.cs | 55 ++++++++++++++- .../Configuration/ExclusionHint.cs | 69 ++++++++++++++++++- .../Configuration/FormFactors.cs | 15 ++++ .../Json/ExcludesJsonConverter.cs | 5 ++ .../Silk.NET.SilkTouch.Common/Constants.cs | 6 ++ .../Silk.NET.SilkTouch.Common/Diagnostics.cs | 49 +++++++++---- .../Generation/SilkTouchContext.cs | 24 ++++++- .../Generation/SilkTouchGenerator.cs | 65 +++++++++++++++-- .../EmitterGenerationExtensions.cs | 9 +++ .../EmitterGenerator.cs | 11 +++ .../OverloaderGenerationExtensions.cs | 9 +++ .../OverloaderGenerator.cs | 11 +++ .../SilkTouchSourceGenerator.cs | 5 ++ .../Mods/XmlMods.cs | 3 + .../ScraperGenerationExtensions.cs | 9 +++ .../ScraperGenerator.cs | 14 +++- .../Subagent/SubagentOptions.cs | 17 +++++ .../Subagent/SubagentUtils.cs | 6 +- .../Subagent/VisualStudioResolver.cs | 49 +++++++++++-- .../Subagent/VisualStudioVarPrint.cs | 1 + .../Transformation/XmlToCSharpTransformer.cs | 3 + src/generators/SilkTouch/GeneratorHandoff.cs | 40 +++++++++-- src/generators/SilkTouch/LogMode.cs | 19 +++++ src/generators/SilkTouch/Program.cs | 3 - src/generators/SilkTouch/SilkTouch.csproj | 4 +- .../Silk.NET.NUKE/Silk.NET.NUKE.csproj | 1 + 32 files changed, 475 insertions(+), 46 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 19c2f6358f..da35586ad8 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -17,6 +17,7 @@ enable + CS1591;CS1573 diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index c64374c37b..99a92d6fdf 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,4 +1,4 @@ - + PreserveNewest + + + + $(SilkDocumentationWarningsAsErrors);$(WarningsAsErrors) + + \ No newline at end of file diff --git a/src/benchmarks/Silk.NET.Maths.Benchmarks/Silk.NET.Maths.Benchmarks.csproj b/src/benchmarks/Silk.NET.Maths.Benchmarks/Silk.NET.Maths.Benchmarks.csproj index c2deaea80e..3181b94963 100644 --- a/src/benchmarks/Silk.NET.Maths.Benchmarks/Silk.NET.Maths.Benchmarks.csproj +++ b/src/benchmarks/Silk.NET.Maths.Benchmarks/Silk.NET.Maths.Benchmarks.csproj @@ -4,6 +4,7 @@ $(SilkTargetFramework) Exe 9.0 + true $(DefineConstants);MATHF diff --git a/src/bindings/Silk.NET.Vulkan/Vk.cs b/src/bindings/Silk.NET.Vulkan/Vk.cs index 9f6477811a..33fe52ebda 100644 --- a/src/bindings/Silk.NET.Vulkan/Vk.cs +++ b/src/bindings/Silk.NET.Vulkan/Vk.cs @@ -3,6 +3,9 @@ namespace Silk.NET.Vulkan { + /// + /// Vulkan bindings. + /// public partial class Vk { diff --git a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs index 7612f9c132..af62acb74a 100644 --- a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs +++ b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindings.cs @@ -3,6 +3,9 @@ namespace Silk.NET.SilkTouch.ClangSharp.Xml { + /// + /// The root of the XML bindings tree. + /// public class XmlBindings { diff --git a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs index 6c46203b86..4fac621b52 100644 --- a/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs +++ b/src/generators/Silk.NET.SilkTouch.ClangSharp.Xml/XmlBindingsVisitor.cs @@ -3,6 +3,9 @@ namespace Silk.NET.SilkTouch.ClangSharp.Xml { + /// + /// A visitor for the XML bindings tree. + /// public class XmlBindingsVisitor { diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs index 2af40f41ce..e5c1cab307 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Config.cs @@ -123,9 +123,17 @@ public record GlobalConfiguration /// /// The root project configuration structure. /// + /// + /// A path (relative to the JSON file) to the "global configuration". That is, a JSON file containing common + /// properties represented by . + /// /// SilkTouch Emitter specific configuration for this project. /// SilkTouch Overloader specific configuration for this project. /// SilkTouch Scraper specific configuration for this project. + /// + /// Contains named environmental conditions which cause SilkTouch to skip this project if any of them are + /// applicable when running via the command line e.g. don't generate a project if not running on Windows 10. + /// public record ProjectConfiguration ( [property: JsonPropertyName("globalFile")] string? GlobalConfigFile, @@ -183,11 +191,49 @@ public record OverloaderConfiguration /// /// SilkTouch Scraper specific configuration. /// + /// + /// The individual scraper jobs to run. Each job's resources (i.e. translation units) are completely isolated. + /// public record ScraperConfiguration ( [property: JsonPropertyName("jobs")] ScraperJobConfiguration[]? Jobs ); + /// + /// Represents the configuration for a single job + /// + /// + /// An array containing lines of a C/C++ header file. This is usually used to specify #includes which are + /// then traversed conditional to whether they were specified in . + /// + /// Extra directories to search in for #included header files. + /// + /// An array containing a list of C/C++ file paths or glob expressions to headers whose declarations should be + /// included in the bindings. + /// + /// + /// Whether to emit Unix-style bindings for code that is not entirely cross-platform (such as bitfield byte + /// allocation) + /// + /// A list of identifiers or constructs to exclude from the generated bindings. + /// + /// A list of XML "mods" to use, which modify the bindings generated by ClangSharp before they are converted to C# + /// code. + /// + /// Properties exposed to mods to customize their behaviour. + /// A list of dynamic-link library names to use for the bindings. + /// The namespace to use for generated C# code. + /// The Clang language to use to generate bindings. Defaults to C++. + /// The Clang language standard to use to generate bindings. Defaults to C++17. + /// Additional command line arguments to pass to the Clang compiler. + /// Additional macros to define ahead of translation unit creation. + /// The class name to use for generated C# code. + /// + /// The prefix to strip from the start of native APIs. This is at the subagent level, so an XML mod may be + /// preferred for more advanced name trimming/modification. + /// + /// A list of JSON files which contain native type remappings. + /// A map of function names to their calling conventions. public record ScraperJobConfiguration ( [property: JsonPropertyName("headerText")] string[]? HeaderText, @@ -196,7 +242,7 @@ public record ScraperJobConfiguration [property: JsonPropertyName("unixMode")] bool? UnixMode, [property: JsonPropertyName("exclude"), JsonConverter(typeof(ExcludesJsonConverter))] Excludes? Exclude, [property: JsonPropertyName("mods")] string[]? Mods, - [property: JsonPropertyName("modOptions")] Dictionary? Properties, + [property: JsonPropertyName("modOptions")] Dictionary? ModProperties, [property: JsonPropertyName("libraryNames")] string[]? LibraryNames, [property: JsonPropertyName("namespace")] string? Namespace, [property: JsonPropertyName("language")] string? Language, @@ -209,6 +255,13 @@ public record ScraperJobConfiguration [property: JsonPropertyName("conventions")] Dictionary? CallingConventions ); + /// + /// Describes native symbol exclusions. + /// + /// A list of symbols excluded by name. + /// + /// A list of symbols excluded by "hint" i.e. a specific kind of symbol excluded completely. + /// public record Excludes ( string[] Identifiers, diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs index 503d11ed47..c3081aeae3 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/ExclusionHint.cs @@ -2,25 +2,92 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime.CompilerServices; namespace Silk.NET.SilkTouch.Configuration { + /// + /// Represents an exclusion "hint" which tells the subagent to exclude a specific kind of API, construct, or other + /// element of the bindings process from the generated bindings. + /// [Flags] public enum ExclusionHint { + /// + /// Represents an empty hint. Does nothing. + /// None = 0, + + /// + /// If set, excludes anonymous field helper code by setting the ExcludeAnonymousFieldHelpers ClangSharp + /// command line option. + /// AnonymousFieldHelpers = 1 << 0, + + /// + /// If set, excludes COM proxies by setting the ExcludeComProxies ClangSharp command line option. + /// ComProxies = 1 << 1, + + /// + /// If set, excludes default remappings for well-known types, such as by setting the + /// NoDefaultRemappings ClangSharp command line option. + /// DefaultRemappings = 1 << 2, + + /// + /// If set, excludes empty records (structs) by setting the ExcludeEmptyRecords ClangSharp command line + /// option. + /// EmptyRecords = 1 << 3, + + /// + /// If set, excludes enum operator code by setting the ExcludeEnumOperators ClangSharp command line + /// option. + /// EnumOperators = 1 << 4, + + /// + /// If set, excludes converting functions with bodies to C# by setting the ExcludeFunctionsWithBody + /// ClangSharp command line option. + /// FunctionsWithBodies = 1 << 5, - UsingStaticForEnums = 1 << 6, + + /// + /// If set, excludes function which the ClangSharp subagent deems are safe to enable the + /// for by unsetting the + /// GenerateAggressiveInlining ClangSharp command line option. + /// AggressiveInlining = 1 << 7, + + /// + /// If set, excludes C# attribute representation of C++ attributes by unsetting the GenerateCppAttributes + /// ClangSharp command line option. + /// CppAttributes = 1 << 8, + + /// + /// If set, excludes bindings to macros by unsetting the GenerateMacroBindings ClangSharp command line + /// option. + /// MacroBindings = 1 << 9, + + /// + /// If set, excludes generating C# attributes representing native C++ class inheritance by unsetting the + /// GenerateNativeInheritanceAttribute ClangSharp command line option. + /// NativeInheritanceAttribute = 1 << 10, + + /// + /// If set, excludes generating bindings to C++ templates by unsetting the GenerateTemplateBindings + /// ClangSharp command line option. + /// TemplateBindings = 1 << 11, + + /// + /// If set, excludes generating C# attributes representing VTable indices for functions within a struct + /// representing a COM interface. + /// VTableIndexAttribute = 1 << 12 } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs index 0512159b19..b888d8a04d 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/FormFactors.cs @@ -5,11 +5,26 @@ namespace Silk.NET.SilkTouch.Configuration { + /// + /// A bitmask enum containing the different kind of "form factors" that SilkTouch supports. That is, the different + /// ways you can invoke SilkTouch. + /// [Flags] public enum FormFactors { + /// + /// Represents an empty bitmask. + /// None = 0, + + /// + /// Represents SilkTouch running in a Roslyn Source Generator (the "Roslyn" form factor) + /// Roslyn = 1 << 0, + + /// + /// Represents SilkTouch running via a standalone command line interface (the "CLI" form factor) + /// CLI = 1 << 1 } } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs index e0b0459692..ed8e8d92c6 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Configuration/Json/ExcludesJsonConverter.cs @@ -9,8 +9,12 @@ namespace Silk.NET.SilkTouch.Configuration.Json { + /// + /// A for converting a JSON string array to an record. + /// public class ExcludesJsonConverter : JsonConverter { + /// public override Excludes? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var list = JsonSerializer.Deserialize(ref reader); @@ -59,6 +63,7 @@ public class ExcludesJsonConverter : JsonConverter return new(identifiers.ToArray(), hints); } + /// public override void Write(Utf8JsonWriter writer, Excludes? value, JsonSerializerOptions options) { throw new NotImplementedException(); diff --git a/src/generators/Silk.NET.SilkTouch.Common/Constants.cs b/src/generators/Silk.NET.SilkTouch.Common/Constants.cs index d96ec1ea95..6b49fdbb33 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Constants.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Constants.cs @@ -3,8 +3,14 @@ namespace Silk.NET.SilkTouch { + /// + /// Encapsulates various common constants used throughout SilkTouch. + /// public static class Constants { + /// + /// The name of the editorconfig option which configures the name of the SilkTouch configuration file to use. + /// public const string ConfigFileEditorconfigOption = "silktouch_config_file"; internal static readonly char[] SemicolonDelimited = {';'}; } diff --git a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs index 36a234d907..59b7fd703f 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Diagnostics.cs @@ -3,11 +3,19 @@ using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; +using Silk.NET.SilkTouch.Configuration; namespace Silk.NET.SilkTouch { + /// + /// Encapsulates all diagnostics raised by first-party, out-of-the-box SilkTouch. + /// public class Diagnostics { + /// + /// An unknown or unspecific general error. This is only to be used in unforeseen, fringe circumstances. Any + /// common, valid, external, and/or controllable circumstances should have its own dedicated diagnostic. + /// public static DiagnosticDescriptor GeneralError { get; } = new ( id: "ST0005", @@ -21,6 +29,10 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); + /// + /// Raised when multiple configuration files which match the file name specified in the editorconfig are found + /// in a project. + /// public static DiagnosticDescriptor MultipleConfigFiles { get; } = new ( id: "ST0006", @@ -35,6 +47,10 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); + /// + /// Raised when no configuration files which match the file name specified in the editorconfig are found in a + /// project. + /// public static DiagnosticDescriptor NoConfigFile { get; } = new ( id: "ST0007", @@ -50,22 +66,13 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); - public static DiagnosticDescriptor NoAssemblyName { get; } = new - ( - id: "ST0008", - title: "Couldn't Determine Assembly Name", - messageFormat: "Couldn't determine \"AssemblyName\", SilkTouch will not run.", - category: "SilkTouch", - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: null, - helpLinkUri: null, - customTags: WellKnownDiagnosticTags.AnalyzerException - ); - + /// + /// Raised when no are specified in a + /// . + /// public static DiagnosticDescriptor NoLibraryName { get; } = new ( - id: "ST0009", + id: "ST0008", title: "No Library Name", messageFormat: "Specify at least one library name " + "(\"scraper\" > \"jobs\" > [{0}] > \"libraryNames\": [\"MyLibrary.dll\"])", @@ -77,6 +84,9 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); + /// + /// Raised when the CLI is unable to resolve the path to a Windows SDK installation. + /// public static DiagnosticDescriptor NoWindowsSdk { get; } = new ( id: "ST0010", @@ -94,6 +104,10 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); + /// + /// Raised when no is specified in a + /// . + /// public static DiagnosticDescriptor NoHeaderText { get; } = new ( id: "ST0011", @@ -108,6 +122,10 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); + /// + /// Raised when the Scraper's ClangSharp subagent exits with an abnormal/non-zero exit code. Usually this is + /// accompanied by one or more diagnostics. + /// public static DiagnosticDescriptor ClangSharpNonZeroExitCode { get; } = new ( id: "ST0012", @@ -121,6 +139,9 @@ public class Diagnostics customTags: WellKnownDiagnosticTags.AnalyzerException ); + /// + /// Raised when the Scraper's ClangSharp subagent raises an error. + /// public static DiagnosticDescriptor ClangSharpError { get; } = new ( id: "ST0013", diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs index 233fcdc724..ec580038fa 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchContext.cs @@ -3,11 +3,23 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Silk.NET.SilkTouch.Configuration; namespace Silk.NET.SilkTouch.Generation { + /// + /// Represents a generator stage's view of a C# project. + /// + /// The name of the assembly represented by the C# project. + /// The Roslyn compilation, containing all C# compiler info (such as syntax trees) + /// The project-specific configuration for this project. + /// + /// The global/common configuration referenced by the 's + /// . + /// + /// The "base directory", used as the root for all relative paths referenced elsewhere in the configuration. This is + /// usually the same folder in which the C# project file (csproj) is contained. + /// public sealed record SilkTouchContext ( string AssemblyName, @@ -22,9 +34,19 @@ string BaseDirectory internal List Diagnostics { get; } = new(); // Public Methods + /// + /// Registers generated output with the context to be added to the C# project, with a "file name hint" which may + /// or may not be used by the form factor to identify this generated file. + /// + /// The file name hint. + /// The generated file's content (i.e. C# code) public void EmitOutput(string fileNameHint, string content) => Outputs.Add((fileNameHint, content)); + /// + /// Raises a diagnostic with the context to be passed along to the user. + /// + /// The diagnostic to raise. public void EmitDiagnostic(Diagnostic diagnostic) => Diagnostics.Add(diagnostic); /// diff --git a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs index 0090612146..b7f88f0bce 100644 --- a/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Common/Generation/SilkTouchGenerator.cs @@ -4,33 +4,81 @@ using System; using System.Collections.Immutable; using System.IO; -using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Silk.NET.SilkTouch.Configuration; -using Ultz.Extensions.Logging; namespace Silk.NET.SilkTouch.Generation { + /// + /// Represents generic, form factor agnostic SilkTouch generator infrastructure. This aims to provide a common + /// interface by which all form factors can bootstrap the generation process. + /// public sealed class SilkTouchGenerator { + /// + /// Creates a for the given . + /// + /// The form factor this generator is running within. public SilkTouchGenerator(FormFactors formFactor) => FormFactor = formFactor; // Public Properties + /// + /// The form factor this generator is running within. + /// public FormFactors FormFactor { get; } + + /// + /// Whether the generator is "active" and is ready for generation to occur against it i.e. whether + /// has been called, but hasn't. + /// public bool IsActive { get; private set; } + + /// + /// The Roslyn for this C# project. + /// public Compilation? Compilation { get; private set; } + + /// + /// The assembly name for this C# project. + /// public string? AssemblyName { get; private set; } + + /// + /// The common/global configuration variables. + /// public GlobalConfiguration? GlobalConfiguration { get; private set; } + + /// + /// The project-specific configuration for this project. + /// public ProjectConfiguration? ThisConfiguration { get; private set; } + + /// + /// The "base directory" of this project, used as the root of all relative paths referenced in the + /// . + /// public string? BaseDirectory { get; private set; } // Public Events + /// + /// Raised when a diagnostic is raised. + /// public event Action? DiagnosticRaised; + + /// + /// Raise when generated output is added. + /// public event Action<(string FileNameHint, string Content)>? OutputGenerated; - // TODO remove duplicate code between SilkTouchSourceGenerator and GeneratorHandoff in this class + /// + /// Activates this generator, preparing it for receiving diagnostics and generated code. + /// + /// The compilation to use for generation. + /// The analyzer options (editorconfig) to use for generation. + /// The analyzer additional files to use for generation. + /// The assembly name of the project being generated. + /// Whether the operation was successful or not. public bool Begin ( Compilation? compilation, @@ -54,7 +102,7 @@ public bool Begin assemblyName ??= compilation?.AssemblyName; if (assemblyName is null) { - DiagnosticRaised?.Invoke(Diagnostic.Create(Diagnostics.NoAssemblyName, Location.None)); + DiagnosticRaised?.Invoke(Diagnostic.Create(Diagnostics.GeneralError, Location.None, "No AssemblyName")); return false; } @@ -91,6 +139,10 @@ public bool Begin return true; } + /// + /// After its use, copies the results of the and raises events accordingly. + /// + /// The context to ingest. public void IngestContext(SilkTouchContext ctx) { var (outputs, diagnostics) = ctx.GetResult(); @@ -105,6 +157,9 @@ public void IngestContext(SilkTouchContext ctx) } } + /// + /// Cleans this generator instance. + /// public void End() { IsActive = false; diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs index 45308bfb7f..a402ed2eab 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerationExtensions.cs @@ -6,8 +6,17 @@ namespace Silk.NET.SilkTouch.Emitter { + /// + /// Contains a helper method for launching the SilkTouch Overloader given a + /// instance. + /// public static class EmitterGenerationExtensions { + /// + /// Runs the emitter. + /// + /// The generator to generate into. + /// Whether the overloader was started successfully. public static bool RunEmitter(this SilkTouchGenerator generator) { if (!generator.IsActive || diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs index 8b6022f37b..6303fbfb92 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/EmitterGenerator.cs @@ -3,9 +3,20 @@ namespace Silk.NET.SilkTouch.Emitter { + /// + /// The SilkTouch Emitter. + /// public static class EmitterGenerator { + /// + /// The form factors which the Emitter runs in by default. + /// public const FormFactors DefaultFormFactors = FormFactors.Roslyn; + + /// + /// Runs the Emitter, generating from/into the given context. + /// + /// The context to use. public static void Run(SilkTouchContext ctx) { // TODO implement diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs index 1fdbad7029..21c67d6147 100644 --- a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerationExtensions.cs @@ -6,8 +6,17 @@ namespace Silk.NET.SilkTouch.Overloader { + /// + /// Contains a helper method for launching the SilkTouch Overloader given a + /// instance. + /// public static class OverloaderGenerationExtensions { + /// + /// Runs the overloader. + /// + /// The generator to generate into. + /// Whether the overloader was started successfully. public static bool RunOverloader(this SilkTouchGenerator generator) { if (!generator.IsActive || diff --git a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs index cdad11d192..25133ab23f 100644 --- a/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Overloader/OverloaderGenerator.cs @@ -3,9 +3,20 @@ namespace Silk.NET.SilkTouch.Overloader { + /// + /// The SilkTouch Overloader. + /// public static class OverloaderGenerator { + /// + /// The form factors which the Overloader runs in by default. + /// public const FormFactors DefaultFormFactors = FormFactors.Roslyn; + + /// + /// Runs the overloader, generating from/into the given context. + /// + /// The context. public static void Run(SilkTouchContext ctx) { // TODO implement diff --git a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs index fb4b301c10..582456c572 100644 --- a/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Roslyn/SilkTouchSourceGenerator.cs @@ -11,13 +11,18 @@ namespace Silk.NET.SilkTouch.Roslyn { + /// + /// The Source Generator form factor for SilkTouch. + /// public class SilkTouchSourceGenerator : ISourceGenerator { + /// public void Initialize(GeneratorInitializationContext context) { // TODO do we need to do initialization downstream? } + /// public void Execute(GeneratorExecutionContext context) { // Prevent debug logs from being output by forcing the LoggerProvider to null - we don't want this in Roslyn diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs index 9fb87a7a79..5f50119afa 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Mods/XmlMods.cs @@ -3,6 +3,9 @@ namespace Silk.NET.SilkTouch.Scraper.Mods { + /// + /// Contains logic for applying XML modifications ("mods") to a parsed tree. + /// public class XmlMods { diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs index 6c88707020..1306d3740b 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerationExtensions.cs @@ -8,8 +8,17 @@ namespace Silk.NET.SilkTouch.Scraper { + /// + /// Contains a helper method for running the SilkTouch Scraper given a instnace. + /// public static class ScraperGenerationExtensions { + /// + /// Runs the SilkTouch Scraper. + /// + /// The generator instance to run the scraper on. + /// The type of the subagent spawner to use. + /// An asynchronous task encapsulating whether the generator was successfully started. public static async Task RunScraperAsync(this SilkTouchGenerator generator) where T : ISubagent, new() { diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs index 45adffead1..943b7df49a 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/ScraperGenerator.cs @@ -17,9 +17,21 @@ namespace Silk.NET.SilkTouch.Scraper { + /// + /// The SilkTouch Scraper. + /// public static class ScraperGenerator { + /// + /// The form factors that the SilkTouch Scraper runs in by default. + /// public const FormFactors DefaultFormFactors = FormFactors.CLI; + + /// + /// Runs the scraper. + /// + /// The SilkTouch Context to generate into. + /// The subagent spawner to use. public static async Task RunAsync(SilkTouchContext ctx) where T:ISubagent, new() { var subagentSpawner = new T(); @@ -31,7 +43,7 @@ await Parallel.ForEachAsync ); } - public static async Task RunAsync + private static async Task RunAsync ( SilkTouchContext ctx, ISubagent subagent, diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs index 0e7f016714..57c7bc4f22 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using ClangSharp; namespace Silk.NET.SilkTouch.Scraper.Subagent @@ -13,6 +14,7 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent /// The only reason this exists is because doesn't play nicely with JSON /// serialization, and we use JSON serialization to communicate the options to the subprocesses. /// + /// The C/C++ header file location to use as a source for bindings generation. /// /// The for ClangSharp to use in XML generation. /// @@ -25,12 +27,21 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent /// /// The for ClangSharp to use in XML generation. /// + /// The extra include directories to add to the Clang arguments. /// /// The for ClangSharp to use in XML generation. /// /// /// The for ClangSharp to use in XML generation. /// + /// The source language to use for clang compilation. Defaults to C++. + /// The source language standard to use for clang compilation. Defaults to C++17. + /// + /// The additional clang command line arguments to use in creating a translation unit. + /// + /// + /// Additional macros to define ahead of evaluation of the . + /// /// /// The for ClangSharp to use in XML generation. /// @@ -67,6 +78,12 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent /// /// The for ClangSharp to use in XML generation. /// + /// + /// Whether to make the launched subagent also launch the debugger. By default, this only happens if a + /// on the parent process but can be overridden by setting + /// the SilkTouch_NoNestedDebug environment variable to 1. Because this is a debug-only parameter, + /// it does not have a command line argument. + /// public record SubagentOptions ( string HeaderFile, diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs index 34d44ab60c..3288bee667 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/SubagentUtils.cs @@ -43,11 +43,6 @@ public static PInvokeGeneratorConfigurationOptions GetOptions(ExclusionHint hint opts |= PInvokeGeneratorConfigurationOptions.ExcludeFunctionsWithBody; } - if ((hints & ExclusionHint.UsingStaticForEnums) != 0) - { - opts |= PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums; - } - // If these hints are not defined, define the corresponding ClangSharp inclusion option. if ((hints & ExclusionHint.MacroBindings) == 0) { @@ -86,6 +81,7 @@ public static PInvokeGeneratorConfigurationOptions GetOptions(ExclusionHint hint opts |= PInvokeGeneratorConfigurationOptions.LogExclusions; opts |= PInvokeGeneratorConfigurationOptions.LogVisitedFiles; + opts |= PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums; return opts; } } diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs index bad4420a19..0a6e8a1036 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioResolver.cs @@ -11,6 +11,25 @@ namespace Silk.NET.SilkTouch.Scraper.Subagent { + /// + /// Encapsulations information regarding installed versions of Visual Studio. + /// + /// The name of this installation. + /// The base folder of this installation. + /// The path to the Universal Common Runtime SDK (Windows SDK). + /// The extra include directories to be added to include the Universal CRT. + /// + /// The version of the Universal Common Runtime. Typically synced with the Windows SDK version. + /// + /// The path to the folder containing the MSVC toolset. + /// + /// The extra include directories to be added to include the MSVC (standard C++) headers. + /// + /// The version of this installation. + /// + /// The environment variables captured within a Developer Command Prompt for this installation. + /// + /// The path to MSBuild for this installation. public record VisualStudioInfo ( string Name, @@ -25,14 +44,17 @@ public record VisualStudioInfo string MSBuildPath ); + /// + /// Contains logic for resolving for all Visual Studio installations. + /// public static class VisualStudioResolver { - internal const string MsvcInstallDirVar = "VCToolsInstallDir"; - internal static readonly string[] MsvcIncludeSubDirs = { "include" }; + private const string MsvcInstallDirVar = "VCToolsInstallDir"; + private static readonly string[] _msvcIncludeSubDirs = { "include" }; - internal const string WinSdkUcrtSdkDirVar = "UniversalCRTSdkDir"; - internal const string WinSdkUcrtVersionVar = "UCRTVersion"; - internal static readonly string[] WinSdkUcrtIncludeSubDirs = + private const string WinSdkUcrtSdkDirVar = "UniversalCRTSdkDir"; + private const string WinSdkUcrtVersionVar = "UCRTVersion"; + private static readonly string[] _winSdkUcrtIncludeSubDirs = { "Include/{0}/um", "Include/{0}/ucrt", @@ -44,6 +66,13 @@ public static class VisualStudioResolver private static bool _vsInfoKnownError; + /// + /// Gets information regarding the instance to use. + /// Unlike , this will return true even if the MSBuild located is a + /// .NET SDK MSBuild and not a desktop MSBuild instance. + /// + /// The resolved. + /// Whether the operation was successful. public static bool TryGetMSBuildInfo ( [NotNullWhen(true)] out VisualStudioInstance? instance @@ -71,6 +100,12 @@ public static bool TryGetMSBuildInfo return true; } + /// + /// Gets the for the most well-equipped installation of Visual Studio on this + /// system to handle Scraper workloads. + /// + /// The resolved info. + /// Whether the operation was successful. public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioInfo? info) { // if we're not on windows, this is never gonna work @@ -124,12 +159,12 @@ public static bool TryGetVisualStudioInfo([NotNullWhen(true)] out VisualStudioIn Log.Debug($"winSdkUcrtSdkDir = \"{winSdkUcrtSdkDir}\""); Log.Debug($"winSdkUcrtVersion = \"{winSdkUcrtVersion}\""); - var ucrtDirs = WinSdkUcrtIncludeSubDirs + var ucrtDirs = _winSdkUcrtIncludeSubDirs .Select(x => Path.Combine(winSdkUcrtSdkDir, string.Format(x, winSdkUcrtVersion))) .Where(Directory.Exists) .ToArray(); - var msvcDirs = MsvcIncludeSubDirs + var msvcDirs = _msvcIncludeSubDirs .Select(x => Path.Combine(msvcInstallDir, x)) .Where(Directory.Exists) .ToArray(); diff --git a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs index 0c9e9644ad..c7eaa5de73 100644 --- a/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs +++ b/src/generators/Silk.NET.SilkTouch.Scraper/Subagent/VisualStudioVarPrint.cs @@ -82,6 +82,7 @@ public static bool TryRun(string vsPath, [NotNullWhen(true)] out Dictionary + /// Contains logic for transforming XML bindings trees into C# code. + /// public class XmlToCSharpTransformer { diff --git a/src/generators/SilkTouch/GeneratorHandoff.cs b/src/generators/SilkTouch/GeneratorHandoff.cs index 225e38abb8..437771b6f8 100644 --- a/src/generators/SilkTouch/GeneratorHandoff.cs +++ b/src/generators/SilkTouch/GeneratorHandoff.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.MSBuild; @@ -10,12 +12,38 @@ using Silk.NET.SilkTouch.Generation; using Silk.NET.SilkTouch.Overloader; using Silk.NET.SilkTouch.Scraper; +using Silk.NET.SilkTouch.Scraper.Subagent; using Ultz.Extensions.Logging; namespace SilkTouch { internal static class GeneratorHandoff { + private static readonly HashSet ApplicableSkipIfs; + + static GeneratorHandoff() + { + ApplicableSkipIfs = new(StringComparer.OrdinalIgnoreCase) + { + // Windows-specific conditions + OperatingSystem.IsWindows() ? "win" : "!win", + OperatingSystem.IsWindowsVersionAtLeast(10) ? "win10" : "!win10", + + // Linux-specific conditions + OperatingSystem.IsLinux() ? "linux" : "!linux", + + // macOS-specific conditions - .NET 6 doesn't support anything less than Mojave + OperatingSystem.IsMacOS() ? "macos" : "!macos", + OperatingSystem.IsMacOSVersionAtLeast(10, 14) ? "macos-mojave" : "!macos-mojave", + OperatingSystem.IsMacOSVersionAtLeast(10, 15) ? "macos-catalina" : "!macos-catalina", + OperatingSystem.IsMacOSVersionAtLeast(11) ? "macos-big-sur" : "!macos-big-sur", + OperatingSystem.IsMacOSVersionAtLeast(12) ? "macos-monterey" : "!macos-monterey", + + // Others + VisualStudioResolver.TryGetVisualStudioInfo(out _) ? "vs" : "!vs" + }; + } + public static int ExitCode { get; private set; } public static async ValueTask HandleProjectAsync(MSBuildWorkspace workspace, Project project) @@ -24,11 +52,7 @@ public static async ValueTask HandleProjectAsync(MSBuildWorkspace workspace, Pro var generator = new SilkTouchGenerator(FormFactors.CLI); var raiseDiagnostic = LogDiagnostic(project.AssemblyName); generator.DiagnosticRaised += raiseDiagnostic; - generator.OutputGenerated += x => - { - // TODO implement this, we need to either add our files to the workspace or come up with some other hack - throw new NotImplementedException(); - }; + generator.OutputGenerated += _ => throw new NotImplementedException(); if (!generator.Begin ( @@ -43,6 +67,12 @@ await project.GetCompilationAsync(), return; } + if (generator.ThisConfiguration?.CommandLineSkipIf?.Any(x => ApplicableSkipIfs.Contains(x.Trim())) ?? false) + { + Log.Information($"Per the configuration, generation of \"{project.AssemblyName}\" has been skipped."); + return; + } + await generator.RunScraperAsync(); await Task.WhenAll(Task.Run(generator.RunEmitter), Task.Run(generator.RunOverloader)); generator.End(); diff --git a/src/generators/SilkTouch/LogMode.cs b/src/generators/SilkTouch/LogMode.cs index d5b6d9d3f9..010ed44754 100644 --- a/src/generators/SilkTouch/LogMode.cs +++ b/src/generators/SilkTouch/LogMode.cs @@ -3,11 +3,30 @@ namespace SilkTouch { + /// + /// The level of logging to use during generation. + /// public enum LogMode { + /// + /// Standard logging. Contains info throughout the generation process and warnings produced. + /// Standard, + + /// + /// Silent logging. Only logs if there's an error and suppresses the "logo". + /// Silent, + + /// + /// Verbose logging. Contains trace logs including suppressed diagnostics. Spammier. + /// Verbose, + + /// + /// Very verbose logging. Contains every log message ever possibly conceived. + /// Spammiest, far too spammy for most uses. + /// VVerbose } } diff --git a/src/generators/SilkTouch/Program.cs b/src/generators/SilkTouch/Program.cs index b6d2296df5..b1365001b1 100644 --- a/src/generators/SilkTouch/Program.cs +++ b/src/generators/SilkTouch/Program.cs @@ -17,12 +17,9 @@ namespace SilkTouch { internal class Program { - public static string[] Args { get; internal set; } public static bool NoEnvironmentEmulation { get; internal set; } - static async Task Main(string[] args) { - Args = args; if (args.Length > 0 && args[0].ToLower() == "clangsharp") { return ClangSharpHandoff.RunClangSharp(args); diff --git a/src/generators/SilkTouch/SilkTouch.csproj b/src/generators/SilkTouch/SilkTouch.csproj index 230a1a2705..f5b85e24ec 100644 --- a/src/generators/SilkTouch/SilkTouch.csproj +++ b/src/generators/SilkTouch/SilkTouch.csproj @@ -3,8 +3,8 @@ Exe net6.0 - + true + silktouch $(NETCoreSdkRuntimeIdentifier) diff --git a/src/infrastructure/Silk.NET.NUKE/Silk.NET.NUKE.csproj b/src/infrastructure/Silk.NET.NUKE/Silk.NET.NUKE.csproj index 8680f41e0f..d91a3f86d5 100644 --- a/src/infrastructure/Silk.NET.NUKE/Silk.NET.NUKE.csproj +++ b/src/infrastructure/Silk.NET.NUKE/Silk.NET.NUKE.csproj @@ -5,6 +5,7 @@ Exe CS0649;CS0169 + true ..\.. ..\.. preview From 7b22e998fed0e91e06ccf52c3ab0c901ace0fc09 Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Tue, 10 Aug 2021 19:28:23 +0100 Subject: [PATCH 10/10] Contributor documentation --- .../for-contributors/generators/README.md | 13 +++++++ .../for-contributors/generators/emitter.md | 3 ++ .../for-contributors/generators/overloader.md | 3 ++ .../for-contributors/generators/readme.md | 0 .../for-contributors/generators/scraper.md | 39 +++++++++++++++++++ .../generators/shared-infrastructure.md | 39 +++++++++++++++++++ src/generators/README.md | 37 +++++++++++++++++- 7 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 documentation/for-contributors/generators/README.md create mode 100644 documentation/for-contributors/generators/emitter.md create mode 100644 documentation/for-contributors/generators/overloader.md delete mode 100644 documentation/for-contributors/generators/readme.md create mode 100644 documentation/for-contributors/generators/scraper.md create mode 100644 documentation/for-contributors/generators/shared-infrastructure.md diff --git a/documentation/for-contributors/generators/README.md b/documentation/for-contributors/generators/README.md new file mode 100644 index 0000000000..710984539b --- /dev/null +++ b/documentation/for-contributors/generators/README.md @@ -0,0 +1,13 @@ +# SilkTouch + +SilkTouch is the Silk.NET project's C# native interoperability code generation stack. It contains useful generators which together generate the majority of the library's codebase. + +It does this by splitting the generation process into multiple phases, all anchored around the concept of a "shared infrastructure" - abstractions for SilkTouch generators which are agnostic of where they're running. Currently the only places SilkTouch can run is in a Roslyn Source Generator and using a `dotnet tool` Command Line Interface, but the idea is so long as no incompatibilities are introduced by each individual phase (such as being locked to .NET 6 instead of .NET Standard 2.0) the generator should be able to run in whatever form factor is desirable - the Shared Infrastructure will provide everything a generator could need for consuming a JSON configuration with C# input code and producing additional generated C# code as a result. + +Learn more about each individual cornerstone of the SilkTouch Stack: +- [Shared Infrastructure](shared-infrastructure.md) +- [Emitter](emitter.md) +- [Overloader](overloader.md) +- [Scraper](scraper.md) + +TODO: Add a link to the original proposal once the proposals folder has been reorganised according to the Software Development Plan. diff --git a/documentation/for-contributors/generators/emitter.md b/documentation/for-contributors/generators/emitter.md new file mode 100644 index 0000000000..f46d2b527d --- /dev/null +++ b/documentation/for-contributors/generators/emitter.md @@ -0,0 +1,3 @@ +# Emitter + +The Emitter. \ No newline at end of file diff --git a/documentation/for-contributors/generators/overloader.md b/documentation/for-contributors/generators/overloader.md new file mode 100644 index 0000000000..427bb135ad --- /dev/null +++ b/documentation/for-contributors/generators/overloader.md @@ -0,0 +1,3 @@ +# Overloader + +The Overloader. \ No newline at end of file diff --git a/documentation/for-contributors/generators/readme.md b/documentation/for-contributors/generators/readme.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/documentation/for-contributors/generators/scraper.md b/documentation/for-contributors/generators/scraper.md new file mode 100644 index 0000000000..6eb51ac3c1 --- /dev/null +++ b/documentation/for-contributors/generators/scraper.md @@ -0,0 +1,39 @@ +# Scraper + +The SilkTouch Scraper is responsible for walking through the abstract syntax tree (AST) of a C/C++ header and all the referenced headers therein to generate C# bindings to as much of this as possible where the configuration indicates this is desirable. Only certain headers will have bindings generated for them, which can be specified by providing the paths to the headers to "traverse" in the JSON configuration. + +## ClangSharp + +To walk through the C/C++ AST, ClangSharp's P/Invoke Generator is used, which is a proven tool for generating C# bindings to C/C++ APIs published by Microsoft. It does this by using the clang compiler to walk through the AST. + +ClangSharp's P/Invoke Generator is capable of producing C# bindings on its own, however these are raw bindings that use DllImport and don't aim to lift the bindings to be more C#-friendly. However, as part of Scraper development work was done to add an "XML output mode" to ClangSharp which instead of producing pure C# code, it outputs XML which roughly represents what the C# code would've looked like. + +## The Subagent + +ClangSharp uses libclang, which has funny rules over multithreading. To maintain a degree of parallelism during generation, the invocation of ClangSharp was moved to be done in a separate process. The original intention was to invoke the `ClangSharpPInvokeGenerator` tool as shipped publicly, but unfortunately `dotnet tool`s cannot be referenced by other libraries or `dotnet tool`s. As such, we created our own shim which mimics the behaviour of and duplicates a lot of code from the `ClangSharpPInvokeGenerator` tool, but within the same CLI executable. + +### The `ISubagent` abstraction + +`ISubagent` is responsible for launching the ClangSharp tool with certain parameters, inputs, and configuration. This is an abstraction such that the main Scraper assembly remains easily portable to another form factor should we enable the Scraper to run in another form factor in the future, as well as to prevent the library assembly knowing too much about the assembly in which its executing. + +The only implementation of this interface is the `ClangSharpSubagent` class in the `SilkTouch` assembly (CLI implementation of the Shared Infrastructure) which gathers information on the currently executing assembly, and relaunches it with `clangsharp` as the first argument and a JSON-encoded record as the second. + +### The `silktouch clangsharp` command + +This is the command that invokes ClangSharp as described in the previous section. This deserializes the JSON-encoded record and passes the data held therein to the `PInvokeGenerator` class within ClangSharp. This will generate the XML dump and then quit, possibly with some ClangSharp-generated diagnostics and a non-zero error code if those diagnostics are severe. The implementation of this is in the `ClangSharpHandoff` class. + +## XML Mods + +Once an initial XML dump has been generated by The Subagent, the XML dump will be parsed by the `Silk.NET.SilkTouch.ClangSharp.Xml` library and produce a tree of records. This is done in the `ScraperGenerator` ahead of the XML mods stage. + +The purpose of the XML mods stage is to walk this tree of records, and modify it depending on the project configuration (for example changing all names to be PascalCase, or adding custom attributes to cause the Overloader to generate certain overloads). Each mod must be specifically enabled, and has free range over the XML. As such, they execute in the order in which they were specified in the project configuration. + +Each XML mod has access to the `ScraperJobConfiguration.ModOptions` and the full XML tree. It does not have any access to resources beyond that, such as the SilkTouch Context. + +TODO: Write words here about each XML mod once XML mods have been implemented + +## Transformation + +After the XML mods have been applied, the transformation stage is responsible for converting the bindings represented by the XML tree back into C# code that can be output via the SilkTouch Context (and added to the project compilation). Unlike ClangSharp, this stage will output bindings which are compatible with/invoke the Emitter and Overloader instead of using DllImport directly. + +TODO: Write more words here once this stage is implemented. diff --git a/documentation/for-contributors/generators/shared-infrastructure.md b/documentation/for-contributors/generators/shared-infrastructure.md new file mode 100644 index 0000000000..02fba83921 --- /dev/null +++ b/documentation/for-contributors/generators/shared-infrastructure.md @@ -0,0 +1,39 @@ +# Shared Infrastructure + +The Shared Infrastructure contains common code and abstractions for bootstrapping and running generators within the SilkTouch Stack. It aims to ensure that SilkTouch generators are easily portable, and have a unified interface over multiple generation mediums such that no extra code is required in individual generators to accomodate new form factors. + +## "Form Factor" + +A "Form Factor", referred to throughout the documentation, is simply a distinct type of application which invokes the SilkTouch Stack via the Shared Infrastructure (i.e. "it's a place where SilkTouch runs"). Distinct types such as Roslyn Source Generators and Command Line Interfaces, which are the two first-party form factors supported today. + +## Configuration + +All JSON configuration structures are represented by C# records in the Silk.NET.SilkTouch.Configuration namespace (in the Configuration folder of Silk.NET.SilkTouch.Common). + +### Project Configuration + +Each project has its own JSON configuration file which defines what each generator does for that particular project. This file should be added by the user as an `AdditionalFiles` file, with the name `silktouch.json` unless the `silktouch_config_file` `.editorconfig` option is used to override this with a new file name or file path (either absolute or relative). + +Each generator has its own specific configuration structure, which is outlined in XML documentation in the `Config.cs` file as well as the markdown documents for each individual generator. + +The project configuration also provides a (relative or absolute) path to a file containing the global configuration. + +### Global Configuration + +The global configuration contains variables which aren't specific to a particular project or generation process/job. Stuff like the License Header, which doesn't need to be duplicated across every single project configuration. + +## Generation + +### SilkTouch "Context" + +The SilkTouch "Context" (represented by the `SilkTouchContext` class in the `Silk.NET.SilkTouch.Generation` namespace, or Generation folder of Silk.MET.SilkTouch.Common) encapsulates all inputs and outputs passed into and retrieved from a generator running atop the Shared Infrastructure. It exists to isolate each individual generator from one another, and provide one unified data structure containing everything a generator might reasonably need to access about a project. + +Some SilkTouch Contexts have access to outputs generated by other generators in the `AdditionalSyntaxTrees` property. To ease context consumption, there is a `AllSyntaxTrees` property which will contain both the Roslyn `Compilation`'s syntax trees as well as the newly-added syntax trees. Currently this is only the Emitter and Overloader if the Scraper has ran before them, though it is intended that the Shared Infrastructure will be expanded to have a full-blown dependency resolution mechanism to allow more generator modularity and dynamic definition of a generator's soft or hard dependencies. + +### The Generator Object + +The Generator Object (represented by the `SilkTouchGenerator` class) is responsible for the consumption of basic project information, and morphing/storing this information in a way that allows for Contexts to be created from the generator object. After a context is created, it will be consumed by a generator, and then once again "ingested" by the generator object. This generator object forwards the results to bindable events, which are subscribed to by the form factor. + +The intention of this is to reduce duplicated code between the two form factors we maintain, and provide an interface to ease the bootstrapping of SilkTouch code generation - this class significantly reduces the amount of work required to implement another form factor. + +Currently there are no methods on `SilkTouchGenerator` which can create a `SilkTouchContext` directly, nor are there APIs which nicely facilitate the passing of generated outputs to a stage depending on them. Both of which are things we'd like to address in the future. \ No newline at end of file diff --git a/src/generators/README.md b/src/generators/README.md index ccd22d8147..58bb88ce71 100644 --- a/src/generators/README.md +++ b/src/generators/README.md @@ -1,3 +1,38 @@ # SilkTouch -This folder contains SilkTouch, our \ No newline at end of file +This folder contains SilkTouch, our C# native interoperability code generation stack. + +Here's a quick explanation of all the projects, for more info see the [developer documentation](../../documentation/for-contributors/generators/readme.md) + +## Silk.NET.SilkTouch.ClangSharp.Xml + +A completely separate/standalone ClangSharp XML parsing library, parsing the output generated by the ClangSharp P/Invoke Generator when in XML mode rather than C# mode. + +## Silk.NET.SilkTouch.Common + +This is the main project for the Roslyn/CLI Shared Infrastructure. It contains all of the configuration constructs, and basic plumbing for passing inputs and outputs to generation stages. + +## Silk.NET.SilkTouch.Emitter + +The SilkTouch Emitter generator, built on top of Silk.NET.SilkTouch.Common. This generates blittable C# platform invoke (P/Invoke) code, and does not do any marshalling. + +## Silk.NET.SilkTouch.Overloader + +The SilkTouch Overloader generator, built on top of Silk.NET.SilkTouch.Common. This, given a signature with pointers or other overloadable types, will generate higher-level, more C#-like overloads that wrap a function, performing necessary marshalling when lowering those types. + +## Silk.NET.SilkTouch.Roslyn + +The Roslyn Source Generator implementation of the Shared Infrastructure. This calls into `SilkTouchGenerator` and creates a `SilkTouchContext` for the Emitter and Overloader, which is passed into and used to run those generators. + +## Silk.NET.SilkTouch.Scraper + +The SilkTouch Scraper generator. This is built on top of Silk.NET.SilkTouch.Common, but only uses the CLI form factor and is locked to .NET 6 (unlike the other packages which must be .NET Standard 2.0 to enable Roslyn Source Generator support) + +## SilkTouch + +The `dotnet tool` and Command Line Interface (CLI) implementation of the Shared Infrastructure. This calls into `SilkTouchGenerator` and creates a `SilkTouchContext` for the Scraper, Emitter, and Overloader; which is passed into and used to run those generators. + +## More Documentation Resources + +- [Developer Documentation](../../documentation/for-contributors/generators/readme.md) +- TODO: Add a proposal link once the reorg of the folder is done \ No newline at end of file