diff --git a/src/windows/wslc/commands/RootCommand.cpp b/src/windows/wslc/commands/RootCommand.cpp index d92c563de..129ec934d 100644 --- a/src/windows/wslc/commands/RootCommand.cpp +++ b/src/windows/wslc/commands/RootCommand.cpp @@ -21,8 +21,13 @@ Module Name: #include "SettingsCommand.h" #include "VersionCommand.h" +#include "wslutil.h" +#include "VersionService.h" + using namespace wsl::windows::wslc::execution; using namespace wsl::shared; +using namespace wsl::windows::common; +using namespace wsl::windows::wslc::services; namespace wsl::windows::wslc { std::vector> RootCommand::GetCommands() const @@ -79,7 +84,7 @@ void RootCommand::ExecuteInternal(CLIExecutionContext& context) const { if (context.Args.Contains(ArgType::Version)) { - VersionCommand::PrintVersion(); + wslutil::PrintMessage(VersionService::GetVersionString()); return; } diff --git a/src/windows/wslc/commands/VersionCommand.cpp b/src/windows/wslc/commands/VersionCommand.cpp index 63f99c106..91d6ab6a5 100644 --- a/src/windows/wslc/commands/VersionCommand.cpp +++ b/src/windows/wslc/commands/VersionCommand.cpp @@ -12,11 +12,20 @@ Module Name: --*/ #include "VersionCommand.h" +#include "VersionTasks.h" using namespace wsl::shared; using namespace wsl::windows::wslc::execution; +using namespace wsl::windows::wslc::task; namespace wsl::windows::wslc { +std::vector VersionCommand::GetArguments() const +{ + return { + Argument::Create(ArgType::Format), + }; +} + std::wstring VersionCommand::ShortDescription() const { return Localization::WSLCCLI_VersionDesc(); @@ -27,14 +36,9 @@ std::wstring VersionCommand::LongDescription() const return Localization::WSLCCLI_VersionLongDesc(); } -void VersionCommand::PrintVersion() -{ - wsl::windows::common::wslutil::PrintMessage(std::format(L"{} {}", s_ExecutableName, WSL_PACKAGE_VERSION)); -} - void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const { - UNREFERENCED_PARAMETER(context); - PrintVersion(); + context << GetVersionInfo // + << ListVersionInfo; } } // namespace wsl::windows::wslc diff --git a/src/windows/wslc/commands/VersionCommand.h b/src/windows/wslc/commands/VersionCommand.h index be700f550..7c2a87cff 100644 --- a/src/windows/wslc/commands/VersionCommand.h +++ b/src/windows/wslc/commands/VersionCommand.h @@ -21,7 +21,7 @@ struct VersionCommand final : public Command VersionCommand(const std::wstring& parent) : Command(CommandName, parent) { } - static void PrintVersion(); + std::vector GetArguments() const override; std::wstring ShortDescription() const override; std::wstring LongDescription() const override; diff --git a/src/windows/wslc/core/ExecutionContextData.h b/src/windows/wslc/core/ExecutionContextData.h index ca550670c..4a4d66ea7 100644 --- a/src/windows/wslc/core/ExecutionContextData.h +++ b/src/windows/wslc/core/ExecutionContextData.h @@ -15,6 +15,7 @@ Module Name: #include "EnumVariantMap.h" #include "ContainerModel.h" #include "ImageModel.h" +#include "VersionModel.h" #include "SessionModel.h" #include @@ -36,6 +37,7 @@ enum class Data : size_t Containers, ContainerOptions, Images, + Version, Max }; @@ -50,6 +52,7 @@ namespace details { DEFINE_DATA_MAPPING(Containers, std::vector); DEFINE_DATA_MAPPING(ContainerOptions, wsl::windows::wslc::models::ContainerOptions); DEFINE_DATA_MAPPING(Images, std::vector); + DEFINE_DATA_MAPPING(Version, wsl::windows::wslc::models::VersionInfo); } // namespace details struct DataMap : wsl::windows::wslc::EnumBasedVariantMap diff --git a/src/windows/wslc/services/VersionModel.h b/src/windows/wslc/services/VersionModel.h new file mode 100644 index 000000000..849a2276f --- /dev/null +++ b/src/windows/wslc/services/VersionModel.h @@ -0,0 +1,90 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VersionModel.h + +Abstract: + + This file contains the VersionModel definitions +--*/ + +#pragma once + +namespace wsl::windows::wslc::models { + +#define WSLC_VERSION_ROW(key, value) std::format("{:<20}{}\n", key ":", value) + +struct ClientVersion +{ + std::string Version = WSL_PACKAGE_VERSION; + std::string GitCommit = COMMIT_HASH; + std::string Built = __DATE__ " " __TIME__; + std::string Os = "windows"; + +#if defined(_AMD64_) + std::string Arch = "amd64"; +#elif defined(_ARM64_) + std::string Arch = "arm64"; +#else + std::string Arch = "unknown"; +#endif + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ClientVersion, Version, GitCommit, Built, Os, Arch); + + std::string ToString() const + { + std::stringstream ss; + ss << "Client:\n"; + ss << WSLC_VERSION_ROW(" Version", Version); + ss << WSLC_VERSION_ROW(" Git commit", GitCommit); + ss << WSLC_VERSION_ROW(" Built", Built); + ss << WSLC_VERSION_ROW(" OS/Arch", Os + "/" + Arch); + return ss.str(); + } +}; + +struct ServerVersion +{ + std::string Kernel = KERNEL_VERSION; + std::string WSLg = WSLG_VERSION; + std::string MSRDC = MSRDC_VERSION; + std::string Direct3D = DIRECT3D_VERSION; + std::string DXCore = DXCORE_VERSION; + std::string Windows = wsl::windows::common::helpers::GetWindowsVersionString(); + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ServerVersion, Kernel, WSLg, MSRDC, Direct3D, DXCore, Windows); + + std::string ToString() const + { + std::stringstream ss; + ss << "Server:\n"; + ss << WSLC_VERSION_ROW(" Linux kernel", Kernel); + ss << WSLC_VERSION_ROW(" WSLg", WSLg); + ss << WSLC_VERSION_ROW(" MSRDC", MSRDC); + ss << WSLC_VERSION_ROW(" Direct3D", Direct3D); + ss << WSLC_VERSION_ROW(" DXCore", DXCore); + ss << WSLC_VERSION_ROW(" Windows", Windows); + return ss.str(); + } +}; + +struct VersionInfo +{ + ClientVersion Client{}; + ServerVersion Server{}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VersionInfo, Client, Server); + + std::string ToString() const + { + std::stringstream ss; + ss << Client.ToString() << "\n"; + ss << Server.ToString(); + return ss.str(); + } +}; +#undef WSLC_VERSION_ROW +} // namespace wsl::windows::wslc::models diff --git a/src/windows/wslc/services/VersionService.cpp b/src/windows/wslc/services/VersionService.cpp new file mode 100644 index 000000000..352606cae --- /dev/null +++ b/src/windows/wslc/services/VersionService.cpp @@ -0,0 +1,33 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VersionService.cpp + +Abstract: + + This file contains the VersionService implementation +--*/ + +#include "precomp.h" +#include "VersionService.h" +#include "Command.h" + +namespace wsl::windows::wslc::services { + +using namespace wsl::shared::string; + +std::wstring VersionService::GetVersionString() +{ + return std::format(L"{} {}", s_ExecutableName, MultiByteToWide(VersionInfo().Client.Version)); +} + +const wsl::windows::wslc::models::VersionInfo& VersionService::VersionInfo() +{ + static const wsl::windows::wslc::models::VersionInfo s_versionInfo{}; + return s_versionInfo; +} + +} // namespace wsl::windows::wslc::services diff --git a/src/windows/wslc/services/VersionService.h b/src/windows/wslc/services/VersionService.h new file mode 100644 index 000000000..f252df5aa --- /dev/null +++ b/src/windows/wslc/services/VersionService.h @@ -0,0 +1,24 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VersionService.h + +Abstract: + + This file contains the VersionService definition +--*/ +#pragma once + +#include "VersionModel.h" +#include + +namespace wsl::windows::wslc::services { +struct VersionService +{ + static std::wstring GetVersionString(); + static const wsl::windows::wslc::models::VersionInfo& VersionInfo(); +}; +} // namespace wsl::windows::wslc::services diff --git a/src/windows/wslc/tasks/VersionTasks.cpp b/src/windows/wslc/tasks/VersionTasks.cpp new file mode 100644 index 000000000..97dd5cb7b --- /dev/null +++ b/src/windows/wslc/tasks/VersionTasks.cpp @@ -0,0 +1,66 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VersionTasks.cpp + +Abstract: + + Implementation of version command related execution logic. + +--*/ +#include "precomp.h" +#include "Argument.h" +#include "ArgumentValidation.h" +#include "ContainerModel.h" +#include "VersionTasks.h" +#include "VersionService.h" +#include "CLIExecutionContext.h" +#include +#include + +using namespace wsl::shared; +using namespace wsl::shared::string; +using namespace wsl::windows::wslc::execution; +using namespace wsl::windows::wslc::models; +using namespace wsl::windows::wslc::services; +using namespace wsl::windows::common::wslutil; + +namespace wsl::windows::wslc::task { + +void GetVersionInfo(CLIExecutionContext& context) +{ + context.Data.Add(VersionService::VersionInfo()); +} + +void ListVersionInfo(CLIExecutionContext& context) +{ + WI_ASSERT(context.Data.Contains(Data::Version)); + auto& versionInfo = context.Data.Get(); + + FormatType format = FormatType::Table; // Default is table + if (context.Args.Contains(ArgType::Format)) + { + format = validation::GetFormatTypeFromString(context.Args.Get()); + } + + switch (format) + { + case FormatType::Json: + { + auto json = ToJson(versionInfo, c_jsonPrettyPrintIndent); + PrintMessage(MultiByteToWide(json)); + break; + } + case FormatType::Table: + { + PrintMessage(MultiByteToWide(versionInfo.ToString())); + break; + } + default: + THROW_HR(E_UNEXPECTED); + } +} +} // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/VersionTasks.h b/src/windows/wslc/tasks/VersionTasks.h new file mode 100644 index 000000000..dccff573e --- /dev/null +++ b/src/windows/wslc/tasks/VersionTasks.h @@ -0,0 +1,22 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VersionTasks.h + +Abstract: + + Declaration of version command execution tasks. +--*/ +#pragma once +#include "CLIExecutionContext.h" +#include "Task.h" + +using wsl::windows::wslc::execution::CLIExecutionContext; + +namespace wsl::windows::wslc::task { +void GetVersionInfo(CLIExecutionContext& context); +void ListVersionInfo(CLIExecutionContext& context); +} // namespace wsl::windows::wslc::task diff --git a/test/windows/wslc/WSLCCLICommandUnitTests.cpp b/test/windows/wslc/WSLCCLICommandUnitTests.cpp index 7d58bf2b6..b73dd9e2f 100644 --- a/test/windows/wslc/WSLCCLICommandUnitTests.cpp +++ b/test/windows/wslc/WSLCCLICommandUnitTests.cpp @@ -142,13 +142,13 @@ class WSLCCLICommandUnitTests VERIFY_ARE_EQUAL(0u, cmd.GetCommands().size()); } - // Test: Verify VersionCommand has no arguments (only the auto-added --help) - TEST_METHOD(VersionCommand_HasNoArguments) + // Test: Verify VersionCommand has the expected arguments (--format, plus the auto-added --help) + TEST_METHOD(VersionCommand_HasFormatArgument) { auto cmd = VersionCommand(L"wslc"); - VERIFY_ARE_EQUAL(0u, cmd.GetArguments().size()); - // Test out that auto added help command is the only one - VERIFY_ARE_EQUAL(1u, cmd.GetAllArguments().size()); + VERIFY_ARE_EQUAL(1u, cmd.GetArguments().size()); + // Test out that --format and auto-added --help are the only arguments + VERIFY_ARE_EQUAL(2u, cmd.GetAllArguments().size()); } // Test: Verify RootCommand contains VersionCommand as a subcommand diff --git a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp index aaa6adf09..0de7372ea 100644 --- a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp +++ b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp @@ -97,6 +97,12 @@ class WSLCCLIExecutionUnitTests dataMap.Add(std::move(images)); handled = true; } + else if (dataType == Data::Version) + { + wsl::windows::wslc::models::VersionInfo versionInfo; + dataMap.Add(std::move(versionInfo)); + handled = true; + } if (!handled) { diff --git a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp index 94fa323f7..0e242946d 100644 --- a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp @@ -66,11 +66,6 @@ class WSLCE2EGlobalTests RunWslcAndVerify(L"INVALID_CMD", {.Stdout = GetHelpMessage(), .Stderr = L"Unrecognized command: 'INVALID_CMD'\r\n", .ExitCode = 1}); } - WSLC_TEST_METHOD(WSLCE2E_VersionCommand) - { - RunWslcAndVerify(L"version", {.Stdout = GetVersionMessage(), .Stderr = L"", .ExitCode = 0}); - } - WSLC_TEST_METHOD(WSLCE2E_VersionFlag) { RunWslcAndVerify(L"--version", {.Stdout = GetVersionMessage(), .Stderr = L"", .ExitCode = 0}); diff --git a/test/windows/wslc/e2e/WSLCE2EVersionTests.cpp b/test/windows/wslc/e2e/WSLCE2EVersionTests.cpp new file mode 100644 index 000000000..50a3904c4 --- /dev/null +++ b/test/windows/wslc/e2e/WSLCE2EVersionTests.cpp @@ -0,0 +1,101 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + WSLCE2EVersionTests.cpp + +Abstract: + + This file contains end-to-end tests for the wslc version command. +--*/ + +#include "precomp.h" +#include "windows/Common.h" +#include "VersionModel.h" +#include "WSLCExecutor.h" +#include "WSLCE2EHelpers.h" +#include "Argument.h" +#include "JsonUtils.h" + +namespace WSLCE2ETests { +using namespace wsl::shared; + +using namespace wsl::windows::wslc::models; + +class WSLCE2EVersionTests +{ + WSLC_TEST_CLASS(WSLCE2EVersionTests) + + WSLC_TEST_METHOD(WSLCE2E_Version_HelpCommand) + { + auto result = RunWslc(L"version --help"); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Version_DefaultFormatIsTable) + { + const auto result = RunWslc(L"version"); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stdout->find(L"Client:")); + VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stdout->find(L"Server:")); + } + + WSLC_TEST_METHOD(WSLCE2E_Version_TableFormatMatchesDefault) + { + const auto defaultResult = RunWslc(L"version"); + defaultResult.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto explicitResult = RunWslc(L"version --format table"); + explicitResult.Verify({.Stdout = defaultResult.Stdout, .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Version_JsonFormat) + { + const auto result = RunWslc(L"version --format json"); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout.has_value()); + + const auto versionInfoFromJson = wsl::shared::FromJson(result.Stdout->c_str()); + VERIFY_ARE_EQUAL(VersionInfo{}.Client.Version, versionInfoFromJson.Client.Version); + } + + WSLC_TEST_METHOD(WSLCE2E_Version_InvalidFormatOption) + { + const auto result = RunWslc(L"version --format yaml"); + result.Verify({.Stderr = L"Invalid format value: yaml is not a recognized format type. Supported format types are: json, table.\r\n", .ExitCode = 1}); + } + +private: + std::wstring GetHelpMessage() const + { + std::wstringstream output; + output << GetWslcHeader() // + << GetDescription() // + << GetUsage() // + << GetAvailableOptions(); + return output.str(); + } + + std::wstring GetDescription() const + { + return Localization::WSLCCLI_VersionLongDesc() + L"\r\n\r\n"; + } + + std::wstring GetUsage() const + { + return L"Usage: wslc version []\r\n\r\n"; + } + + std::wstring GetAvailableOptions() const + { + std::wstringstream options; + options << L"The following options are available:\r\n" + << L" --format Output formatting (json or table) (Default: table)\r\n" + << L" -h,--help Shows help about the selected command\r\n" + << L"\r\n"; + return options.str(); + } +}; +} // namespace WSLCE2ETests