From 6758d2d8f84845bf800caebf722666a5fb5f1799 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 09:52:54 +1000 Subject: [PATCH 1/7] Publish a platform-neutral .NET tool --- src/SeqCli/SeqCli.csproj | 10 +--------- test/SeqCli.Tests/SeqCli.Tests.csproj | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj index b62c6f98..e45b5fd7 100644 --- a/src/SeqCli/SeqCli.csproj +++ b/src/SeqCli/SeqCli.csproj @@ -16,20 +16,12 @@ false false false - true - true - true true + true $(DefineConstants);WINDOWS - - $(DefineConstants);OSX - - - $(DefineConstants);LINUX - $(DefineConstants);UNIX diff --git a/test/SeqCli.Tests/SeqCli.Tests.csproj b/test/SeqCli.Tests/SeqCli.Tests.csproj index 73d17553..15c2d9c0 100644 --- a/test/SeqCli.Tests/SeqCli.Tests.csproj +++ b/test/SeqCli.Tests/SeqCli.Tests.csproj @@ -2,19 +2,11 @@ net9.0 $(TargetFrameworks);net9.0-windows - true - true - true + true $(DefineConstants);WINDOWS - - $(DefineConstants);OSX - - - $(DefineConstants);LINUX - $(DefineConstants);UNIX From 486f7eab6118525cba4a52f1d7a01b44c5723686 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 10:06:20 +1000 Subject: [PATCH 2/7] Get rid of -windows TFM: service control dependencies have now been updated to support non-windows TFMs! :) --- build/Build.Windows.ps1 | 6 +--- .../Cli/Commands/Forwarder/InstallCommand.cs | 6 +--- .../Cli/Commands/Forwarder/RestartCommand.cs | 4 --- .../Cli/Commands/Forwarder/RunCommand.cs | 35 ++++++++++--------- .../Cli/Commands/Forwarder/StartCommand.cs | 4 --- .../Cli/Commands/Forwarder/StatusCommand.cs | 4 --- .../Cli/Commands/Forwarder/StopCommand.cs | 4 --- .../Commands/Forwarder/UninstallCommand.cs | 4 --- .../Cli/Features/ServiceCredentialsFeature.cs | 4 --- src/SeqCli/Cli/Features/StoragePathFeature.cs | 15 ++++---- .../Config/SeqCliEncryptionProviderConfig.cs | 7 ++-- .../Filesystem/System/SystemStoreDirectory.cs | 6 ++-- .../Forwarder/Filesystem/System/Unix/Libc.cs | 2 -- .../SeqCliForwarderWindowsService.cs | 4 --- .../Forwarder/Util/AccountRightsHelper.cs | 4 --- .../Forwarder/Util/ExecutionEnvironment.cs | 13 ++++--- .../Forwarder/Util/ServiceConfiguration.cs | 4 --- src/SeqCli/Forwarder/Util/WindowsProcess.cs | 6 +--- src/SeqCli/PlainText/Framing/FrameReader.cs | 5 ++- src/SeqCli/Program.cs | 4 +-- src/SeqCli/SeqCli.csproj | 9 +---- test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj | 3 +- .../Config/ExternalDataProtectorTests.cs | 9 +++-- .../SeqCliEncryptionProviderConfigTests.cs | 10 +++--- test/SeqCli.Tests/SeqCli.Tests.csproj | 10 +----- 25 files changed, 57 insertions(+), 125 deletions(-) diff --git a/build/Build.Windows.ps1 b/build/Build.Windows.ps1 index 90a13199..b55e2dbf 100644 --- a/build/Build.Windows.ps1 +++ b/build/Build.Windows.ps1 @@ -13,7 +13,6 @@ $version = Get-SemVer Write-Output "Building version $version" $framework = 'net9.0' -$windowsTfmSuffix = '-windows' function Clean-Output { @@ -28,7 +27,7 @@ function Restore-Packages function Execute-Tests($version) { - & dotnet test ./test/SeqCli.Tests/SeqCli.Tests.csproj -c Release --framework "$framework$windowsTfmSuffix" /p:Configuration=Release /p:Platform=x64 /p:VersionPrefix=$version + & dotnet test ./test/SeqCli.Tests/SeqCli.Tests.csproj -c Release --framework "$framework" /p:Configuration=Release /p:Platform=x64 /p:VersionPrefix=$version if($LASTEXITCODE -ne 0) { throw "Build failed" } } @@ -42,9 +41,6 @@ function Publish-Archives($version) $rids = $([xml](Get-Content .\src\SeqCli\SeqCli.csproj)).Project.PropertyGroup.RuntimeIdentifiers[0].Split(';') foreach ($rid in $rids) { $tfm = $framework - if ($rid -eq "win-x64") { - $tfm = "$tfm$windowsTfmSuffix" - } & dotnet publish ./src/SeqCli/SeqCli.csproj -c Release -f $tfm -r $rid --self-contained /p:VersionPrefix=$version if($LASTEXITCODE -ne 0) { throw "Build failed" } diff --git a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs index cbdcfde1..f59e4f16 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -100,7 +98,7 @@ void Install() if (netshResult != 0) Console.WriteLine($"Could not add URL reservation for {listenUri}: `netsh` returned {netshResult}; ignoring"); - var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Program.BinaryName); + var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Program.WindowsBinaryName); var forwarderRunCmdline = $"\"{exePath}\" forwarder run --pre --storage=\"{_storagePath.StorageRootPath}\""; var binPath = forwarderRunCmdline.Replace("\"", "\\\""); @@ -149,5 +147,3 @@ static string MakeListenUriReservationPattern(string uri) return listenUri; } } - -#endif diff --git a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs index 467ce1e1..1e4d6975 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.Diagnostics.CodeAnalysis; using System.ServiceProcess; @@ -80,5 +78,3 @@ protected override Task Run() } } } - -#endif diff --git a/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs index ad0816ef..ace247d8 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs @@ -17,6 +17,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Autofac; @@ -36,10 +37,8 @@ using Serilog.Events; using Serilog.Formatting.Compact; -#if WINDOWS using System.Security.Cryptography.X509Certificates; using SeqCli.Forwarder.ServiceProcess; -#endif // ReSharper disable UnusedType.Global @@ -134,12 +133,16 @@ protected override async Task Run(string[] unrecognized) { options.Listen(ipAddress, apiListenUri.Port, listenOptions => { -#if WINDOWS - listenOptions.UseHttps(StoreName.My, apiListenUri.Host, - location: StoreLocation.LocalMachine, allowInvalid: true); -#else - listenOptions.UseHttps(); -#endif + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + listenOptions.UseHttps(StoreName.My, apiListenUri.Host, + location: StoreLocation.LocalMachine, allowInvalid: true); + } + else + { + listenOptions.UseHttps(); + } + }); } else @@ -193,14 +196,14 @@ protected override async Task Run(string[] unrecognized) // ReSharper disable once UnusedParameter.Local static int RunService(ServerService service) { -#if WINDOWS - System.ServiceProcess.ServiceBase.Run([ - new SeqCliForwarderWindowsService(service) - ]); - return 0; -#else - throw new NotSupportedException("Windows services are not supported on this platform."); -#endif + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + throw new NotSupportedException("Windows services are not supported on this platform."); + + + System.ServiceProcess.ServiceBase.Run([ + new SeqCliForwarderWindowsService(service) + ]); + return 0; } static async Task RunStandardIOAsync(ServerService service, TextWriter cout) diff --git a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs index 1eb7fb80..825f41e4 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.Diagnostics.CodeAnalysis; using System.ServiceProcess; @@ -64,5 +62,3 @@ protected override Task Run() } } } - -#endif diff --git a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs index 5339defa..2c320eb7 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.Diagnostics.CodeAnalysis; using System.ServiceProcess; @@ -48,5 +46,3 @@ protected override Task Run() return Task.FromResult(1); } } - -#endif diff --git a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs index b86695b4..3b75fee7 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.Diagnostics.CodeAnalysis; using System.ServiceProcess; @@ -65,5 +63,3 @@ protected override Task Run() } } } - -#endif diff --git a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs index 80e9ffbc..2f234c86 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.IO; using System.Threading.Tasks; @@ -46,5 +44,3 @@ protected override Task Run() } } } - -#endif diff --git a/src/SeqCli/Cli/Features/ServiceCredentialsFeature.cs b/src/SeqCli/Cli/Features/ServiceCredentialsFeature.cs index d82b15a8..f7687afa 100644 --- a/src/SeqCli/Cli/Features/ServiceCredentialsFeature.cs +++ b/src/SeqCli/Cli/Features/ServiceCredentialsFeature.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using SeqCli.Cli; namespace SeqCli.Forwarder.Cli.Features @@ -38,5 +36,3 @@ public override void Enable(OptionSet options) } } } - -#endif diff --git a/src/SeqCli/Cli/Features/StoragePathFeature.cs b/src/SeqCli/Cli/Features/StoragePathFeature.cs index 66f68baa..9fa15cb8 100644 --- a/src/SeqCli/Cli/Features/StoragePathFeature.cs +++ b/src/SeqCli/Cli/Features/StoragePathFeature.cs @@ -1,9 +1,7 @@ using System; using System.IO; - -#if WINDOWS +using System.Runtime.InteropServices; using SeqCli.Forwarder.ServiceProcess; -#endif namespace SeqCli.Cli.Features; @@ -63,12 +61,13 @@ static string GetDefaultStorageRoot() static string? TryQueryInstalledStorageRoot() { -#if WINDOWS - if (Forwarder.Util.ServiceConfiguration.GetServiceStoragePath( - SeqCliForwarderWindowsService.WindowsServiceName, out var storage)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Forwarder.Util.ServiceConfiguration.GetServiceStoragePath( + SeqCliForwarderWindowsService.WindowsServiceName, out var storage)) + { return storage; -#endif - + } + return null; } } \ No newline at end of file diff --git a/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs b/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs index 492b4a16..8dde00c4 100644 --- a/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs +++ b/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.Runtime.InteropServices; using SeqCli.Encryptor; namespace SeqCli.Config; @@ -38,10 +39,6 @@ public IDataProtector DataProtector() return new ExternalDataProtector(Encryptor, EncryptorArgs, Decryptor, DecryptorArgs); } -#if WINDOWS - return new WindowsNativeDataProtector(); -#else - return new PlaintextDataProtector(); -#endif + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new WindowsNativeDataProtector() : new PlaintextDataProtector(); } } \ No newline at end of file diff --git a/src/SeqCli/Forwarder/Filesystem/System/SystemStoreDirectory.cs b/src/SeqCli/Forwarder/Filesystem/System/SystemStoreDirectory.cs index 5a38ac98..695666d0 100644 --- a/src/SeqCli/Forwarder/Filesystem/System/SystemStoreDirectory.cs +++ b/src/SeqCli/Forwarder/Filesystem/System/SystemStoreDirectory.cs @@ -21,9 +21,7 @@ using SeqCli.Config; using Serilog; -#if UNIX using SeqCli.Forwarder.Filesystem.System.Unix; -#endif namespace SeqCli.Forwarder.Filesystem.System; @@ -145,7 +143,8 @@ public override StoreFile ReplaceContents(string name, Span contents, bool static void Dirsync(string directoryPath) { -#if UNIX + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + var dir = Libc.open(directoryPath, 0); if (dir == -1) return; @@ -155,6 +154,5 @@ static void Dirsync(string directoryPath) Libc.fsync(dir); Libc.close(dir); #pragma warning restore CA1806 -#endif } } diff --git a/src/SeqCli/Forwarder/Filesystem/System/Unix/Libc.cs b/src/SeqCli/Forwarder/Filesystem/System/Unix/Libc.cs index 4561a20a..38393514 100644 --- a/src/SeqCli/Forwarder/Filesystem/System/Unix/Libc.cs +++ b/src/SeqCli/Forwarder/Filesystem/System/Unix/Libc.cs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if UNIX using System.Runtime.InteropServices; namespace SeqCli.Forwarder.Filesystem.System.Unix; @@ -28,4 +27,3 @@ static class Libc [DllImport("libc")] public static extern int fsync(int fd); } -#endif diff --git a/src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs b/src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs index 43a21b17..6b83458f 100644 --- a/src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs +++ b/src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System.Diagnostics.CodeAnalysis; using System.Net; using System.ServiceProcess; @@ -46,5 +44,3 @@ protected override void OnStop() } } } - -#endif diff --git a/src/SeqCli/Forwarder/Util/AccountRightsHelper.cs b/src/SeqCli/Forwarder/Util/AccountRightsHelper.cs index 9074c532..30b8df18 100644 --- a/src/SeqCli/Forwarder/Util/AccountRightsHelper.cs +++ b/src/SeqCli/Forwarder/Util/AccountRightsHelper.cs @@ -3,8 +3,6 @@ // http://www.codeproject.com/Articles/4863/LSA-Functions-Privileges-and-Impersonation // Modified and reformatted. -#if WINDOWS - using System; using System.Runtime.InteropServices; using System.Text; @@ -189,5 +187,3 @@ public static void EnsureServiceLogOnRights(string accountName) } } } - -#endif diff --git a/src/SeqCli/Forwarder/Util/ExecutionEnvironment.cs b/src/SeqCli/Forwarder/Util/ExecutionEnvironment.cs index 4025c3eb..47171fa1 100644 --- a/src/SeqCli/Forwarder/Util/ExecutionEnvironment.cs +++ b/src/SeqCli/Forwarder/Util/ExecutionEnvironment.cs @@ -1,6 +1,4 @@ -#if WINDOWS -using SeqCli.Forwarder.Util; -#endif +using System.Runtime.InteropServices; namespace SeqCli.Forwarder.Util; @@ -12,12 +10,13 @@ static bool IsRunningAsWindowsService { get { -#if WINDOWS + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { var parent = WindowsProcess.GetParentProcess(); return parent?.ProcessName == "services"; -#else + } + return false; -#endif } } -} \ No newline at end of file +} diff --git a/src/SeqCli/Forwarder/Util/ServiceConfiguration.cs b/src/SeqCli/Forwarder/Util/ServiceConfiguration.cs index 598a2e3c..16ee8b4f 100644 --- a/src/SeqCli/Forwarder/Util/ServiceConfiguration.cs +++ b/src/SeqCli/Forwarder/Util/ServiceConfiguration.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if WINDOWS - using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -108,5 +106,3 @@ public static bool GetServiceStoragePath(string serviceName, out string? storage } } -#endif - diff --git a/src/SeqCli/Forwarder/Util/WindowsProcess.cs b/src/SeqCli/Forwarder/Util/WindowsProcess.cs index 8e4d96a2..e97b1add 100644 --- a/src/SeqCli/Forwarder/Util/WindowsProcess.cs +++ b/src/SeqCli/Forwarder/Util/WindowsProcess.cs @@ -1,6 +1,4 @@ -#if WINDOWS - -using System; +using System; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; @@ -47,5 +45,3 @@ readonly struct PROCESS_BASIC_INFORMATION } } } - -#endif diff --git a/src/SeqCli/PlainText/Framing/FrameReader.cs b/src/SeqCli/PlainText/Framing/FrameReader.cs index d0f769e0..3488b3ec 100644 --- a/src/SeqCli/PlainText/Framing/FrameReader.cs +++ b/src/SeqCli/PlainText/Framing/FrameReader.cs @@ -14,6 +14,7 @@ using System; using System.IO; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Superpower; @@ -36,13 +37,11 @@ public FrameReader(TextReader source, TextParser frameStart, TimeSpan _frameStart = frameStart ?? throw new ArgumentNullException(nameof(frameStart)); _trailingLineArrivalDeadline = trailingLineArrivalDeadline; -#if WINDOWS // Somehow, PowerShell manages to send a UTF-8 BOM when piping a command's output to us // via STDIN, regardless of how we set Console.InputEncoding. This hackily skips the BOM, // while we all live in hope of some brighter future. - if (_source.Peek() == 65279) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _source.Peek() == 65279) _source.Read(); -#endif } public async Task TryReadAsync() diff --git a/src/SeqCli/Program.cs b/src/SeqCli/Program.cs index 9b4f8ffe..c6e12e12 100644 --- a/src/SeqCli/Program.cs +++ b/src/SeqCli/Program.cs @@ -27,9 +27,7 @@ namespace SeqCli; class Program { -#if WINDOWS - public const string BinaryName = "seqcli.exe"; -#endif + public const string WindowsBinaryName = "seqcli.exe"; static async Task Main(string[] args) { diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj index e45b5fd7..b6caa5e6 100644 --- a/src/SeqCli/SeqCli.csproj +++ b/src/SeqCli/SeqCli.csproj @@ -1,7 +1,7 @@  Exe - net9.0;net9.0-windows + net9.0 seqcli ..\..\asset\SeqCli.ico win-x64;linux-x64;linux-musl-x64;osx-x64;linux-arm64;linux-musl-arm64;osx-arm64 @@ -17,13 +17,6 @@ false false true - true - - - $(DefineConstants);WINDOWS - - - $(DefineConstants);UNIX diff --git a/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj b/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj index d9a5a806..797cf88c 100644 --- a/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj +++ b/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj @@ -2,8 +2,7 @@ Exe - net9.0 - $(TargetFrameworks);net9.0-windows + net9.0 diff --git a/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs b/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs index 1b14fa4c..f86133e5 100644 --- a/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs +++ b/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.IO; +using System.Runtime.InteropServices; using System.Text; using SeqCli.Encryptor; using SeqCli.Tests.Support; @@ -17,10 +18,11 @@ public void IfEncryptorDoesNotExistEncryptThrows() Assert.Throws(() => protector.Encrypt(Some.Bytes(200))); } -#if UNIX [Fact] public void IfEncryptorFailsEncryptThrows() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + var protector = new ExternalDataProtector("bash", "-c \"exit 1\"", Some.String(), null); // May be `Exception` or `IOException`. Assert.ThrowsAny(() => protector.Encrypt(Some.Bytes(200))); @@ -29,6 +31,8 @@ public void IfEncryptorFailsEncryptThrows() [Fact] public void EncryptCallsEncryptor() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + const string prefix = "123"; var encoding = new UTF8Encoding(false); @@ -47,6 +51,8 @@ public void EncryptCallsEncryptor() [Fact] public void EncryptionRoundTrips() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + const string echo = "bash"; const string echoArgs = "-c \"cat -\""; var protector = new ExternalDataProtector(echo, echoArgs, echo, echoArgs); @@ -54,5 +60,4 @@ public void EncryptionRoundTrips() var actual = protector.Decrypt(protector.Encrypt(expected)); Assert.Equal(expected, actual); } -#endif } diff --git a/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs b/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs index 3f171c97..aafaf681 100644 --- a/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs +++ b/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs @@ -8,27 +8,27 @@ namespace SeqCli.Tests.Config; public class SeqCliEncryptionProviderConfigTests { -#if WINDOWS [Fact] public void DefaultDataProtectorOnWindowsIsDpapi() { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); var config = new SeqCliEncryptionProviderConfig(); var provider = config.DataProtector(); Assert.IsType(provider); } -#else + [Fact] public void DefaultDataProtectorOnUnixIsPlaintext() { - Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var config = new SeqCliEncryptionProviderConfig(); var provider = config.DataProtector(); Assert.IsType(provider); } -#endif [Fact] public void SpecifyingEncryptorRequiresDecryptor() @@ -63,4 +63,4 @@ public void SpecifyingEncryptorAndDecryptorActivatesExternalDataProtector() Assert.IsType(config.DataProtector()); } -} \ No newline at end of file +} diff --git a/test/SeqCli.Tests/SeqCli.Tests.csproj b/test/SeqCli.Tests/SeqCli.Tests.csproj index 15c2d9c0..ee5e4a40 100644 --- a/test/SeqCli.Tests/SeqCli.Tests.csproj +++ b/test/SeqCli.Tests/SeqCli.Tests.csproj @@ -1,14 +1,6 @@  - net9.0 - $(TargetFrameworks);net9.0-windows - true - - - $(DefineConstants);WINDOWS - - - $(DefineConstants);UNIX + net9.0 From 272f23271cda73c92626eeca9b439299bf2fe57c Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 10:26:47 +1000 Subject: [PATCH 3/7] Include platform support and preview status in generated Markdown help --- src/SeqCli/Cli/CommandAttribute.cs | 4 ++- src/SeqCli/Cli/CommandLineHost.cs | 7 ++++- src/SeqCli/Cli/CommandMetadata.cs | 1 + .../Cli/Commands/Forwarder/InstallCommand.cs | 3 +- .../Cli/Commands/Forwarder/RestartCommand.cs | 2 +- .../Cli/Commands/Forwarder/StartCommand.cs | 2 +- .../Cli/Commands/Forwarder/StatusCommand.cs | 2 +- .../Cli/Commands/Forwarder/StopCommand.cs | 2 +- .../Commands/Forwarder/UninstallCommand.cs | 2 +- src/SeqCli/Cli/Commands/HelpCommand.cs | 30 ++++++++++++++++--- src/SeqCli/Cli/SupportedPlatforms.cs | 12 ++++++++ 11 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/SeqCli/Cli/SupportedPlatforms.cs diff --git a/src/SeqCli/Cli/CommandAttribute.cs b/src/SeqCli/Cli/CommandAttribute.cs index 95aaf184..60facce0 100644 --- a/src/SeqCli/Cli/CommandAttribute.cs +++ b/src/SeqCli/Cli/CommandAttribute.cs @@ -24,16 +24,18 @@ public class CommandAttribute : Attribute, ICommandMetadata public string HelpText { get; } public string? Example { get; set; } public FeatureVisibility Visibility { get; set; } + public SupportedPlatforms Platforms { get; set; } public CommandAttribute(string name, string helpText) { Name = name; HelpText = helpText; Visibility = FeatureVisibility.Visible; + Platforms = SupportedPlatforms.All; } public CommandAttribute(string name, string subCommand, string helpText) : this(name, helpText) { SubCommand = subCommand; } -} \ No newline at end of file +} diff --git a/src/SeqCli/Cli/CommandLineHost.cs b/src/SeqCli/Cli/CommandLineHost.cs index 7bf7d661..4d9084c6 100644 --- a/src/SeqCli/Cli/CommandLineHost.cs +++ b/src/SeqCli/Cli/CommandLineHost.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Autofac.Features.Metadata; using Serilog.Core; @@ -55,8 +56,12 @@ public async Task Run(string[] args, LoggingLevelSwitch levelSwitch) if (args.Any(a => a.Trim() is prereleaseArg)) featureVisibility |= FeatureVisibility.Preview; + var currentPlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? SupportedPlatforms.Windows + : SupportedPlatforms.Unix; + var cmd = _availableCommands.SingleOrDefault(c => - featureVisibility.HasFlag(c.Metadata.Visibility) && + c.Metadata.Platforms.HasFlag(currentPlatform) && featureVisibility.HasFlag(c.Metadata.Visibility) && c.Metadata.Name == commandName && (c.Metadata.SubCommand == subCommandName || c.Metadata.SubCommand == null)); diff --git a/src/SeqCli/Cli/CommandMetadata.cs b/src/SeqCli/Cli/CommandMetadata.cs index e062236b..cc7bf027 100644 --- a/src/SeqCli/Cli/CommandMetadata.cs +++ b/src/SeqCli/Cli/CommandMetadata.cs @@ -21,4 +21,5 @@ public class CommandMetadata : ICommandMetadata public required string HelpText { get; set; } public string? Example { get; set; } public FeatureVisibility Visibility { get; set; } + public SupportedPlatforms Platforms { get; set; } } diff --git a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs index f59e4f16..fadb3a80 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs @@ -15,6 +15,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Threading.Tasks; using SeqCli.Cli.Features; @@ -27,7 +28,7 @@ namespace SeqCli.Cli.Commands.Forwarder; // ReSharper disable once ClassNeverInstantiated.Global -[Command("forwarder", "install", "Install the forwarder as a Windows service", Visibility = FeatureVisibility.Preview)] +[Command("forwarder", "install", "Install the forwarder as a Windows service", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.Windows)] [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] class InstallCommand : Command { diff --git a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs index 1e4d6975..d6938a4b 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs @@ -22,7 +22,7 @@ namespace SeqCli.Cli.Commands.Forwarder; -[Command("forwarder", "restart", "Restart the forwarder Windows service", Visibility = FeatureVisibility.Preview)] +[Command("forwarder", "restart", "Restart the forwarder Windows service", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.Windows)] [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] class RestartCommand : Command { diff --git a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs index 825f41e4..1e6b54b4 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs @@ -20,7 +20,7 @@ namespace SeqCli.Cli.Commands.Forwarder; -[Command("forwarder", "start", "Start the forwarder Windows service", Visibility = FeatureVisibility.Preview)] +[Command("forwarder", "start", "Start the forwarder Windows service", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.Windows)] [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] class StartCommand : Command { diff --git a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs index 2c320eb7..c148a6f3 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs @@ -20,7 +20,7 @@ namespace SeqCli.Cli.Commands.Forwarder; -[Command("forwarder", "status", "Show the status of the forwarder Windows service", Visibility = FeatureVisibility.Preview)] +[Command("forwarder", "status", "Show the status of the forwarder Windows service", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.Windows)] [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] class StatusCommand : Command { diff --git a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs index 3b75fee7..c26c2785 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs @@ -20,7 +20,7 @@ namespace SeqCli.Cli.Commands.Forwarder; -[Command("forwarder", "stop", "Stop the forwarder Windows service", Visibility = FeatureVisibility.Preview)] +[Command("forwarder", "stop", "Stop the forwarder Windows service", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.Windows)] [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] class StopCommand : Command { diff --git a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs index 2f234c86..a72c9e09 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs @@ -20,7 +20,7 @@ namespace SeqCli.Cli.Commands.Forwarder; -[Command("forwarder", "uninstall", "Uninstall the forwarder Windows service", Visibility = FeatureVisibility.Preview)] +[Command("forwarder", "uninstall", "Uninstall the forwarder Windows service", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.Windows)] class UninstallCommand : Command { protected override Task Run() diff --git a/src/SeqCli/Cli/Commands/HelpCommand.cs b/src/SeqCli/Cli/Commands/HelpCommand.cs index b274e4bd..1e478157 100644 --- a/src/SeqCli/Cli/Commands/HelpCommand.cs +++ b/src/SeqCli/Cli/Commands/HelpCommand.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Autofac.Features.Metadata; using CommandList = System.Collections.Generic.List, SeqCli.Cli.CommandMetadata>>; @@ -25,10 +26,14 @@ namespace SeqCli.Cli.Commands; [Command("help", "Show information about available commands", Example = "seqcli help search")] class HelpCommand : Command { + readonly SupportedPlatforms _currentPlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? SupportedPlatforms.Windows + : SupportedPlatforms.Unix; + readonly IEnumerable, CommandMetadata>> _allCommands; bool _markdown; FeatureVisibility _included = FeatureVisibility.Visible; - + public HelpCommand(IEnumerable, CommandMetadata>> allCommands) { _allCommands = allCommands.OrderBy(c => c.Metadata.Name).ToList(); @@ -38,12 +43,12 @@ public HelpCommand(IEnumerable, CommandMetadata>> allCommands Options.Add("m|markdown", "Generate markdown for use in documentation", _ => _markdown = true); } - IEnumerable, CommandMetadata>> AvailableCommands() => - _allCommands.Where(c => _included.HasFlag(c.Metadata.Visibility)); + IEnumerable, CommandMetadata>> AvailableCommands(SupportedPlatforms platform, FeatureVisibility visibility) => + _allCommands.Where(c => (c.Metadata.Platforms & platform) != SupportedPlatforms.None && visibility.HasFlag(c.Metadata.Visibility)); protected override Task Run(string[] unrecognized) { - var orderedCommands = AvailableCommands() + var orderedCommands = (_markdown ? AvailableCommands(SupportedPlatforms.All, FeatureVisibility.Visible | FeatureVisibility.Preview) : AvailableCommands(_currentPlatform, _included)) .OrderBy(c => c.Metadata.Name) .ThenBy(c => c.Metadata.SubCommand) .ToList(); @@ -142,6 +147,23 @@ static void PrintMarkdownHelp(string executableName, CommandList orderedCommands else Console.WriteLine($"### `{cmd.Metadata.Name}`"); Console.WriteLine(); + if (cmd.Metadata.Platforms != SupportedPlatforms.All || + cmd.Metadata.Visibility == FeatureVisibility.Preview) + { + Console.Write(">"); + if (cmd.Metadata.Visibility == FeatureVisibility.Preview) + { + Console.Write(" Preview command: only available when the `--pre` command-line flag is specified."); + } + + if (cmd.Metadata.Platforms != SupportedPlatforms.All) + { + Console.Write($" This command is supported on **{cmd.Metadata.Platforms}** platforms only."); + } + Console.WriteLine(); + Console.WriteLine(); + } + Console.WriteLine(cmd.Metadata.HelpText + "."); Console.WriteLine(); diff --git a/src/SeqCli/Cli/SupportedPlatforms.cs b/src/SeqCli/Cli/SupportedPlatforms.cs new file mode 100644 index 00000000..d9a998bb --- /dev/null +++ b/src/SeqCli/Cli/SupportedPlatforms.cs @@ -0,0 +1,12 @@ +using System; + +namespace SeqCli.Cli; + +[Flags] +public enum SupportedPlatforms +{ + None, + Windows, + Unix, + All = Windows | Unix +} \ No newline at end of file From 5bc300319ad5e3f40efc3123322978604d4b0a98 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 10:30:29 +1000 Subject: [PATCH 4/7] Fix tests --- test/SeqCli.Tests/Cli/CommandLineHostTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/SeqCli.Tests/Cli/CommandLineHostTests.cs b/test/SeqCli.Tests/Cli/CommandLineHostTests.cs index 73586db1..fd100f2e 100644 --- a/test/SeqCli.Tests/Cli/CommandLineHostTests.cs +++ b/test/SeqCli.Tests/Cli/CommandLineHostTests.cs @@ -21,10 +21,10 @@ public async Task CommandLineHostPicksCorrectCommand() { new( new Lazy(() => new ActionCommand(() => executed.Add("test"))), - new CommandMetadata {Name = "test", HelpText = "help"}), + new CommandMetadata {Name = "test", HelpText = "help", Platforms = SupportedPlatforms.All}), new( new Lazy(() => new ActionCommand(() => executed.Add("test2"))), - new CommandMetadata {Name = "test2", HelpText = "help"}) + new CommandMetadata {Name = "test2", HelpText = "help", Platforms = SupportedPlatforms.All}) }; var commandLineHost = new CommandLineHost(availableCommands); await commandLineHost.Run(["test"],new LoggingLevelSwitch()); @@ -40,7 +40,7 @@ public async Task PrereleaseCommandsAreIgnoredWithoutFlag() { new( new Lazy(() => new ActionCommand(() => executed.Add("test"))), - new CommandMetadata {Name = "test", HelpText = "help", Visibility = FeatureVisibility.Preview}), + new CommandMetadata {Name = "test", HelpText = "help", Visibility = FeatureVisibility.Preview, Platforms = SupportedPlatforms.All}), }; var commandLineHost = new CommandLineHost(availableCommands); var exit = await commandLineHost.Run(["test"],new LoggingLevelSwitch()); @@ -61,10 +61,10 @@ public async Task WhenMoreThanOneSubcommandAndTheUserRunsWithSubcommandEnsurePic { new( new Lazy(() => new ActionCommand(() => executed.Add("test-subcommand1"))), - new CommandMetadata {Name = "test", SubCommand = "subcommand1", HelpText = "help"}), + new CommandMetadata {Name = "test", SubCommand = "subcommand1", HelpText = "help", Platforms = SupportedPlatforms.All}), new( new Lazy(() => new ActionCommand(() => executed.Add("test-subcommand2"))), - new CommandMetadata {Name = "test", SubCommand = "subcommand2", HelpText = "help"}) + new CommandMetadata {Name = "test", SubCommand = "subcommand2", HelpText = "help", Platforms = SupportedPlatforms.All}) }; var commandLineHost = new CommandLineHost(availableCommands); await commandLineHost.Run(["test", "subcommand2"], new LoggingLevelSwitch()); @@ -82,7 +82,7 @@ public async Task VerboseOptionSetsLoggingLevelToInformation() { new( new Lazy(() => new ActionCommand(() => { })), - new CommandMetadata {Name = "test", HelpText = "help"}) + new CommandMetadata {Name = "test", HelpText = "help", Platforms = SupportedPlatforms.All}) }; var commandLineHost = new CommandLineHost(availableCommands); From 76ef7e90e8930e0a558452a188a03c323905eaed Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 10:54:20 +1000 Subject: [PATCH 5/7] Preview commands are now documented --- test/SeqCli.EndToEnd/Help/MarkdownHelpTestCase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/SeqCli.EndToEnd/Help/MarkdownHelpTestCase.cs b/test/SeqCli.EndToEnd/Help/MarkdownHelpTestCase.cs index 9177492b..bfecf1ff 100644 --- a/test/SeqCli.EndToEnd/Help/MarkdownHelpTestCase.cs +++ b/test/SeqCli.EndToEnd/Help/MarkdownHelpTestCase.cs @@ -24,8 +24,6 @@ public Task ExecuteAsync(SeqConnection connection, ILogger logger, CliCommandRun var indexOfTemplateImport = markdown.IndexOf("### `template import`", StringComparison.Ordinal); Assert.NotEqual(indexOfTemplateExport, indexOfTemplateImport); Assert.True(indexOfTemplateExport < indexOfTemplateImport); - - Assert.DoesNotContain("### `forwarder run`", markdown); return Task.CompletedTask; } From 80e0308218974bf3c348cdf09d5355c4c6223799 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 11:02:04 +1000 Subject: [PATCH 6/7] Windows build RID splitting --- build/Build.Windows.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Build.Windows.ps1 b/build/Build.Windows.ps1 index b55e2dbf..d3e803cf 100644 --- a/build/Build.Windows.ps1 +++ b/build/Build.Windows.ps1 @@ -38,7 +38,7 @@ function Create-ArtifactDir function Publish-Archives($version) { - $rids = $([xml](Get-Content .\src\SeqCli\SeqCli.csproj)).Project.PropertyGroup.RuntimeIdentifiers[0].Split(';') + $rids = $([xml](Get-Content .\src\SeqCli\SeqCli.csproj)).Project.PropertyGroup.RuntimeIdentifiers.Split(';') foreach ($rid in $rids) { $tfm = $framework From 6c58c4a4240dd29e01633872b03a1764506eec78 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 24 Jul 2025 11:20:35 +1000 Subject: [PATCH 7/7] Unix -> Posix --- src/SeqCli/Cli/CommandLineHost.cs | 2 +- src/SeqCli/Cli/Commands/HelpCommand.cs | 2 +- src/SeqCli/Cli/SupportedPlatforms.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SeqCli/Cli/CommandLineHost.cs b/src/SeqCli/Cli/CommandLineHost.cs index 4d9084c6..d48eb561 100644 --- a/src/SeqCli/Cli/CommandLineHost.cs +++ b/src/SeqCli/Cli/CommandLineHost.cs @@ -58,7 +58,7 @@ public async Task Run(string[] args, LoggingLevelSwitch levelSwitch) var currentPlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? SupportedPlatforms.Windows - : SupportedPlatforms.Unix; + : SupportedPlatforms.Posix; var cmd = _availableCommands.SingleOrDefault(c => c.Metadata.Platforms.HasFlag(currentPlatform) && featureVisibility.HasFlag(c.Metadata.Visibility) && diff --git a/src/SeqCli/Cli/Commands/HelpCommand.cs b/src/SeqCli/Cli/Commands/HelpCommand.cs index 1e478157..fe502899 100644 --- a/src/SeqCli/Cli/Commands/HelpCommand.cs +++ b/src/SeqCli/Cli/Commands/HelpCommand.cs @@ -28,7 +28,7 @@ class HelpCommand : Command { readonly SupportedPlatforms _currentPlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? SupportedPlatforms.Windows - : SupportedPlatforms.Unix; + : SupportedPlatforms.Posix; readonly IEnumerable, CommandMetadata>> _allCommands; bool _markdown; diff --git a/src/SeqCli/Cli/SupportedPlatforms.cs b/src/SeqCli/Cli/SupportedPlatforms.cs index d9a998bb..ec5ff4d9 100644 --- a/src/SeqCli/Cli/SupportedPlatforms.cs +++ b/src/SeqCli/Cli/SupportedPlatforms.cs @@ -7,6 +7,6 @@ public enum SupportedPlatforms { None, Windows, - Unix, - All = Windows | Unix + Posix, + All = Windows | Posix } \ No newline at end of file