mirror of
https://github.com/dorssel/usbipd-win.git
synced 2026-11-10 14:43:56 +08:00
Refactor to ETW logging
This commit is contained in:
30 files changed
+1185
-71
No files matched your search
+13
-1
@@ -191,7 +191,7 @@ charset = utf-8-bom
|
||||
indent_size = 2
|
||||
insert_final_newline = false
|
||||
|
||||
[*.{manifest,props,targets}]
|
||||
[*.{props,targets}]
|
||||
charset = utf-8-bom
|
||||
indent_size = 2
|
||||
|
||||
@@ -214,3 +214,15 @@ insert_final_newline = false
|
||||
[{global.json,dotnet-tools.json,Directory.Packages.props}]
|
||||
charset = utf-8
|
||||
insert_final_newline = false
|
||||
|
||||
[*.{xml,manifest}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{rc,rc2}]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
insert_final_newline = true
|
||||
+7
-4
@@ -14,14 +14,17 @@
|
||||
*.license text
|
||||
*.json text
|
||||
*.yml text
|
||||
*.xml text
|
||||
|
||||
*.slnx text
|
||||
*.snk binary
|
||||
|
||||
*.csproj text
|
||||
*.cs text eol=crlf
|
||||
*.props text
|
||||
*.targets text
|
||||
*.csproj text
|
||||
*.cs text eol=crlf
|
||||
*.props text
|
||||
*.targets text
|
||||
*.manifest text
|
||||
*.rc2 text
|
||||
|
||||
*.wixproj text
|
||||
*.wxs text
|
||||
|
||||
@@ -52,12 +52,18 @@ jobs:
|
||||
dotnet restore
|
||||
dotnet restore Installer
|
||||
|
||||
- name: Build
|
||||
- name: Build x64
|
||||
run: |
|
||||
dotnet build --configuration Release --no-restore --property:Platform=x64 --binaryLogger:build-x64.binlog
|
||||
dotnet build --configuration Release --no-restore --property:Platform=ARM64 Usbipd --binaryLogger:build-arm64.binlog
|
||||
dotnet build --configuration Release --no-restore --property:Platform=x64 `
|
||||
--binaryLogger:build-x64.binlog
|
||||
|
||||
- name: Test
|
||||
- name: Build arm64
|
||||
run: |
|
||||
dotnet build --configuration Release --no-restore --property:Platform=ARM64 --property:BuildProjectReferences=false Usbipd `
|
||||
--binaryLogger:build-arm64.binlog
|
||||
# NOTE: Only build Usbipd for arm64. All platform-independent projects have already been built.
|
||||
|
||||
- name: Test (x64 only)
|
||||
run: |
|
||||
dotnet test --configuration Release --no-build --property:Platform=x64 `
|
||||
--report-spekt-junit `
|
||||
|
||||
@@ -27,7 +27,7 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
|
||||
<!-- This allows building on Linux (required for Dependabot); however, building the installer requires Windows -->
|
||||
<!-- This allows NuGet restore on Linux (required for Dependabot); however, building requires Windows -->
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
|
||||
<!-- Common defaults -->
|
||||
|
||||
@@ -22,5 +22,8 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<!-- UnitTests -->
|
||||
<PackageVersion Include="JunitXml.TestLogger" Version="8.0.0" />
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<!-- Win32ResourceGenerator -->
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.6.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.2526" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -7,6 +7,7 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<Wix
|
||||
xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:fw="http://wixtoolset.org/schemas/v4/wxs/firewall"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"
|
||||
>
|
||||
<Fragment>
|
||||
<DirectoryRef Id="APPLICATIONFOLDER">
|
||||
@@ -92,6 +93,12 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<Component Id="COPYING.md">
|
||||
<File Id="COPYING.md" Name="COPYING.md" />
|
||||
</Component>
|
||||
|
||||
<Component Id="EventManifest.xml">
|
||||
<File Id="EventManifest.xml" Name="EventManifest.xml">
|
||||
<util:EventManifest MessageFile="[#usbipd.exe]" ResourceFile="[#usbipd.exe]" />
|
||||
</File>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- See: https://github.com/dorssel/usbipd-win/wiki/Installer-design -->
|
||||
@@ -197,6 +204,7 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<ComponentGroup Id="usbipd">
|
||||
<ComponentRef Id="COPYING.md" />
|
||||
<ComponentRef Id="usbipd.exe" />
|
||||
<ComponentRef Id="EventManifest.xml" />
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -2,8 +2,6 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
using static Usbipd.ConsoleTools;
|
||||
|
||||
namespace Usbipd;
|
||||
@@ -34,43 +32,34 @@ sealed partial class CommandHandlers : ICommandHandlers
|
||||
|
||||
using var host = Host.CreateDefaultBuilder()
|
||||
.UseWindowsService()
|
||||
.ConfigureAppConfiguration((context, builder) =>
|
||||
{
|
||||
var defaultConfig = new Dictionary<string, string?>();
|
||||
if (WindowsServiceHelpers.IsWindowsService())
|
||||
{
|
||||
// EventLog defaults to Warning, which is OK for .NET components,
|
||||
// but we want to specifically log Information from our own component.
|
||||
defaultConfig.Add($"Logging:EventLog:LogLevel:{nameof(Usbipd)}", "Information");
|
||||
}
|
||||
else
|
||||
{
|
||||
// When not running as a Windows service, do not spam the EventLog.
|
||||
defaultConfig.Add("Logging:EventLog:LogLevel:Default", "None");
|
||||
}
|
||||
// set the above as defaults
|
||||
_ = builder.AddInMemoryCollection(defaultConfig);
|
||||
.ConfigureAppConfiguration((context, builder) => _ = builder
|
||||
// set the defaults
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
// Our ETW logger only logs explicit LoggerMessages (and not ASP.NET and what not).
|
||||
{ $"Logging:Etw:LogLevel:Default", "None" },
|
||||
// It is smart enough to only log events when a listener is attached.
|
||||
{ $"Logging:Etw:LogLevel:{nameof(Usbipd)}", "Trace" }
|
||||
})
|
||||
// allow overrides from the environment
|
||||
_ = builder.AddEnvironmentVariables();
|
||||
.AddEnvironmentVariables()
|
||||
// allow overrides from the command line
|
||||
_ = builder.AddCommandLine(args);
|
||||
})
|
||||
.ConfigureLogging((context, logging) =>
|
||||
{
|
||||
if (!EventLog.SourceExists(Program.Product))
|
||||
{
|
||||
EventLog.CreateEventSource(Program.Product, "Application");
|
||||
}
|
||||
_ = logging.AddEventLog(settings => settings.SourceName = Program.Product);
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
_ = services.AddHostedService<Server>();
|
||||
_ = services.AddSingleton<PcapNg>();
|
||||
_ = services.AddScoped<ClientContext>();
|
||||
_ = services.AddScoped<ConnectedClient>();
|
||||
_ = services.AddScoped<AttachedClient>();
|
||||
})
|
||||
.AddCommandLine(args)
|
||||
)
|
||||
.ConfigureLogging((context, logging) => _ = logging
|
||||
// The default builder also adds an EventLog provider, but we don't want that.
|
||||
.ClearProviders()
|
||||
.AddConsole()
|
||||
.AddDebug()
|
||||
.AddEtwLogger()
|
||||
)
|
||||
.ConfigureServices((hostContext, services) => _ = services
|
||||
.AddHostedService<Server>()
|
||||
.AddSingleton<PcapNg>()
|
||||
.AddScoped<ClientContext>()
|
||||
.AddScoped<ConnectedClient>()
|
||||
.AddScoped<AttachedClient>()
|
||||
)
|
||||
.Build();
|
||||
|
||||
await host.RunAsync(cancellationToken);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.Diagnostics.Eventing.Reader;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.Diagnostics.Etw;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
sealed class EtwLogger : ILogger
|
||||
{
|
||||
readonly REGHANDLE RegistrationHandle;
|
||||
|
||||
public EtwLogger(REGHANDLE registrationHandle)
|
||||
{
|
||||
RegistrationHandle = registrationHandle;
|
||||
}
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return RegistrationHandle != default && logLevel switch
|
||||
{
|
||||
LogLevel.Critical => PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Critical, 0x8000000000000000)
|
||||
|| PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Critical, 0x4000000000000000),
|
||||
LogLevel.Error => PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Error, 0x8000000000000000)
|
||||
|| PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Error, 0x4000000000000000),
|
||||
LogLevel.Warning => PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Warning, 0x8000000000000000)
|
||||
|| PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Warning, 0x4000000000000000),
|
||||
LogLevel.Information => PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Informational, 0x8000000000000000)
|
||||
|| PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Informational, 0x4000000000000000),
|
||||
LogLevel.Debug or LogLevel.Trace => (bool)PInvoke.EventProviderEnabled(RegistrationHandle, (byte)StandardEventLevel.Verbose, 0x4000000000000000),
|
||||
LogLevel.None or _ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if ((RegistrationHandle == default) || !IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to call the formatter at most once.
|
||||
string? message = null;
|
||||
|
||||
// Log to the Operational channel, but only events that are not specifically Debug or Trace level.
|
||||
if (logLevel is not (LogLevel.Debug or LogLevel.Trace))
|
||||
{
|
||||
var descriptor = new EVENT_DESCRIPTOR
|
||||
{
|
||||
Id = (ushort)eventId.Id, // This works for Event Viewer
|
||||
Version = 0, // Not used by us
|
||||
Channel = 16, // Must be 16 to match the manifest Operational channel
|
||||
Level = (byte)(logLevel switch
|
||||
{
|
||||
LogLevel.Critical => StandardEventLevel.Critical,
|
||||
LogLevel.Error => StandardEventLevel.Error,
|
||||
LogLevel.Warning => StandardEventLevel.Warning,
|
||||
_ => StandardEventLevel.Informational
|
||||
}),
|
||||
Opcode = 0,
|
||||
Task = 0,
|
||||
Keyword = 0x8000000000000000
|
||||
};
|
||||
if (PInvoke.EventEnabled(RegistrationHandle, descriptor))
|
||||
{
|
||||
message = formatter(state, exception);
|
||||
unsafe // DevSkim: ignore DS172412
|
||||
{
|
||||
fixed (char* pMessageChars = message)
|
||||
{
|
||||
Span<EVENT_DATA_DESCRIPTOR> dataDescriptors = [
|
||||
new EVENT_DATA_DESCRIPTOR {
|
||||
Ptr = (ulong)pMessageChars,
|
||||
Size = (uint)((message.Length + 1) * sizeof(char)),
|
||||
Reserved = 0
|
||||
},
|
||||
];
|
||||
_ = PInvoke.EventWrite(RegistrationHandle, descriptor, dataDescriptors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log to the Debug channel.
|
||||
{
|
||||
var descriptor = new EVENT_DESCRIPTOR
|
||||
{
|
||||
Id = (ushort)eventId.Id, // This works for Event Viewer
|
||||
Version = 0, // Not used by us
|
||||
Channel = 17, // Must be 17 to match the manifest Debug channel
|
||||
Level = (byte)(logLevel switch
|
||||
{
|
||||
LogLevel.Critical => StandardEventLevel.Critical,
|
||||
LogLevel.Error => StandardEventLevel.Error,
|
||||
LogLevel.Warning => StandardEventLevel.Warning,
|
||||
LogLevel.Information => StandardEventLevel.Informational,
|
||||
_ => StandardEventLevel.Verbose
|
||||
}),
|
||||
Opcode = 0,
|
||||
Task = 0,
|
||||
Keyword = 0x4000000000000000
|
||||
};
|
||||
if (PInvoke.EventEnabled(RegistrationHandle, descriptor))
|
||||
{
|
||||
message ??= formatter(state, exception);
|
||||
unsafe // DevSkim: ignore DS172412
|
||||
{
|
||||
fixed (char* pMessageChars = message)
|
||||
{
|
||||
Span<EVENT_DATA_DESCRIPTOR> dataDescriptors = [
|
||||
new EVENT_DATA_DESCRIPTOR {
|
||||
Ptr = (ulong)pMessageChars,
|
||||
Size = (uint)((message.Length + 1) * sizeof(char)),
|
||||
Reserved = 0
|
||||
},
|
||||
];
|
||||
_ = PInvoke.EventWrite(RegistrationHandle, descriptor, dataDescriptors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
static class EtwLoggerExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddEtwLogger(this ILoggingBuilder builder)
|
||||
{
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EtwLoggerProvider>());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.Diagnostics.Etw;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
[ProviderAlias("Etw")]
|
||||
sealed class EtwLoggerProvider : ILoggerProvider
|
||||
{
|
||||
REGHANDLE RegistrationHandle;
|
||||
|
||||
public EtwLoggerProvider()
|
||||
{
|
||||
// The GUID is derived from the provider name "usbipd" using the algorithm described in
|
||||
// https://learn.microsoft.com/en-us/windows/win32/etw/guid-generation-algorithm
|
||||
|
||||
unsafe // DevSkim: ignore DS172412
|
||||
{
|
||||
var result = PInvoke.EventRegister(new("{766A7E49-AC4D-5405-97C9-C9A2F7C4C458}"), null, null, out RegistrationHandle);
|
||||
if (result != 0)
|
||||
{
|
||||
RegistrationHandle = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new EtwLogger(RegistrationHandle);
|
||||
}
|
||||
|
||||
bool IsDisposed;
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (RegistrationHandle != default)
|
||||
{
|
||||
unsafe // DevSkim: ignore DS172412
|
||||
{
|
||||
_ = PInvoke.EventUnregister(RegistrationHandle);
|
||||
RegistrationHandle = default;
|
||||
}
|
||||
}
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -9,24 +9,24 @@ namespace Usbipd;
|
||||
|
||||
static partial class LogEvents
|
||||
{
|
||||
[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Client {clientAddress} claimed device at {busId} ({instanceId}).")]
|
||||
[LoggerMessage(1, LogLevel.Information, "Client {clientAddress} claimed device at {busId} ({instanceId}).")]
|
||||
public static partial void ClientAttach(this ILogger logger, IPAddress clientAddress, BusId busId, string instanceId);
|
||||
|
||||
[LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "Client {clientAddress} released device at {busId} ({instanceId}).")]
|
||||
[LoggerMessage(2, LogLevel.Information, "Client {clientAddress} released device at {busId} ({instanceId}).")]
|
||||
public static partial void ClientDetach(this ILogger logger, IPAddress clientAddress, BusId busId, string instanceId);
|
||||
|
||||
[LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "An exception occurred while communicating with the client:")]
|
||||
[LoggerMessage(3, LogLevel.Error, "An exception occurred while communicating with the client.")]
|
||||
public static partial void ClientError(this ILogger logger, Exception ex);
|
||||
|
||||
[LoggerMessage(EventId = 4, Level = LogLevel.Error, Message = "An internal error occurred: {text}")]
|
||||
[LoggerMessage(4, LogLevel.Error, "An internal error occurred: {text}")]
|
||||
public static partial void InternalError(this ILogger logger, string text, Exception? ex = null);
|
||||
|
||||
[LoggerMessage(EventId = 5, Level = LogLevel.Information, Message = "Auto-bind of device at {busId} ({instanceId}) by client {clientAddress}.")]
|
||||
[LoggerMessage(5, LogLevel.Information, "Auto-bind of device at {busId} ({instanceId}) by client {clientAddress}.")]
|
||||
public static partial void AutoBind(this ILogger logger, IPAddress clientAddress, BusId busId, string instanceId);
|
||||
|
||||
[LoggerMessage(EventId = 1000, Level = LogLevel.Debug, Message = "{text}")]
|
||||
[LoggerMessage(1000, LogLevel.Debug, "{text}")]
|
||||
public static partial void Debug(this ILogger logger, string text);
|
||||
|
||||
[LoggerMessage(EventId = 1001, Level = LogLevel.Trace, Message = "{text}")]
|
||||
[LoggerMessage(1001, LogLevel.Trace, "{text}")]
|
||||
public static partial void Trace(this ILogger logger, string text);
|
||||
}
|
||||
@@ -131,3 +131,9 @@ IOCTL_USB_HUB_CYCLE_PORT
|
||||
// USB_NODE_CONNECTION_INFORMATION_EX
|
||||
USB_NODE_CONNECTION_INFORMATION_EX_V2
|
||||
USB_CYCLE_PORT_PARAMS
|
||||
|
||||
EventEnabled
|
||||
EventProviderEnabled
|
||||
EventRegister
|
||||
EventUnregister
|
||||
EventWrite
|
||||
+45
-10
@@ -11,7 +11,6 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(MainTargetFramework)</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
@@ -23,6 +22,25 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<AssemblyName>usbipd</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<CompilerVisibleProperty Include="ProjectDir" />
|
||||
<CompilerVisibleProperty Include="IntermediateOutputPath" />
|
||||
<CompilerVisibleProperty Include="Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Win32ResourceGenerator\Win32ResourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false">
|
||||
<SetPlatform>Platform=AnyCPU</SetPlatform>
|
||||
<SetConfiguration>Configuration=Release</SetConfiguration>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="SetWin32Resource" BeforeTargets="BeforeCompile">
|
||||
<PropertyGroup>
|
||||
<Win32Resource>$(IntermediateOutputPath)generated\Win32ResourceGenerator\Resources.res</Win32Resource>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Usbipd.Automation\*.cs" LinkBase="Usbipd.Automation" />
|
||||
</ItemGroup>
|
||||
@@ -47,25 +65,42 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- (generated) event provider manifest -->
|
||||
<Content
|
||||
Include="$(IntermediateOutputPath)generated\Win32ResourceGenerator\EventManifest.xml"
|
||||
Link="EventManifest.xml"
|
||||
CopyToOutputDirectory="PreserveNewest"
|
||||
/>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyManifestOnPublishNoBuild" AfterTargets="Publish">
|
||||
<Copy
|
||||
SourceFiles="$(IntermediateOutputPath)generated\Win32ResourceGenerator\EventManifest.xml"
|
||||
DestinationFiles="$(PublishDir)EventManifest.xml"
|
||||
SkipUnchangedFiles="true"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- WSL -->
|
||||
<!-- Copy everything at the top level only, except the REUSE .license files (if any). -->
|
||||
<None Update="WSL\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Remove="WSL\*\*" />
|
||||
<None Remove="WSL\*.license" />
|
||||
<None Remove="WSL\**\*" />
|
||||
<Content Include="WSL\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Remove="WSL\*.license" />
|
||||
<!-- Also copy everything in the platform directory moving it to the top level, except the REUSE .license files (if any). -->
|
||||
<None Include="WSL\$(Platform)\*" Link="WSL\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Remove="WSL\$(Platform)\*.license" />
|
||||
<Content Include="WSL\$(Platform)\*" Link="WSL\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Remove="WSL\$(Platform)\*.license" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Drivers -->
|
||||
<!-- Copy everything at the top level only, except the REUSE .license files (if any). -->
|
||||
<None Include="..\Drivers\*" Link="Drivers\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Remove="..\Drivers\*.license" />
|
||||
<Content Include="..\Drivers\*" Link="Drivers\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Remove="..\Drivers\*.license" />
|
||||
<!-- Also copy everything in the platform directory moving it to the top level, except the REUSE .license files (if any). -->
|
||||
<None Include="..\Drivers\$(Platform)\*" Link="Drivers\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Remove="..\Drivers\$(Platform)\*.license" />
|
||||
<Content Include="..\Drivers\$(Platform)\*" Link="Drivers\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Remove="..\Drivers\$(Platform)\*.license" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+1
-7
@@ -1,9 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2020 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
@@ -16,7 +11,6 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
SPDX-FileCopyrightText: 2020 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
NOTE:
|
||||
The application manifest is shipped as part of the binary release, which is covered by the COPYING file.
|
||||
The SPDX tags are for source licenses only.
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
namespace Win32ResourceGenerator;
|
||||
|
||||
record struct BuildOptions(
|
||||
string ProjectDir,
|
||||
string IntermediateOutputPath,
|
||||
string Configuration);
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Win32ResourceGenerator;
|
||||
|
||||
static class DiagnosticRules
|
||||
{
|
||||
const string Category = "Win32ResourceGenerator.LoggerMessage";
|
||||
|
||||
#pragma warning disable RS2008 // Enable analyzer release tracking
|
||||
|
||||
public static readonly DiagnosticDescriptor DuplicateIdRule = new("EM001", "Duplicate EventId detected",
|
||||
"The EventId '{0}' is already used by method '{1}'. Event IDs must be unique across the project.",
|
||||
Category, DiagnosticSeverity.Error, true);
|
||||
|
||||
public static readonly DiagnosticDescriptor MissingPropertyRule = new("EM002", "Property missing",
|
||||
"The property '{0}' is missing",
|
||||
Category, DiagnosticSeverity.Error, true);
|
||||
|
||||
public static readonly DiagnosticDescriptor DuplicatePropertyRule = new("EM003", "Duplicate Property",
|
||||
"The property '{0}' is specified more than once. Properties should be set either by constructor arguments or by named arguments, but not both.",
|
||||
Category, DiagnosticSeverity.Error, true);
|
||||
|
||||
public static readonly DiagnosticDescriptor InvalidEventIdRule = new("EM004", "Invalid EventId",
|
||||
"The EventId '{0}' is not valid. Event IDs must be between 1 and 65535, id 0 is not allowed.",
|
||||
Category, DiagnosticSeverity.Error, true);
|
||||
|
||||
public static readonly DiagnosticDescriptor InvalidLevelRule = new("EM005", "Invalid Level",
|
||||
"The level '{0}' is not valid. Levels must be between LogLevel.Trace and LogLevel.Critical, LogLevel.None is not supported.",
|
||||
Category, DiagnosticSeverity.Error, true);
|
||||
|
||||
public static readonly DiagnosticDescriptor InvalidMessageRule = new("EM006", "Invalid Message",
|
||||
"The message '{0}' is not valid. Messages must be non-empty and non-whitespace.",
|
||||
Category, DiagnosticSeverity.Error, true);
|
||||
|
||||
#pragma warning restore RS2008 // Enable analyzer release tracking
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<instrumentationManifest xmlns="{{DefaultXmlNs}}" xmlns:win="{{WinXmlNs}}">
|
||||
<instrumentation>
|
||||
<events>
|
||||
<provider name="{{ProviderName}}" guid="{{ProviderGuid}}" symbol="P" resourceFileName="usbipd.exe" messageFileName="usbipd.exe" message="$(string.Provider.Name)">
|
||||
<channels>
|
||||
<channel name="{{ProviderName}}/Operational" chid="O" type="Operational" enabled="true" message="$(string.Channel.Operational.Name)" />
|
||||
<channel name="{{ProviderName}}/Debug" chid="D" type="Debug" enabled="false" message="$(string.Channel.Debug.Name)" />
|
||||
</channels>
|
||||
<templates>
|
||||
<template tid="T">
|
||||
<data name="Message" inType="win:UnicodeString"/>
|
||||
</template>
|
||||
</templates>
|
||||
<events>
|
||||
{{eventsXml}}
|
||||
</events>
|
||||
</provider>
|
||||
</events>
|
||||
</instrumentation>
|
||||
<localization>
|
||||
<resources culture="en-US">
|
||||
<stringTable>
|
||||
<string id="Provider.Name" value="{{ProviderName}}" />
|
||||
<string id="Channel.Operational.Name" value="Operational" />
|
||||
<string id="Channel.Debug.Name" value="Debug" />
|
||||
<string id="M" value="%1" />
|
||||
</stringTable>
|
||||
</resources>
|
||||
</localization>
|
||||
</instrumentationManifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
NOTE:
|
||||
The generated EventManifest.xml generated from this template is shipped as part of the binary release, which is covered
|
||||
by the COPYING file. The SPDX tags are for source licenses only.
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#error This file is not editable with the visual studio resource editor.
|
||||
#endif
|
||||
|
||||
|
||||
// Instead of pulling in the entire Windows SDK, we can just define the constants we need here.
|
||||
|
||||
#define LANG_NEUTRAL 0x00
|
||||
#define SUBLANG_NEUTRAL 0x00
|
||||
|
||||
#define RT_MANIFEST 24
|
||||
|
||||
// app.manifest is provided by the main (compiling) project.
|
||||
|
||||
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
||||
1 RT_MANIFEST "app.manifest"
|
||||
|
||||
// This we generate ourselves from VersionInfoTemplate.rc2.
|
||||
#include "VersionInfo.rc"
|
||||
|
||||
// This one is generated by mc.exe, after we generated EventManifest.xml from EventManifestTemplate.xml.
|
||||
#include "EventManifest.rc"
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#error This file is not editable with the visual studio resource editor.
|
||||
#endif
|
||||
|
||||
|
||||
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, CI_BUILD_NUMBER
|
||||
PRODUCTVERSION VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, COMMITS_SINCE_VERSION_SOURCE
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS FILE_FLAGS
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "{{BlockCulture}}"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "{{CompanyName}}"
|
||||
VALUE "FileDescription", "Utility for sharing locally connected USB devices to other machines"
|
||||
VALUE "FileVersion", "{{FileVersion}}"
|
||||
VALUE "InternalName", "usbipd"
|
||||
VALUE "LegalCopyright", "{{LegalCopyright}}"
|
||||
VALUE "OriginalFilename", "usbipd.exe"
|
||||
VALUE "ProductName", "{{ProductName}}"
|
||||
VALUE "ProductVersion", "{{ProductVersion}}"
|
||||
{{OptionalPrivateBuild}}
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", {{LCID}}, {{CodePage}}
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,300 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Storage.FileSystem;
|
||||
|
||||
namespace Win32ResourceGenerator;
|
||||
|
||||
[Generator]
|
||||
public class Generator : IIncrementalGenerator
|
||||
{
|
||||
readonly string WindowsSDKBuildToolsBinVersionedArchFolder;
|
||||
|
||||
readonly byte[] Resources;
|
||||
|
||||
readonly FileVersionInfo FileVersionInfo;
|
||||
|
||||
readonly string EventManifestTemplate;
|
||||
|
||||
readonly string VersionInfoTemplate;
|
||||
|
||||
const string DefaultXmlNs = "http://schemas.microsoft.com/win/2004/08/events"; // DevSkim: ignore DS137138
|
||||
const string WinXmlNs = "http://manifests.microsoft.com/win/2004/08/windows/events"; // DevSkim: ignore DS137138
|
||||
|
||||
const string ProviderName = "usbipd";
|
||||
readonly string ProviderGuid;
|
||||
|
||||
public Generator()
|
||||
{
|
||||
// Initialize some constants.
|
||||
{
|
||||
// The provider GUID is generated based on the provider name.
|
||||
using var eventSource = new EventSource(ProviderName);
|
||||
ProviderGuid = eventSource.Guid.ToString("B").ToUpperInvariant();
|
||||
}
|
||||
|
||||
// Get some embedded data from the generator assembly.
|
||||
{
|
||||
var assembly = typeof(Generator).Assembly;
|
||||
// MSBuild properties of the source generator project are embedded as custom attributes.
|
||||
{
|
||||
var metadata = assembly.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
WindowsSDKBuildToolsBinVersionedArchFolder = metadata.Single(a => a.Key == "WindowsSDKBuildToolsBinVersionedArchFolder").Value;
|
||||
}
|
||||
// (Template) resource files are embedded as resources.
|
||||
{
|
||||
var resourceNames = assembly.GetManifestResourceNames();
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceNames.Single(r => r.EndsWith(".Resources.rc2")));
|
||||
using var memoryStream = new MemoryStream();
|
||||
stream.CopyTo(memoryStream);
|
||||
Resources = memoryStream.ToArray();
|
||||
}
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceNames.Single(r => r.EndsWith(".EventManifestTemplate.xml")));
|
||||
using var reader = new StreamReader(stream);
|
||||
EventManifestTemplate = reader.ReadToEnd();
|
||||
}
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceNames.Single(r => r.EndsWith(".VersionInfoTemplate.rc2")));
|
||||
using var reader = new StreamReader(stream);
|
||||
VersionInfoTemplate = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
// Our own FileVersionInfo is used to generate the VersionInfo.rc file.
|
||||
FileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// We need to filter out generated sources, which either have no file path or end with .g.cs.
|
||||
var loggerMessages = context.SyntaxProvider.ForAttributeWithMetadataName(
|
||||
"Microsoft.Extensions.Logging.LoggerMessageAttribute",
|
||||
(syntaxNode, _) =>
|
||||
!string.IsNullOrEmpty(syntaxNode.SyntaxTree.FilePath)
|
||||
&& !syntaxNode.SyntaxTree.FilePath.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase),
|
||||
(context, _) => LoggerMessage.Create(context));
|
||||
|
||||
var buildOptions = context.AnalyzerConfigOptionsProvider.Select((provider, ct) =>
|
||||
{
|
||||
_ = provider.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir);
|
||||
_ = provider.GlobalOptions.TryGetValue("build_property.intermediateOutputPath", out var intermediateOutputPath);
|
||||
_ = provider.GlobalOptions.TryGetValue("build_property.Configuration", out var configuration);
|
||||
return new BuildOptions(projectDir ?? string.Empty, intermediateOutputPath ?? string.Empty, configuration ?? string.Empty);
|
||||
});
|
||||
|
||||
var loggerMessagesWithOptions = loggerMessages.Collect().Combine(buildOptions);
|
||||
|
||||
context.RegisterSourceOutput(loggerMessagesWithOptions, (context, loggerMessagesWithOptions) =>
|
||||
{
|
||||
var loggerMessages = loggerMessagesWithOptions.Left;
|
||||
var buildOptions = loggerMessagesWithOptions.Right;
|
||||
|
||||
var seenIds = new Dictionary<int, LoggerMessage>();
|
||||
var hasErrors = false;
|
||||
|
||||
foreach (var loggerMessage in loggerMessages)
|
||||
{
|
||||
if (loggerMessage.ReportDiagnostics(context))
|
||||
{
|
||||
hasErrors = true;
|
||||
}
|
||||
else if (seenIds.TryGetValue(loggerMessage.Id, out var existingMethod))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.DuplicateIdRule, loggerMessage.Location,
|
||||
loggerMessage.Id, existingMethod.MethodName));
|
||||
hasErrors = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
seenIds[loggerMessage.Id] = loggerMessage;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var eventsXml = new StringBuilder();
|
||||
foreach (var loggerMessage in loggerMessages)
|
||||
{
|
||||
var isDebug = loggerMessage.Level == EventLevel.Verbose;
|
||||
_ = eventsXml.AppendLine(
|
||||
$" <event value=\"{loggerMessage.Id}\" channel=\"{(isDebug ? 'D' : 'O')}\" template=\"T\" level=\"win:{loggerMessage.Level}\" message=\"$(string.M)\" />"
|
||||
);
|
||||
}
|
||||
|
||||
var eventManifestXml = EventManifestTemplate
|
||||
.Replace($"{{{{{nameof(DefaultXmlNs)}}}}}", DefaultXmlNs)
|
||||
.Replace($"{{{{{nameof(WinXmlNs)}}}}}", WinXmlNs)
|
||||
.Replace($"{{{{{nameof(ProviderName)}}}}}", ProviderName)
|
||||
.Replace($"{{{{{nameof(ProviderGuid)}}}}}", ProviderGuid)
|
||||
.Replace($"{{{{{nameof(eventsXml)}}}}}", eventsXml.ToString().Trim())
|
||||
;
|
||||
|
||||
var eventManifestBytes = new UTF8Encoding(false).GetBytes(eventManifestXml);
|
||||
|
||||
var generatorOutputDir = Path.Combine(
|
||||
buildOptions.ProjectDir,
|
||||
buildOptions.IntermediateOutputPath,
|
||||
"generated",
|
||||
nameof(Win32ResourceGenerator));
|
||||
var eventManifestPath = Path.Combine(generatorOutputDir, "EventManifest.xml");
|
||||
|
||||
_ = Directory.CreateDirectory(generatorOutputDir);
|
||||
|
||||
var manifestChanged = UpdateIfContentChanged(eventManifestPath, eventManifestBytes);
|
||||
if (manifestChanged)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.Combine(WindowsSDKBuildToolsBinVersionedArchFolder, "mc.exe"),
|
||||
Arguments = "-um EventManifest.xml",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = generatorOutputDir,
|
||||
};
|
||||
|
||||
using var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Failed to start the process.");
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"mc.exe error (Code {process.ExitCode}): {error}");
|
||||
}
|
||||
}
|
||||
|
||||
_ = UpdateIfContentChanged(Path.Combine(generatorOutputDir, "Resources.rc"), Resources);
|
||||
|
||||
var applicationManifest = File.ReadAllBytes(Path.Combine(buildOptions.ProjectDir, "app.manifest"));
|
||||
_ = UpdateIfContentChanged(Path.Combine(generatorOutputDir, "app.manifest"), applicationManifest);
|
||||
|
||||
_ = GenerateVersionInfo(generatorOutputDir, buildOptions.Configuration == "Release");
|
||||
|
||||
if (IsUpdateRequired(generatorOutputDir,
|
||||
["Resources.rc", "app.manifest", "VersionInfo.rc", "EventManifest.rc", "EventManifestTEMP.BIN", "MSG00001.bin"],
|
||||
["Resources.res"]))
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.Combine(WindowsSDKBuildToolsBinVersionedArchFolder, "rc.exe"),
|
||||
Arguments = "/NoLogo /8 Resources.rc",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = generatorOutputDir,
|
||||
};
|
||||
|
||||
using var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Failed to start the process.");
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"rc.exe error (Code {process.ExitCode}): {error}");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
static bool IsUpdateRequired(string generatorOutputDir, string[] inputs, string[] outputs)
|
||||
{
|
||||
var outputFiles = outputs.Select(output => Path.Combine(generatorOutputDir, output)).ToArray();
|
||||
var inputFiles = inputs.Select(input => Path.Combine(generatorOutputDir, input)).ToArray();
|
||||
if (outputFiles.Any(outputFile => !File.Exists(outputFile)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var outputLastWriteTime = outputFiles.Max(outputFile => File.GetLastWriteTimeUtc(outputFile));
|
||||
var inputLastWriteTime = inputFiles.Max(inputFile => File.GetLastWriteTimeUtc(inputFile));
|
||||
return inputLastWriteTime > outputLastWriteTime;
|
||||
}
|
||||
|
||||
static bool UpdateIfContentChanged(string filePath, byte[] newContent)
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var currentContent = File.ReadAllBytes(filePath);
|
||||
if (currentContent.SequenceEqual(newContent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File.WriteAllBytes(filePath, newContent);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GenerateVersionInfo(string generatorOutputDir, bool release)
|
||||
{
|
||||
var culture = new CultureInfo("en-US", false);
|
||||
|
||||
var versionInfoPath = Path.Combine(generatorOutputDir, "VersionInfo.rc");
|
||||
|
||||
var version = new Version(GitVersionInformation.AssemblySemFileVer);
|
||||
|
||||
VS_FIXEDFILEINFO_FILE_FLAGS fileFlags = 0;
|
||||
string? privateBuild = null;
|
||||
if (!release)
|
||||
{
|
||||
fileFlags |= VS_FIXEDFILEINFO_FILE_FLAGS.VS_FF_DEBUG | VS_FIXEDFILEINFO_FILE_FLAGS.VS_FF_PRIVATEBUILD;
|
||||
privateBuild = "This is an unofficial (debug) build.";
|
||||
}
|
||||
else if (version.Revision == 0)
|
||||
{
|
||||
fileFlags |= VS_FIXEDFILEINFO_FILE_FLAGS.VS_FF_PRIVATEBUILD;
|
||||
privateBuild = "This is an unofficial (non-CI) build.";
|
||||
}
|
||||
|
||||
privateBuild = privateBuild is not null
|
||||
? $"""
|
||||
VALUE "PrivateBuild", "{privateBuild}"
|
||||
"""
|
||||
: string.Empty;
|
||||
|
||||
var versionInfoContent = VersionInfoTemplate
|
||||
.Replace("VS_VERSION_INFO", $"{PInvoke.VS_VERSION_INFO}")
|
||||
.Replace("VERSION_MAJOR", $"{version.Major}")
|
||||
.Replace("VERSION_MINOR", $"{version.Minor}")
|
||||
.Replace("VERSION_PATCH", $"{version.Build}")
|
||||
.Replace("CI_BUILD_NUMBER", $"{version.Revision}")
|
||||
.Replace("COMMITS_SINCE_VERSION_SOURCE", $"{GitVersionInformation.CommitsSinceVersionSource}")
|
||||
.Replace("VOS_NT_WINDOWS32", $"0x{(uint)VS_FIXEDFILEINFO_FILE_OS.VOS_NT_WINDOWS32:x8}L")
|
||||
.Replace("VFT_APP", $"0x{(uint)VS_FIXEDFILEINFO_FILE_TYPE.VFT_APP:x8}L")
|
||||
.Replace("VS_FFI_FILEFLAGSMASK", $"0x{PInvoke.VS_FFI_FILEFLAGSMASK:x8}L")
|
||||
.Replace("FILE_FLAGS", $"0x{(uint)fileFlags:x8}L")
|
||||
.Replace("{{OptionalPrivateBuild}}", privateBuild)
|
||||
.Replace("{{BlockCulture}}", $"{culture.LCID:x4}{Encoding.Unicode.CodePage:x4}")
|
||||
.Replace("{{CompanyName}}", $"{FileVersionInfo.CompanyName}")
|
||||
.Replace("{{FileVersion}}", $"{FileVersionInfo.FileVersion}")
|
||||
.Replace("{{LegalCopyright}}", $"{FileVersionInfo.LegalCopyright}")
|
||||
.Replace("{{ProductName}}", $"{FileVersionInfo.ProductName}")
|
||||
.Replace("{{ProductVersion}}", $"{FileVersionInfo.ProductVersion}")
|
||||
.Replace("{{LCID}}", $"{culture.LCID}")
|
||||
.Replace("{{CodePage}}", $"{Encoding.Unicode.CodePage}")
|
||||
;
|
||||
|
||||
var versionInfoBytes = new UTF8Encoding(false).GetBytes(versionInfoContent);
|
||||
return UpdateIfContentChanged(versionInfoPath, versionInfoBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1035:Do not use APIs banned for analyzers", Justification = "We have to, since we output binary files.")]
|
||||
@@ -0,0 +1,168 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Win32ResourceGenerator;
|
||||
|
||||
public class LoggerMessage
|
||||
{
|
||||
LoggerMessage(string methodName, Location location,
|
||||
int? constructorId, int? constructorLevel, string? constructorMessage,
|
||||
int? namedId, int? namedLevel, string? namedMessage)
|
||||
{
|
||||
MethodName = methodName;
|
||||
Location = location;
|
||||
ConstructorId = constructorId;
|
||||
ConstructorLevel = constructorLevel;
|
||||
ConstructorMessage = constructorMessage;
|
||||
NamedId = namedId;
|
||||
NamedLevel = namedLevel;
|
||||
NamedMessage = namedMessage;
|
||||
}
|
||||
|
||||
public string MethodName { get; }
|
||||
|
||||
public Location Location { get; }
|
||||
|
||||
readonly int? ConstructorId;
|
||||
readonly int? ConstructorLevel;
|
||||
readonly string? ConstructorMessage;
|
||||
|
||||
readonly int? NamedId;
|
||||
readonly int? NamedLevel;
|
||||
readonly string? NamedMessage;
|
||||
|
||||
public bool IsValid { get; private set; }
|
||||
|
||||
public ushort Id => IsValid ? (ushort)(ConstructorId ?? NamedId)! : throw new InvalidOperationException("Not valid");
|
||||
|
||||
public EventLevel Level => (IsValid ? (ConstructorLevel ?? NamedLevel)! : throw new InvalidOperationException("Not valid")) switch
|
||||
{
|
||||
// NOTE: We do not want to reference Microsoft.Extensions.Logging.Abstractions in this project, so we cannot use LogLevel enum directly.
|
||||
|
||||
5 /* LogLevel.Critical */ => EventLevel.Critical,
|
||||
4 /* LogLevel.Error */ => EventLevel.Error,
|
||||
3 /* LogLevel.Warning */ => EventLevel.Warning,
|
||||
2 /* LogLevel.Information */ => EventLevel.Informational,
|
||||
1 /* LogLevel.Debug */ => EventLevel.Verbose,
|
||||
0 /* LogLevel.Trace */ => EventLevel.Verbose,
|
||||
// NOTE: We cannot support LogLevel.None (6).
|
||||
6 or _ => throw new InvalidOperationException("Not valid"),
|
||||
};
|
||||
|
||||
public string Message => IsValid ? (ConstructorMessage ?? NamedMessage)! : throw new InvalidOperationException("Not valid");
|
||||
|
||||
static public LoggerMessage Create(GeneratorAttributeSyntaxContext context)
|
||||
{
|
||||
var diagnosticDescriptors = new List<DiagnosticDescriptor>();
|
||||
|
||||
int? constructorId = null;
|
||||
int? constructorLevel = null;
|
||||
string? constructorMessage = null;
|
||||
|
||||
var attribute = context.Attributes.Single();
|
||||
|
||||
foreach (var arg in attribute.ConstructorArguments)
|
||||
{
|
||||
switch (arg.Type?.ToDisplayString())
|
||||
{
|
||||
case "int":
|
||||
constructorId = (int?)arg.Value;
|
||||
break;
|
||||
case "Microsoft.Extensions.Logging.LogLevel":
|
||||
constructorLevel = (int?)arg.Value;
|
||||
break;
|
||||
case "string":
|
||||
constructorMessage = (string?)arg.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int? namedId = null;
|
||||
int? namedLevel = null;
|
||||
string? namedMessage = null;
|
||||
|
||||
foreach (var arg in attribute.NamedArguments)
|
||||
{
|
||||
switch (arg.Key)
|
||||
{
|
||||
case "EventId":
|
||||
namedId = (int)(arg.Value.Value ?? 0);
|
||||
break;
|
||||
case "Level":
|
||||
namedLevel = (int?)arg.Value.Value;
|
||||
break;
|
||||
case "Message":
|
||||
namedMessage = (string?)arg.Value.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Decouple the location details from the syntax to allow caching and avoid holding onto syntax trees.
|
||||
var syntaxLocation = context.TargetNode.GetLocation();
|
||||
return new(context.TargetSymbol.Name,
|
||||
Location.Create(syntaxLocation.SourceTree!.FilePath, syntaxLocation.SourceSpan, syntaxLocation.GetLineSpan().Span),
|
||||
constructorId, constructorLevel, constructorMessage,
|
||||
namedId, namedLevel, namedMessage);
|
||||
}
|
||||
|
||||
public bool ReportDiagnostics(SourceProductionContext context)
|
||||
{
|
||||
var hasErrors = false;
|
||||
|
||||
if (ConstructorId is null && NamedId is null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.MissingPropertyRule, Location, "EventId"));
|
||||
hasErrors = true;
|
||||
}
|
||||
else if (ConstructorId is not null && NamedId is not null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.DuplicatePropertyRule, Location, "EventId"));
|
||||
hasErrors = true;
|
||||
}
|
||||
else if ((ConstructorId ?? NamedId) is <= 0 or > ushort.MaxValue)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.InvalidEventIdRule, Location, ConstructorId ?? NamedId));
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (ConstructorLevel is null && NamedLevel is null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.MissingPropertyRule, Location, "Level"));
|
||||
hasErrors = true;
|
||||
}
|
||||
else if (ConstructorLevel is not null && NamedLevel is not null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.DuplicatePropertyRule, Location, "Level"));
|
||||
hasErrors = true;
|
||||
}
|
||||
else if ((ConstructorLevel ?? NamedLevel) is < 0 or > 5)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.InvalidLevelRule, Location, ConstructorLevel ?? NamedLevel));
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (ConstructorMessage is null && NamedMessage is null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.MissingPropertyRule, Location, "Message"));
|
||||
hasErrors = true;
|
||||
}
|
||||
else if (ConstructorMessage is not null && NamedMessage is not null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.DuplicatePropertyRule, Location, "Message"));
|
||||
hasErrors = true;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(ConstructorMessage ?? NamedMessage))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.InvalidMessageRule, Location, ConstructorMessage ?? NamedMessage));
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
IsValid = !hasErrors;
|
||||
|
||||
return hasErrors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
LANG_ENGLISH
|
||||
SUBLANG_ENGLISH_US
|
||||
|
||||
VS_VERSION_INFO
|
||||
|
||||
VS_FFI_FILEFLAGSMASK
|
||||
|
||||
VS_FIXEDFILEINFO_FILE_FLAGS
|
||||
VS_FIXEDFILEINFO_FILE_OS
|
||||
VS_FIXEDFILEINFO_FILE_TYPE
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma warning disable IDE0130 // Namespace does not match folder structure
|
||||
namespace System.Runtime.CompilerServices;
|
||||
#pragma warning restore IDE0130 // Namespace does not match folder structure
|
||||
|
||||
public class IsExternalInit { }
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
|
||||
<Configurations>Release</Configurations>
|
||||
<Configuration>Release</Configuration>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="EmbeddedResources\**" />
|
||||
<EmbeddedResource Remove="EmbeddedResources\**\*.license" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>WindowsSDKBuildToolsBinVersionedArchFolder</_Parameter1>
|
||||
<_Parameter2>$(WindowsSDKBuildToolsBinVersionedArchFolder)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.8.2, )",
|
||||
"resolved": "6.8.2",
|
||||
"contentHash": "eTd8jUiogUXpVssFyDWWdMrPr5Cdc57tWaQ3Cv/eMIhnumOHZhDkc/wcy8hYU8aH7NS4VT2w/pHjVITDSTXMGw=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.CSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "r1DrKQ/L0xTw03wJrLr36AMQNslyaeEKBFyFmQcOKa8HX3YvmhY//JEUafb6IR/m0gmaUVCfBTWitKJRNb7YAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Analyzers": "5.3.0",
|
||||
"Microsoft.CodeAnalysis.Common": "[5.6.0]",
|
||||
"System.Buffers": "4.6.1",
|
||||
"System.Collections.Immutable": "10.0.1",
|
||||
"System.Memory": "4.6.3",
|
||||
"System.Numerics.Vectors": "4.6.1",
|
||||
"System.Reflection.Metadata": "10.0.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.1.2",
|
||||
"System.Text.Encoding.CodePages": "8.0.0",
|
||||
"System.Threading.Tasks.Extensions": "4.6.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.CsWin32": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.3.298, )",
|
||||
"resolved": "0.3.298",
|
||||
"contentHash": "moz/CojV5HSeJYV9pNVE6mWMKnJGvPgdD31EhBLyUJ8Ui06PBrH9oPFK1ljXbJJMM4EzKnkE1a0AtBbH1BHDGA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "70.0.11-preview",
|
||||
"Microsoft.Windows.WDK.Win32Metadata": "0.13.25-experimental"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.SDK.BuildTools": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.28000.2526, )",
|
||||
"resolved": "10.0.28000.2526",
|
||||
"contentHash": "voeXXRC5EKuvnUCkPxkEhCbNuv/FM1RzjCQ3lj58Ju5AlVzh/9Lg+a7hqy57mh/00BH/9YNZCJRUjIZJrzTJ4w=="
|
||||
},
|
||||
"NETStandard.Library": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.0.3, )",
|
||||
"resolved": "2.0.3",
|
||||
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.3.0",
|
||||
"contentHash": "KuLhbZwB0L8JikL86AE5VWEp3RLNjIcp+j8yz9EJ/UBgRz4+qDEjHg/tluRFbpYpD/e37BqaaNFbQ0vqawBwWQ=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "eWYNB5e92PSdkQ0xcmy2aLtrvBXNydnVi0Hj/VjaAely6XBqA3By+ClGAJaj4d16pzQmrXPLLK9RDVuS1Ec9xQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Analyzers": "5.3.0",
|
||||
"System.Buffers": "4.6.1",
|
||||
"System.Collections.Immutable": "10.0.1",
|
||||
"System.Memory": "4.6.3",
|
||||
"System.Numerics.Vectors": "4.6.1",
|
||||
"System.Reflection.Metadata": "10.0.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.1.2",
|
||||
"System.Text.Encoding.CodePages": "8.0.0",
|
||||
"System.Threading.Tasks.Extensions": "4.6.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Docs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.42-alpha",
|
||||
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "70.0.11-preview",
|
||||
"contentHash": "i8Dv2KCKzH8MQAsgLeUa5zFGyQOoz+TlvnbkFUkjsUtqhj4oguVj9rDpVACs5fLayZbwItOSllf+bhwr+z2yeg=="
|
||||
},
|
||||
"Microsoft.Windows.WDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.13.25-experimental",
|
||||
"contentHash": "IM50tb/+UIwBr9FMr6ZKcZjCMW+Axo6NjGqKxgjUfyCY8dRnYUfrJEXxAaXoWtYP4X8EmASmC1Jtwh4XucseZg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "63.0.31-preview"
|
||||
}
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.6.1",
|
||||
"contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
|
||||
},
|
||||
"System.Collections.Immutable": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.1",
|
||||
"contentHash": "kdTe61B8P7i2M1pODC3MLbZ/CfFGjpC6c6jzxjQoB5DHZNewayCRqgFUmx3JKB6vLQtozpMQEiw+R5fO32Jv4g==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.6.3",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
|
||||
}
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.6.3",
|
||||
"contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.6.1",
|
||||
"System.Numerics.Vectors": "4.6.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
|
||||
}
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.6.1",
|
||||
"contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.1",
|
||||
"contentHash": "zpcfT/wacPPhE17zcudozlxQtWN/84qyiMyZNGLnK4cj2IMBtLsZYwYjVnALUhPliwyUVj/P7kaZvBWYBCnf2Q==",
|
||||
"dependencies": {
|
||||
"System.Collections.Immutable": "10.0.1"
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.1.2",
|
||||
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
|
||||
},
|
||||
"System.Text.Encoding.CodePages": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.5",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Threading.Tasks.Extensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.6.3",
|
||||
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: 2026 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
@@ -22,6 +22,9 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
<File Path="Platform.Build.props" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="Win32ResourceGenerator/Win32ResourceGenerator.csproj">
|
||||
<BuildType Solution="Debug|*" Project="Release" />
|
||||
</Project>
|
||||
<Project Path="Installer/Installer.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0">
|
||||
<Build Project="false" />
|
||||
</Project>
|
||||
|
||||
Reference in new issue
Block a user