From 1d2b39081e2af305b5c329a125a110229fe2a75c Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:00:04 -0700 Subject: [PATCH 01/15] Init template support --- src/windows/wslc/CMakeLists.txt | 37 ++++++++- .../wslc/arguments/ArgumentValidation.cpp | 10 +-- src/windows/wslc/commands/ContainerCommand.h | 1 - .../wslc/commands/ContainerListCommand.cpp | 12 --- src/windows/wslc/core/TemplateRenderer.cpp | 56 +++++++++++++ src/windows/wslc/core/TemplateRenderer.h | 30 +++++++ src/windows/wslc/services/ContainerModel.h | 1 + src/windows/wslc/tasks/ContainerTasks.cpp | 24 ++++++ src/windows/wslc/tools/gotemplate/render.def | 3 + src/windows/wslc/tools/gotemplate/render.go | 79 +++++++++++++++++++ 10 files changed, 233 insertions(+), 20 deletions(-) create mode 100644 src/windows/wslc/core/TemplateRenderer.cpp create mode 100644 src/windows/wslc/core/TemplateRenderer.h create mode 100644 src/windows/wslc/tools/gotemplate/render.def create mode 100644 src/windows/wslc/tools/gotemplate/render.go diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index b1552aa9a..08f180c26 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -6,6 +6,33 @@ list(TRANSFORM WSLC_SUBDIR_PATHS APPEND /*.cpp OUTPUT_VARIABLE SOURCE_PATTERNS) file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${HEADER_PATTERNS}) file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${SOURCE_PATTERNS}) +# Build Go template renderer library +set(GO_TEMPLATE_DIR ${CMAKE_CURRENT_BINARY_DIR}/tools/gotemplate) +set(GO_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/gotemplate) + +if(CMAKE_GENERATOR_PLATFORM MATCHES "ARM64") + set(GO_ARCH arm64) + set(GO_LIB_MACHINE ARM64) +else() + set(GO_ARCH amd64) + set(GO_LIB_MACHINE X64) +endif() + +set(GO_OUTPUT_DLL ${GO_OUTPUT_DIR}/render.dll) +set(GO_OUTPUT_LIB ${GO_OUTPUT_DIR}/render.lib) + +add_custom_command( + OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} + COMMAND ${CMAKE_COMMAND} -E make_directory ${GO_OUTPUT_DIR} + COMMAND powershell -NoProfile -Command "cd '${GO_TEMPLATE_DIR}' ; \$env:GOOS='windows' ; \$env:GOARCH='${GO_ARCH}' ; \$env:CGO_ENABLED='1' ; go build -o '${GO_OUTPUT_DLL}' -buildmode=c-shared render.go" + COMMAND lib /def:${GO_TEMPLATE_DIR}/render.def /out:${GO_OUTPUT_LIB} /machine:${GO_LIB_MACHINE} + DEPENDS ${GO_TEMPLATE_DIR}/render.go ${GO_TEMPLATE_DIR}/go.mod ${GO_TEMPLATE_DIR}/render.def + COMMENT "Building Go template renderer library" + VERBATIM +) + +add_custom_target(gotemplate_lib ALL DEPENDS ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB}) + # Object library for WSLC components. # Used to build the executable and also unit testing components. add_library(wslclib OBJECT ${SOURCES} ${HEADERS}) @@ -14,7 +41,11 @@ target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUB target_link_libraries(wslclib ${COMMON_LINK_LIBRARIES} yaml-cpp - common) + common + ${GO_OUTPUT_LIB}) + +# Add dependency on Go library +add_dependencies(wslclib gotemplate_lib) target_precompile_headers(wslclib REUSE_FROM common) set_target_properties(wslclib PROPERTIES FOLDER windows) @@ -24,6 +55,10 @@ add_executable(wslc $) target_link_libraries(wslc wslclib) +add_custom_command(TARGET wslc POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GO_OUTPUT_DLL} $/render.dll +) + set_target_properties(wslc PROPERTIES FOLDER windows) # For prettier source tree browsing diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index ea47a5f55..0ce864e64 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -158,15 +158,13 @@ FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring { return FormatType::Json; } - else if (IsEqual(input, L"table")) + + if (IsEqual(input, L"table")) { return FormatType::Table; } - else - { - throw ArgumentException(std::format( - L"Invalid {} value: {} is not a recognized format type. Supported format types are: json, table.", argName, input)); - } + + return FormatType::Template; } } // namespace wsl::windows::wslc::validation \ No newline at end of file diff --git a/src/windows/wslc/commands/ContainerCommand.h b/src/windows/wslc/commands/ContainerCommand.h index 503618a6d..269084147 100644 --- a/src/windows/wslc/commands/ContainerCommand.h +++ b/src/windows/wslc/commands/ContainerCommand.h @@ -119,7 +119,6 @@ struct ContainerListCommand final : public Command std::wstring LongDescription() const override; protected: - void ValidateArgumentsInternal(const ArgMap& execArgs) const override; void ExecuteInternal(CLIExecutionContext& context) const override; }; diff --git a/src/windows/wslc/commands/ContainerListCommand.cpp b/src/windows/wslc/commands/ContainerListCommand.cpp index 1fb3a6795..7662694d7 100644 --- a/src/windows/wslc/commands/ContainerListCommand.cpp +++ b/src/windows/wslc/commands/ContainerListCommand.cpp @@ -46,18 +46,6 @@ std::wstring ContainerListCommand::LongDescription() const return Localization::WSLCCLI_ContainerListLongDesc(); } -void ContainerListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const -{ - if (execArgs.Contains(ArgType::Format)) - { - auto format = execArgs.Get(); - if (!IsEqual(format, L"json") && !IsEqual(format, L"table")) - { - throw CommandException(Localization::WSLCCLI_InvalidFormatError()); - } - } -} - // clang-format off void ContainerListCommand::ExecuteInternal(CLIExecutionContext& context) const { diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp new file mode 100644 index 000000000..5cca8229d --- /dev/null +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -0,0 +1,56 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + GoTemplateRenderer.cpp + +Abstract: + + Implementation of the Go template renderer. + +--*/ + +#include "TemplateRenderer.h" +#include +#include +#include +#include + +namespace wsl::windows::wslc::core { + +using namespace wsl::shared::string; + +// Forward declaration of C functions from Go library +extern "C" { + char* RenderGoTemplate(char* templateStr, char* jsonData); + void FreeMemory(char* ptr); +} + +std::wstring TemplateRenderer::Render(const std::string& templateStr, const std::string& jsonData) +{ + // Call the Go template renderer + char* result = RenderGoTemplate(const_cast(templateStr.c_str()), const_cast(jsonData.c_str())); + + if (result == nullptr) + { + throw std::runtime_error("error: Go template renderer returned null"); + } + + // Check if result is an error message (starts with "error:") + std::string resultStr(result); + + // Free the memory allocated by Go + FreeMemory(result); + + // If it's an error, throw an exception + if (resultStr.starts_with("error:")) + { + throw std::runtime_error(resultStr); + } + + return MultiByteToWide(resultStr); +} + +} // namespace wsl::windows::wslc::core diff --git a/src/windows/wslc/core/TemplateRenderer.h b/src/windows/wslc/core/TemplateRenderer.h new file mode 100644 index 000000000..175f17043 --- /dev/null +++ b/src/windows/wslc/core/TemplateRenderer.h @@ -0,0 +1,30 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + TemplateRenderer.h + +Abstract: + + This file contains the interface for rendering Go templates with JSON data. + +--*/ + +#pragma once + +#include +#include + +namespace wsl::windows::wslc::core { + +struct TemplateRenderer +{ + // Renders a Go template with the provided JSON data. + // Returns the rendered output as a wide string. + // Throws an exception if template rendering fails. + static std::wstring Render(const std::string& templateStr, const std::string& jsonData); +}; + +} // namespace wsl::windows::wslc::core \ No newline at end of file diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index c4da81fab..a95c51040 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -25,6 +25,7 @@ enum class FormatType { Table, Json, + Template, }; struct ContainerOptions diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 55e19e478..20196fc5d 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -11,6 +11,7 @@ Module Name: Implementation of container command related execution logic. --*/ + #include "Argument.h" #include "ArgumentValidation.h" #include "CLIExecutionContext.h" @@ -20,6 +21,7 @@ Module Name: #include "SessionModel.h" #include "SessionService.h" #include "TableOutput.h" +#include "TemplateRenderer.h" #include #include @@ -167,6 +169,28 @@ void ListContainers(CLIExecutionContext& context) table.Complete(); break; } + case FormatType::Template: + { + WI_ASSERT(context.Args.Contains(ArgType::Format)); + auto templateStr = WideToMultiByte(context.Args.Get()); + + // Render the template using Go + try + { + for (const auto& container : containers) + { + auto json = ToJson(container, c_jsonPrettyPrintIndent); + auto result = wsl::windows::wslc::core::TemplateRenderer::Render(templateStr, json); + PrintMessage(result); + } + } + catch (const std::exception& e) + { + PrintMessage(MultiByteToWide(std::string("Template rendering error: ") + e.what())); + context.ExitCode = 1; + } + break; + } default: THROW_HR(E_UNEXPECTED); } diff --git a/src/windows/wslc/tools/gotemplate/render.def b/src/windows/wslc/tools/gotemplate/render.def new file mode 100644 index 000000000..36306eade --- /dev/null +++ b/src/windows/wslc/tools/gotemplate/render.def @@ -0,0 +1,3 @@ +EXPORTS + RenderGoTemplate + FreeMemory \ No newline at end of file diff --git a/src/windows/wslc/tools/gotemplate/render.go b/src/windows/wslc/tools/gotemplate/render.go new file mode 100644 index 000000000..a85048cc4 --- /dev/null +++ b/src/windows/wslc/tools/gotemplate/render.go @@ -0,0 +1,79 @@ +// +build cgo + +package main + +/* +#include +#include + +// Exported C functions +char* RenderGoTemplate(char* templateStr, char* jsonData); +void FreeMemory(char* ptr); +*/ +import "C" + +import ( + "bytes" + "encoding/json" + "text/template" + "unsafe" +) + +func toJSONString(v interface{}) (string, error) { + b, err := json.Marshal(v) + if err != nil { + return "", err + } + + return string(b), nil +} + +// RenderGoTemplate renders a Go template with the provided JSON data. +// The template string and JSON data are passed as C strings. +// Returns a C string with the rendered output that must be freed by the caller. +// +//export RenderGoTemplate +func RenderGoTemplate(templateStr *C.char, jsonData *C.char) *C.char { + if templateStr == nil || jsonData == nil { + return C.CString("error: null pointers provided") + } + + // Convert C strings to Go strings + goTemplate := C.GoString(templateStr) + goJSON := C.GoString(jsonData) + + // Parse the JSON data into a generic interface + var data interface{} + err := json.Unmarshal([]byte(goJSON), &data) + if err != nil { + return C.CString("error: failed to parse JSON: " + err.Error()) + } + + // Parse the template + funcMap := template.FuncMap{ + "json": toJSONString, + } + + tmpl, err := template.New("container-list").Funcs(funcMap).Parse(goTemplate) + if err != nil { + return C.CString("error: failed to parse template: " + err.Error()) + } + + // Render the template + var result bytes.Buffer + err = tmpl.Execute(&result, data) + if err != nil { + return C.CString("error: failed to execute template: " + err.Error()) + } + + // Return the result as a C string + return C.CString(result.String()) +} + +//export FreeMemory +func FreeMemory(ptr *C.char) { + C.free(unsafe.Pointer(ptr)) +} + +func main() { +} \ No newline at end of file From 6189485fce9126026c2da2b9a02bcf7e66c45bad Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:00:37 -0700 Subject: [PATCH 02/15] Clang format --- src/windows/wslc/arguments/ArgumentValidation.cpp | 2 +- src/windows/wslc/core/TemplateRenderer.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 0ce864e64..0ed9f3c91 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -158,7 +158,7 @@ FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring { return FormatType::Json; } - + if (IsEqual(input, L"table")) { return FormatType::Table; diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index 5cca8229d..3e4956699 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -24,8 +24,8 @@ using namespace wsl::shared::string; // Forward declaration of C functions from Go library extern "C" { - char* RenderGoTemplate(char* templateStr, char* jsonData); - void FreeMemory(char* ptr); +char* RenderGoTemplate(char* templateStr, char* jsonData); +void FreeMemory(char* ptr); } std::wstring TemplateRenderer::Render(const std::string& templateStr, const std::string& jsonData) From 90f800f8add46f9292897ce7146930fed0938b64 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:08:56 -0700 Subject: [PATCH 03/15] Addressed comments --- src/windows/wslc/CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index 08f180c26..125a9f655 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -7,15 +7,17 @@ file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${HEADER_PATTERNS}) file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${SOURCE_PATTERNS}) # Build Go template renderer library -set(GO_TEMPLATE_DIR ${CMAKE_CURRENT_BINARY_DIR}/tools/gotemplate) -set(GO_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/gotemplate) +set(GO_TEMPLATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/gotemplate) +set(GO_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles/gotemplate) -if(CMAKE_GENERATOR_PLATFORM MATCHES "ARM64") +if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "arm64" OR "${TARGET_PLATFORM}" STREQUAL "arm64") set(GO_ARCH arm64) set(GO_LIB_MACHINE ARM64) -else() +elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64|amd64" OR "${TARGET_PLATFORM}" MATCHES "x64|amd64") set(GO_ARCH amd64) set(GO_LIB_MACHINE X64) +else() + message(FATAL_ERROR "Unsupported platform: ${CMAKE_GENERATOR_PLATFORM}") endif() set(GO_OUTPUT_DLL ${GO_OUTPUT_DIR}/render.dll) From 1b06ed8aecd629231bb33d11349e64cdfe6f3b8a Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:36:47 -0700 Subject: [PATCH 04/15] Code enhancement --- src/windows/wslc/CMakeLists.txt | 25 ++++++++++++--------- src/windows/wslc/core/TemplateRenderer.cpp | 9 +++----- src/windows/wslc/tools/gotemplate/render.go | 4 ---- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index 125a9f655..c001329cb 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -8,37 +8,42 @@ file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${SOURCE_PATTERNS}) # Build Go template renderer library set(GO_TEMPLATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/gotemplate) -set(GO_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles/gotemplate) +set(GO_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/gotemplate) -if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "arm64" OR "${TARGET_PLATFORM}" STREQUAL "arm64") +# Reuse TARGET_PLATFORM already normalized by the root CMakeLists.txt +if("${TARGET_PLATFORM}" STREQUAL "arm64") set(GO_ARCH arm64) set(GO_LIB_MACHINE ARM64) -elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64|amd64" OR "${TARGET_PLATFORM}" MATCHES "x64|amd64") +else() set(GO_ARCH amd64) set(GO_LIB_MACHINE X64) -else() - message(FATAL_ERROR "Unsupported platform: ${CMAKE_GENERATOR_PLATFORM}") endif() set(GO_OUTPUT_DLL ${GO_OUTPUT_DIR}/render.dll) set(GO_OUTPUT_LIB ${GO_OUTPUT_DIR}/render.lib) +set(GO_OUTPUT_HEADER ${GO_OUTPUT_DIR}/render.h) + +# Add GO_OUTPUT_HEADER to HEADERS +list(APPEND HEADERS ${GO_OUTPUT_HEADER}) add_custom_command( - OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} + OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} ${GO_OUTPUT_HEADER} COMMAND ${CMAKE_COMMAND} -E make_directory ${GO_OUTPUT_DIR} - COMMAND powershell -NoProfile -Command "cd '${GO_TEMPLATE_DIR}' ; \$env:GOOS='windows' ; \$env:GOARCH='${GO_ARCH}' ; \$env:CGO_ENABLED='1' ; go build -o '${GO_OUTPUT_DLL}' -buildmode=c-shared render.go" + COMMAND ${CMAKE_COMMAND} -E env GOOS=windows GOARCH=${GO_ARCH} CGO_ENABLED=1 + go build -o ${GO_OUTPUT_DLL} -buildmode=c-shared render.go COMMAND lib /def:${GO_TEMPLATE_DIR}/render.def /out:${GO_OUTPUT_LIB} /machine:${GO_LIB_MACHINE} - DEPENDS ${GO_TEMPLATE_DIR}/render.go ${GO_TEMPLATE_DIR}/go.mod ${GO_TEMPLATE_DIR}/render.def + WORKING_DIRECTORY ${GO_TEMPLATE_DIR} + DEPENDS ${GO_TEMPLATE_DIR}/render.go ${GO_TEMPLATE_DIR}/render.def COMMENT "Building Go template renderer library" VERBATIM ) -add_custom_target(gotemplate_lib ALL DEPENDS ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB}) +add_custom_target(gotemplate_lib ALL DEPENDS ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} ${GO_OUTPUT_HEADER}) # Object library for WSLC components. # Used to build the executable and also unit testing components. add_library(wslclib OBJECT ${SOURCES} ${HEADERS}) -target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUBDIR_PATHS}) +target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUBDIR_PATHS} ${GO_OUTPUT_DIR}) target_link_libraries(wslclib ${COMMON_LINK_LIBRARIES} diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index 3e4956699..adf6e2abc 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -18,16 +18,13 @@ Module Name: #include #include +// Use the cgo-generated header from the Go template renderer build +#include "render.h" + namespace wsl::windows::wslc::core { using namespace wsl::shared::string; -// Forward declaration of C functions from Go library -extern "C" { -char* RenderGoTemplate(char* templateStr, char* jsonData); -void FreeMemory(char* ptr); -} - std::wstring TemplateRenderer::Render(const std::string& templateStr, const std::string& jsonData) { // Call the Go template renderer diff --git a/src/windows/wslc/tools/gotemplate/render.go b/src/windows/wslc/tools/gotemplate/render.go index a85048cc4..9eb284f49 100644 --- a/src/windows/wslc/tools/gotemplate/render.go +++ b/src/windows/wslc/tools/gotemplate/render.go @@ -5,10 +5,6 @@ package main /* #include #include - -// Exported C functions -char* RenderGoTemplate(char* templateStr, char* jsonData); -void FreeMemory(char* ptr); */ import "C" From e3fdd17d8bfad2ba74abf652072880047d1be867 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:42:47 -0700 Subject: [PATCH 05/15] Code enhancement --- src/windows/wslc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index c001329cb..03d7710be 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${SOURCE_PATTERNS}) # Build Go template renderer library set(GO_TEMPLATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/gotemplate) -set(GO_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/gotemplate) +set(GO_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/gotemplate") # Reuse TARGET_PLATFORM already normalized by the root CMakeLists.txt if("${TARGET_PLATFORM}" STREQUAL "arm64") From f331dbb7b2b37f84fe49595ed15c80201a417c2e Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:56:15 -0700 Subject: [PATCH 06/15] Include render.go --- src/windows/wslc/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index 03d7710be..26cd5d228 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -40,9 +40,13 @@ add_custom_command( add_custom_target(gotemplate_lib ALL DEPENDS ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} ${GO_OUTPUT_HEADER}) +# Include Go source files for browsing in Solution Explorer +set(GO_SOURCES ${GO_TEMPLATE_DIR}/render.go) +set_source_files_properties(${GO_SOURCES} PROPERTIES HEADER_FILE_ONLY TRUE) + # Object library for WSLC components. # Used to build the executable and also unit testing components. -add_library(wslclib OBJECT ${SOURCES} ${HEADERS}) +add_library(wslclib OBJECT ${SOURCES} ${HEADERS} ${GO_SOURCES}) target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUBDIR_PATHS} ${GO_OUTPUT_DIR}) target_link_libraries(wslclib @@ -69,4 +73,4 @@ add_custom_command(TARGET wslc POST_BUILD set_target_properties(wslc PROPERTIES FOLDER windows) # For prettier source tree browsing -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES} ${HEADERS}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES} ${HEADERS} ${GO_SOURCES}) From 99fb1989918f3cad98a795cff541607099f43dea Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:42:56 -0700 Subject: [PATCH 07/15] COde enhancement --- src/windows/wslc/core/TemplateRenderer.cpp | 30 ++----- src/windows/wslc/core/TemplateRenderer.h | 6 +- src/windows/wslc/tasks/ContainerTasks.cpp | 20 ++--- src/windows/wslc/tools/gotemplate/render.def | 4 +- src/windows/wslc/tools/gotemplate/render.go | 90 ++++++++++---------- 5 files changed, 63 insertions(+), 87 deletions(-) diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index adf6e2abc..e218d4628 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -13,10 +13,7 @@ Module Name: --*/ #include "TemplateRenderer.h" -#include -#include #include -#include // Use the cgo-generated header from the Go template renderer build #include "render.h" @@ -25,29 +22,16 @@ namespace wsl::windows::wslc::core { using namespace wsl::shared::string; -std::wstring TemplateRenderer::Render(const std::string& templateStr, const std::string& jsonData) +bool TemplateRenderer::TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output) { - // Call the Go template renderer - char* result = RenderGoTemplate(const_cast(templateStr.c_str()), const_cast(jsonData.c_str())); + char* rawOutput = nullptr; + auto success = TryRenderGoTemplate(const_cast(templateStr.c_str()), const_cast(jsonData.c_str()), &rawOutput); - if (result == nullptr) - { - throw std::runtime_error("error: Go template renderer returned null"); - } + std::string result(rawOutput ? rawOutput : ""); + FreeGoString(rawOutput); - // Check if result is an error message (starts with "error:") - std::string resultStr(result); - - // Free the memory allocated by Go - FreeMemory(result); - - // If it's an error, throw an exception - if (resultStr.starts_with("error:")) - { - throw std::runtime_error(resultStr); - } - - return MultiByteToWide(resultStr); + output = MultiByteToWide(result); + return success != 0; } } // namespace wsl::windows::wslc::core diff --git a/src/windows/wslc/core/TemplateRenderer.h b/src/windows/wslc/core/TemplateRenderer.h index 175f17043..1bbdc65ce 100644 --- a/src/windows/wslc/core/TemplateRenderer.h +++ b/src/windows/wslc/core/TemplateRenderer.h @@ -15,16 +15,14 @@ Module Name: #pragma once #include -#include namespace wsl::windows::wslc::core { struct TemplateRenderer { // Renders a Go template with the provided JSON data. - // Returns the rendered output as a wide string. - // Throws an exception if template rendering fails. - static std::wstring Render(const std::string& templateStr, const std::string& jsonData); + // Returns true on success with the rendered output, false on failure with an error message. + static bool TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output); }; } // namespace wsl::windows::wslc::core \ No newline at end of file diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 20196fc5d..78f6c090f 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -174,20 +174,18 @@ void ListContainers(CLIExecutionContext& context) WI_ASSERT(context.Args.Contains(ArgType::Format)); auto templateStr = WideToMultiByte(context.Args.Get()); - // Render the template using Go - try + for (const auto& container : containers) { - for (const auto& container : containers) + auto json = ToJson(container, c_jsonPrettyPrintIndent); + std::wstring result; + if (!wsl::windows::wslc::core::TemplateRenderer::TryRender(templateStr, json, result)) { - auto json = ToJson(container, c_jsonPrettyPrintIndent); - auto result = wsl::windows::wslc::core::TemplateRenderer::Render(templateStr, json); - PrintMessage(result); + PrintMessage(L"Template rendering error: " + result); + context.ExitCode = 1; + break; } - } - catch (const std::exception& e) - { - PrintMessage(MultiByteToWide(std::string("Template rendering error: ") + e.what())); - context.ExitCode = 1; + + PrintMessage(result); } break; } diff --git a/src/windows/wslc/tools/gotemplate/render.def b/src/windows/wslc/tools/gotemplate/render.def index 36306eade..a048c8244 100644 --- a/src/windows/wslc/tools/gotemplate/render.def +++ b/src/windows/wslc/tools/gotemplate/render.def @@ -1,3 +1,3 @@ EXPORTS - RenderGoTemplate - FreeMemory \ No newline at end of file + TryRenderGoTemplate + FreeGoString \ No newline at end of file diff --git a/src/windows/wslc/tools/gotemplate/render.go b/src/windows/wslc/tools/gotemplate/render.go index 9eb284f49..745c54ef0 100644 --- a/src/windows/wslc/tools/gotemplate/render.go +++ b/src/windows/wslc/tools/gotemplate/render.go @@ -4,71 +4,67 @@ package main /* #include -#include */ import "C" import ( - "bytes" - "encoding/json" - "text/template" - "unsafe" + "bytes" + "encoding/json" + "text/template" + "unsafe" ) func toJSONString(v interface{}) (string, error) { - b, err := json.Marshal(v) - if err != nil { - return "", err - } + b, err := json.Marshal(v) + if err != nil { + return "", err + } - return string(b), nil + return string(b), nil } -// RenderGoTemplate renders a Go template with the provided JSON data. -// The template string and JSON data are passed as C strings. -// Returns a C string with the rendered output that must be freed by the caller. +// TryRenderGoTemplate renders a Go template with the provided JSON data. +// Returns 1 on success, 0 on failure. On success, *output contains the rendered result. +// On failure, *output contains the error message. The caller must free *output with FreeGoString. // -//export RenderGoTemplate -func RenderGoTemplate(templateStr *C.char, jsonData *C.char) *C.char { - if templateStr == nil || jsonData == nil { - return C.CString("error: null pointers provided") - } +//export TryRenderGoTemplate +func TryRenderGoTemplate(templateStr *C.char, jsonData *C.char, output **C.char) C.int { + if templateStr == nil || jsonData == nil { + *output = C.CString("null pointer provided") + return 0 + } - // Convert C strings to Go strings - goTemplate := C.GoString(templateStr) - goJSON := C.GoString(jsonData) + var data interface{} + if err := json.Unmarshal([]byte(C.GoString(jsonData)), &data); err != nil { + *output = C.CString("failed to parse JSON: " + err.Error()) + return 0 + } - // Parse the JSON data into a generic interface - var data interface{} - err := json.Unmarshal([]byte(goJSON), &data) - if err != nil { - return C.CString("error: failed to parse JSON: " + err.Error()) - } + funcMap := template.FuncMap{ + "json": toJSONString, + } - // Parse the template - funcMap := template.FuncMap{ - "json": toJSONString, - } + tmpl, err := template.New("gotemplate").Funcs(funcMap).Parse(C.GoString(templateStr)) + if err != nil { + *output = C.CString("failed to parse template: " + err.Error()) + return 0 + } - tmpl, err := template.New("container-list").Funcs(funcMap).Parse(goTemplate) - if err != nil { - return C.CString("error: failed to parse template: " + err.Error()) - } + var result bytes.Buffer + if err = tmpl.Execute(&result, data); err != nil { + *output = C.CString("failed to execute template: " + err.Error()) + return 0 + } - // Render the template - var result bytes.Buffer - err = tmpl.Execute(&result, data) - if err != nil { - return C.CString("error: failed to execute template: " + err.Error()) - } - - // Return the result as a C string - return C.CString(result.String()) + *output = C.CString(result.String()) + return 1 } -//export FreeMemory -func FreeMemory(ptr *C.char) { - C.free(unsafe.Pointer(ptr)) +// FreeGoString frees a string allocated by TryRenderGoTemplate. +// +//export FreeGoString +func FreeGoString(ptr *C.char) { + C.free(unsafe.Pointer(ptr)) } func main() { From 3304aaa50118ee42f60ccf757543e76fcef4ba78 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:05:27 -0700 Subject: [PATCH 08/15] code enhancement --- src/windows/wslc/CMakeLists.txt | 11 +++-------- src/windows/wslc/core/TemplateRenderer.cpp | 9 +++++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index 26cd5d228..cdbab4a27 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -21,13 +21,8 @@ endif() set(GO_OUTPUT_DLL ${GO_OUTPUT_DIR}/render.dll) set(GO_OUTPUT_LIB ${GO_OUTPUT_DIR}/render.lib) -set(GO_OUTPUT_HEADER ${GO_OUTPUT_DIR}/render.h) - -# Add GO_OUTPUT_HEADER to HEADERS -list(APPEND HEADERS ${GO_OUTPUT_HEADER}) - add_custom_command( - OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} ${GO_OUTPUT_HEADER} + OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} COMMAND ${CMAKE_COMMAND} -E make_directory ${GO_OUTPUT_DIR} COMMAND ${CMAKE_COMMAND} -E env GOOS=windows GOARCH=${GO_ARCH} CGO_ENABLED=1 go build -o ${GO_OUTPUT_DLL} -buildmode=c-shared render.go @@ -38,7 +33,7 @@ add_custom_command( VERBATIM ) -add_custom_target(gotemplate_lib ALL DEPENDS ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} ${GO_OUTPUT_HEADER}) +add_custom_target(gotemplate_lib ALL DEPENDS ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB}) # Include Go source files for browsing in Solution Explorer set(GO_SOURCES ${GO_TEMPLATE_DIR}/render.go) @@ -47,7 +42,7 @@ set_source_files_properties(${GO_SOURCES} PROPERTIES HEADER_FILE_ONLY TRUE) # Object library for WSLC components. # Used to build the executable and also unit testing components. add_library(wslclib OBJECT ${SOURCES} ${HEADERS} ${GO_SOURCES}) -target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUBDIR_PATHS} ${GO_OUTPUT_DIR}) +target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUBDIR_PATHS}) target_link_libraries(wslclib ${COMMON_LINK_LIBRARIES} diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index e218d4628..88ff8d089 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -15,8 +15,13 @@ Module Name: #include "TemplateRenderer.h" #include -// Use the cgo-generated header from the Go template renderer build -#include "render.h" +// Forward-declare the Go template renderer functions (exported from render.dll). +// We declare these directly instead of including the cgo-generated render.h +// to avoid Go boilerplate types that don't compile cleanly with MSVC. +extern "C" { + int TryRenderGoTemplate(char* templateStr, char* jsonData, char** output); + void FreeGoString(char* ptr); +} namespace wsl::windows::wslc::core { From b254595676d385049bb31be1b20191bbbe309ded Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:05:50 -0700 Subject: [PATCH 09/15] Clang format --- src/windows/wslc/core/TemplateRenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index 88ff8d089..583b9232a 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -19,8 +19,8 @@ Module Name: // We declare these directly instead of including the cgo-generated render.h // to avoid Go boilerplate types that don't compile cleanly with MSVC. extern "C" { - int TryRenderGoTemplate(char* templateStr, char* jsonData, char** output); - void FreeGoString(char* ptr); +int TryRenderGoTemplate(char* templateStr, char* jsonData, char** output); +void FreeGoString(char* ptr); } namespace wsl::windows::wslc::core { From 5dc677e8bd7fdba560d4eff210df2914376373e6 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:28:23 -0700 Subject: [PATCH 10/15] optimize --- src/windows/wslc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index cdbab4a27..75aaaa5e3 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -25,7 +25,7 @@ add_custom_command( OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} COMMAND ${CMAKE_COMMAND} -E make_directory ${GO_OUTPUT_DIR} COMMAND ${CMAKE_COMMAND} -E env GOOS=windows GOARCH=${GO_ARCH} CGO_ENABLED=1 - go build -o ${GO_OUTPUT_DLL} -buildmode=c-shared render.go + go build -o ${GO_OUTPUT_DLL} -buildmode=c-shared -trimpath -ldflags=-s\ -w render.go COMMAND lib /def:${GO_TEMPLATE_DIR}/render.def /out:${GO_OUTPUT_LIB} /machine:${GO_LIB_MACHINE} WORKING_DIRECTORY ${GO_TEMPLATE_DIR} DEPENDS ${GO_TEMPLATE_DIR}/render.go ${GO_TEMPLATE_DIR}/render.def From 000d50ddcc4437574d41bcba8424a1d7f3ab30d2 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Thu, 16 Apr 2026 07:54:35 -0700 Subject: [PATCH 11/15] Added render --- .../wslc/arguments/ArgumentValidation.cpp | 14 +------ .../wslc/arguments/ArgumentValidation.h | 3 +- src/windows/wslc/core/TemplateRenderer.cpp | 39 +++++++++++++++---- src/windows/wslc/core/TemplateRenderer.h | 16 +++++++- src/windows/wslc/tasks/ContainerTasks.cpp | 11 +----- src/windows/wslc/tasks/ImageTasks.cpp | 14 +++++++ src/windows/wslc/tools/gotemplate/render.go | 28 ++++++++----- .../windows/wslc/WSLCCLIArgumentUnitTests.cpp | 2 - 8 files changed, 84 insertions(+), 43 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 0ed9f3c91..4505d9a37 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -31,10 +31,6 @@ void Argument::Validate(const ArgMap& execArgs) const { switch (m_argType) { - case ArgType::Format: - validation::ValidateFormatTypeFromString(execArgs.GetAll(), m_name); - break; - case ArgType::Signal: validation::ValidateWSLCSignalFromString(execArgs.GetAll(), m_name); break; @@ -144,15 +140,7 @@ WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring return static_cast(signalValue); } -void ValidateFormatTypeFromString(const std::vector& values, const std::wstring& argName) -{ - for (const auto& value : values) - { - std::ignore = GetFormatTypeFromString(value, argName); - } -} - -FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName) +FormatType GetFormatTypeFromString(const std::wstring& input) { if (IsEqual(input, L"json")) { diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 23208699c..08f1dfb59 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -57,8 +57,7 @@ T GetIntegerFromString(const std::wstring& value, const std::wstring& argName = void ValidateWSLCSignalFromString(const std::vector& values, const std::wstring& argName); WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName = {}); -void ValidateFormatTypeFromString(const std::vector& values, const std::wstring& argName); -FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {}); +FormatType GetFormatTypeFromString(const std::wstring& input); void ValidateVolumeMount(const std::vector& values); diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index 583b9232a..0f1316af2 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -27,16 +27,41 @@ namespace wsl::windows::wslc::core { using namespace wsl::shared::string; -bool TemplateRenderer::TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output) +TemplateRenderer::RenderResult TemplateRenderer::TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output) { - char* rawOutput = nullptr; - auto success = TryRenderGoTemplate(const_cast(templateStr.c_str()), const_cast(jsonData.c_str()), &rawOutput); + try + { + char* rawOutput = nullptr; + auto success = TryRenderGoTemplate(const_cast(templateStr.c_str()), const_cast(jsonData.c_str()), &rawOutput); - std::string result(rawOutput ? rawOutput : ""); - FreeGoString(rawOutput); + std::string result(rawOutput ? rawOutput : ""); + FreeGoString(rawOutput); - output = MultiByteToWide(result); - return success != 0; + output = MultiByteToWide(result); + return static_cast(success); + } + catch (const std::exception& ex) + { + output = MultiByteToWide(ex.what()); + return RenderResult::Fail_Unknown; + } +} + +void TemplateRenderer::Render(const std::string& templateStr, const std::string& jsonData, std::wstring& output) +{ + switch (TryRender(templateStr, jsonData, output)) + { + case RenderResult::Success: + return; + case RenderResult::Fail_NullPointer: + case RenderResult::Fail_ParseJSON: + case RenderResult::Fail_ParseTemplate: + case RenderResult::Fail_ExecuteTemplate: + THROW_HR_WITH_USER_ERROR(E_INVALIDARG, output); + case RenderResult::Fail_Unknown: + default: + THROW_HR(E_UNEXPECTED); + } } } // namespace wsl::windows::wslc::core diff --git a/src/windows/wslc/core/TemplateRenderer.h b/src/windows/wslc/core/TemplateRenderer.h index 1bbdc65ce..de3fabc01 100644 --- a/src/windows/wslc/core/TemplateRenderer.h +++ b/src/windows/wslc/core/TemplateRenderer.h @@ -20,9 +20,23 @@ namespace wsl::windows::wslc::core { struct TemplateRenderer { + enum class RenderResult + { + Success = 0, + Fail_NullPointer = 1, + Fail_ParseJSON = 2, + Fail_ParseTemplate = 3, + Fail_ExecuteTemplate = 4, + + // Catch-all for any unknown errors + Fail_Unknown = -1, + }; + // Renders a Go template with the provided JSON data. // Returns true on success with the rendered output, false on failure with an error message. - static bool TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output); + static RenderResult TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output); + + static void Render(const std::string& templateStr, const std::string& jsonData, std::wstring& output); }; } // namespace wsl::windows::wslc::core \ No newline at end of file diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 78f6c090f..7ef35eac5 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -31,6 +31,7 @@ using namespace wsl::windows::common::wslutil; using namespace wsl::windows::wslc::execution; using namespace wsl::windows::wslc::models; using namespace wsl::windows::wslc::services; +using namespace wsl::windows::wslc::core; namespace wsl::windows::wslc::task { void AttachContainer::operator()(CLIExecutionContext& context) const @@ -171,20 +172,12 @@ void ListContainers(CLIExecutionContext& context) } case FormatType::Template: { - WI_ASSERT(context.Args.Contains(ArgType::Format)); auto templateStr = WideToMultiByte(context.Args.Get()); - for (const auto& container : containers) { auto json = ToJson(container, c_jsonPrettyPrintIndent); std::wstring result; - if (!wsl::windows::wslc::core::TemplateRenderer::TryRender(templateStr, json, result)) - { - PrintMessage(L"Template rendering error: " + result); - context.ExitCode = 1; - break; - } - + TemplateRenderer::Render(templateStr, json, result); PrintMessage(result); } break; diff --git a/src/windows/wslc/tasks/ImageTasks.cpp b/src/windows/wslc/tasks/ImageTasks.cpp index 461e00589..50df2949b 100644 --- a/src/windows/wslc/tasks/ImageTasks.cpp +++ b/src/windows/wslc/tasks/ImageTasks.cpp @@ -22,6 +22,7 @@ Module Name: #include "PullImageCallback.h" #include "TableOutput.h" #include "Task.h" +#include "TemplateRenderer.h" #include using namespace wsl::shared; @@ -29,6 +30,7 @@ using namespace wsl::windows::common::string; using namespace wsl::windows::common::wslutil; using namespace wsl::windows::wslc::execution; using namespace wsl::windows::wslc::services; +using namespace wsl::windows::wslc::core; namespace wsl::windows::wslc::task { void BuildImage(CLIExecutionContext& context) @@ -124,6 +126,18 @@ void ListImages(CLIExecutionContext& context) table.Complete(); break; } + case FormatType::Template: + { + auto templateStr = WideToMultiByte(context.Args.Get()); + for (const auto& image : images) + { + auto json = ToJson(image, c_jsonPrettyPrintIndent); + std::wstring result; + TemplateRenderer::Render(templateStr, json, result); + PrintMessage(result); + } + break; + } default: THROW_HR(E_UNEXPECTED); } diff --git a/src/windows/wslc/tools/gotemplate/render.go b/src/windows/wslc/tools/gotemplate/render.go index 745c54ef0..9695e9736 100644 --- a/src/windows/wslc/tools/gotemplate/render.go +++ b/src/windows/wslc/tools/gotemplate/render.go @@ -23,6 +23,16 @@ func toJSONString(v interface{}) (string, error) { return string(b), nil } +// Return codes +// Those errors should be in sync with the ones defined in TemplateRenderer +const ( + Success = 0 + Fail_NullPointer = 1 + Fail_ParseJSON = 2 + Fail_ParseTemplate = 3 + Fail_ExecuteTemplate = 4 +) + // TryRenderGoTemplate renders a Go template with the provided JSON data. // Returns 1 on success, 0 on failure. On success, *output contains the rendered result. // On failure, *output contains the error message. The caller must free *output with FreeGoString. @@ -30,14 +40,14 @@ func toJSONString(v interface{}) (string, error) { //export TryRenderGoTemplate func TryRenderGoTemplate(templateStr *C.char, jsonData *C.char, output **C.char) C.int { if templateStr == nil || jsonData == nil { - *output = C.CString("null pointer provided") - return 0 + *output = C.CString("") + return Fail_NullPointer } var data interface{} if err := json.Unmarshal([]byte(C.GoString(jsonData)), &data); err != nil { - *output = C.CString("failed to parse JSON: " + err.Error()) - return 0 + *output = C.CString(err.Error()) + return Fail_ParseJSON } funcMap := template.FuncMap{ @@ -46,18 +56,18 @@ func TryRenderGoTemplate(templateStr *C.char, jsonData *C.char, output **C.char) tmpl, err := template.New("gotemplate").Funcs(funcMap).Parse(C.GoString(templateStr)) if err != nil { - *output = C.CString("failed to parse template: " + err.Error()) - return 0 + *output = C.CString(err.Error()) + return Fail_ParseTemplate } var result bytes.Buffer if err = tmpl.Execute(&result, data); err != nil { - *output = C.CString("failed to execute template: " + err.Error()) - return 0 + *output = C.CString(err.Error()) + return Fail_ExecuteTemplate } *output = C.CString(result.String()) - return 1 + return Success } // FreeGoString frees a string allocated by TryRenderGoTemplate. diff --git a/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp b/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp index 26508f868..263e04a4e 100644 --- a/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp +++ b/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp @@ -137,8 +137,6 @@ class WSLCCLIArgumentUnitTests format = validation::GetFormatTypeFromString(L"table"); VERIFY_ARE_EQUAL(format, FormatType::Table); VERIFY_THROWS(validation::GetFormatTypeFromString(L"xml"), ArgumentException); - VERIFY_NO_THROW(validation::ValidateFormatTypeFromString({L"json", L"table"}, L"formatArg")); - VERIFY_THROWS(validation::ValidateFormatTypeFromString({L"JSON", L"TABLE", L"csv"}, L"formatArg"), ArgumentException); } // Test: Verify EnumVariantMap behavior with ArgTypes. From 86821709fd617f32610c34f0702766249a71dd90 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:48:05 -0700 Subject: [PATCH 12/15] Addressed comments --- src/windows/wslc/core/TemplateRenderer.cpp | 5 +- src/windows/wslc/core/TemplateRenderer.h | 5 +- src/windows/wslc/tasks/ContainerTasks.cpp | 2 +- src/windows/wslc/tasks/ImageTasks.cpp | 2 +- src/windows/wslc/tools/gotemplate/render.def | 2 + src/windows/wslc/tools/gotemplate/render.go | 81 +++++++++---------- .../windows/wslc/WSLCCLIArgumentUnitTests.cpp | 5 +- 7 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index 0f1316af2..ab311742f 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -19,7 +19,7 @@ Module Name: // We declare these directly instead of including the cgo-generated render.h // to avoid Go boilerplate types that don't compile cleanly with MSVC. extern "C" { -int TryRenderGoTemplate(char* templateStr, char* jsonData, char** output); +int TryRenderGoTemplate(const char* templateStr, const char* jsonData, char** output); void FreeGoString(char* ptr); } @@ -32,7 +32,7 @@ TemplateRenderer::RenderResult TemplateRenderer::TryRender(const std::string& te try { char* rawOutput = nullptr; - auto success = TryRenderGoTemplate(const_cast(templateStr.c_str()), const_cast(jsonData.c_str()), &rawOutput); + auto success = TryRenderGoTemplate(templateStr.c_str(), jsonData.c_str(), &rawOutput); std::string result(rawOutput ? rawOutput : ""); FreeGoString(rawOutput); @@ -54,6 +54,7 @@ void TemplateRenderer::Render(const std::string& templateStr, const std::string& case RenderResult::Success: return; case RenderResult::Fail_NullPointer: + THROW_HR(E_POINTER); case RenderResult::Fail_ParseJSON: case RenderResult::Fail_ParseTemplate: case RenderResult::Fail_ExecuteTemplate: diff --git a/src/windows/wslc/core/TemplateRenderer.h b/src/windows/wslc/core/TemplateRenderer.h index de3fabc01..3bc57fb14 100644 --- a/src/windows/wslc/core/TemplateRenderer.h +++ b/src/windows/wslc/core/TemplateRenderer.h @@ -28,14 +28,11 @@ struct TemplateRenderer Fail_ParseTemplate = 3, Fail_ExecuteTemplate = 4, - // Catch-all for any unknown errors + // All other failures Fail_Unknown = -1, }; - // Renders a Go template with the provided JSON data. - // Returns true on success with the rendered output, false on failure with an error message. static RenderResult TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output); - static void Render(const std::string& templateStr, const std::string& jsonData, std::wstring& output); }; diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 7ef35eac5..d38f7ad9e 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -175,7 +175,7 @@ void ListContainers(CLIExecutionContext& context) auto templateStr = WideToMultiByte(context.Args.Get()); for (const auto& container : containers) { - auto json = ToJson(container, c_jsonPrettyPrintIndent); + auto json = ToJson(container); std::wstring result; TemplateRenderer::Render(templateStr, json, result); PrintMessage(result); diff --git a/src/windows/wslc/tasks/ImageTasks.cpp b/src/windows/wslc/tasks/ImageTasks.cpp index 50df2949b..6cd0b3173 100644 --- a/src/windows/wslc/tasks/ImageTasks.cpp +++ b/src/windows/wslc/tasks/ImageTasks.cpp @@ -131,7 +131,7 @@ void ListImages(CLIExecutionContext& context) auto templateStr = WideToMultiByte(context.Args.Get()); for (const auto& image : images) { - auto json = ToJson(image, c_jsonPrettyPrintIndent); + auto json = ToJson(image); std::wstring result; TemplateRenderer::Render(templateStr, json, result); PrintMessage(result); diff --git a/src/windows/wslc/tools/gotemplate/render.def b/src/windows/wslc/tools/gotemplate/render.def index a048c8244..73d74a123 100644 --- a/src/windows/wslc/tools/gotemplate/render.def +++ b/src/windows/wslc/tools/gotemplate/render.def @@ -1,3 +1,5 @@ +LIBRARY render + EXPORTS TryRenderGoTemplate FreeGoString \ No newline at end of file diff --git a/src/windows/wslc/tools/gotemplate/render.go b/src/windows/wslc/tools/gotemplate/render.go index 9695e9736..c0d8cc7df 100644 --- a/src/windows/wslc/tools/gotemplate/render.go +++ b/src/windows/wslc/tools/gotemplate/render.go @@ -1,4 +1,4 @@ -// +build cgo +//go:build cgo package main @@ -8,29 +8,29 @@ package main import "C" import ( - "bytes" - "encoding/json" - "text/template" - "unsafe" + "bytes" + "encoding/json" + "text/template" + "unsafe" ) func toJSONString(v interface{}) (string, error) { - b, err := json.Marshal(v) - if err != nil { - return "", err - } + b, err := json.Marshal(v) + if err != nil { + return "", err + } - return string(b), nil + return string(b), nil } // Return codes // Those errors should be in sync with the ones defined in TemplateRenderer const ( - Success = 0 - Fail_NullPointer = 1 - Fail_ParseJSON = 2 - Fail_ParseTemplate = 3 - Fail_ExecuteTemplate = 4 + Success = 0 + Fail_NullPointer = 1 + Fail_ParseJSON = 2 + Fail_ParseTemplate = 3 + Fail_ExecuteTemplate = 4 ) // TryRenderGoTemplate renders a Go template with the provided JSON data. @@ -39,43 +39,42 @@ const ( // //export TryRenderGoTemplate func TryRenderGoTemplate(templateStr *C.char, jsonData *C.char, output **C.char) C.int { - if templateStr == nil || jsonData == nil { - *output = C.CString("") - return Fail_NullPointer - } + if templateStr == nil || jsonData == nil || output == nil { + return Fail_NullPointer + } - var data interface{} - if err := json.Unmarshal([]byte(C.GoString(jsonData)), &data); err != nil { - *output = C.CString(err.Error()) - return Fail_ParseJSON - } + var data interface{} + if err := json.Unmarshal([]byte(C.GoString(jsonData)), &data); err != nil { + *output = C.CString(err.Error()) + return Fail_ParseJSON + } - funcMap := template.FuncMap{ - "json": toJSONString, - } + funcMap := template.FuncMap{ + "json": toJSONString, + } - tmpl, err := template.New("gotemplate").Funcs(funcMap).Parse(C.GoString(templateStr)) - if err != nil { - *output = C.CString(err.Error()) - return Fail_ParseTemplate - } + tmpl, err := template.New("gotemplate").Funcs(funcMap).Parse(C.GoString(templateStr)) + if err != nil { + *output = C.CString(err.Error()) + return Fail_ParseTemplate + } - var result bytes.Buffer - if err = tmpl.Execute(&result, data); err != nil { - *output = C.CString(err.Error()) - return Fail_ExecuteTemplate - } + var result bytes.Buffer + if err = tmpl.Execute(&result, data); err != nil { + *output = C.CString(err.Error()) + return Fail_ExecuteTemplate + } - *output = C.CString(result.String()) - return Success + *output = C.CString(result.String()) + return Success } // FreeGoString frees a string allocated by TryRenderGoTemplate. // //export FreeGoString func FreeGoString(ptr *C.char) { - C.free(unsafe.Pointer(ptr)) + C.free(unsafe.Pointer(ptr)) } func main() { -} \ No newline at end of file +} diff --git a/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp b/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp index 263e04a4e..b48e2e257 100644 --- a/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp +++ b/test/windows/wslc/WSLCCLIArgumentUnitTests.cpp @@ -136,7 +136,10 @@ class WSLCCLIArgumentUnitTests VERIFY_ARE_EQUAL(format, FormatType::Json); format = validation::GetFormatTypeFromString(L"table"); VERIFY_ARE_EQUAL(format, FormatType::Table); - VERIFY_THROWS(validation::GetFormatTypeFromString(L"xml"), ArgumentException); + format = validation::GetFormatTypeFromString(L"{{json .}}"); + VERIFY_ARE_EQUAL(format, FormatType::Template); + format = validation::GetFormatTypeFromString(L"template"); + VERIFY_ARE_EQUAL(format, FormatType::Template); } // Test: Verify EnumVariantMap behavior with ArgTypes. From 45e804a57de2b5b23e21f61188a647332df55e55 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:21:37 -0700 Subject: [PATCH 13/15] Addressed comments --- src/windows/wslc/CMakeLists.txt | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index 75aaaa5e3..cd5c3bfe0 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -10,13 +10,33 @@ file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${SOURCE_PATTERNS}) set(GO_TEMPLATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/gotemplate) set(GO_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/gotemplate") +find_program(GO_EXECUTABLE go REQUIRED) + # Reuse TARGET_PLATFORM already normalized by the root CMakeLists.txt if("${TARGET_PLATFORM}" STREQUAL "arm64") set(GO_ARCH arm64) set(GO_LIB_MACHINE ARM64) + find_program(GO_CLANG_EXECUTABLE + NAMES clang + HINTS + "$ENV{VCToolsInstallDir}/Llvm/x64/bin" + "$ENV{VCINSTALLDIR}/Tools/Llvm/x64/bin" + "$ENV{ProgramFiles}/LLVM/bin" + "$ENV{ProgramFiles(x86)}/LLVM/bin") + + if(NOT GO_CLANG_EXECUTABLE) + message(FATAL_ERROR "clang is required for arm64 cgo builds. Install the Windows Clang toolchain or add clang to PATH.") + endif() + + set(GO_CGO_ENV + "CC=${GO_CLANG_EXECUTABLE}" + "CGO_CFLAGS=--target=aarch64-pc-windows-msvc" + "CGO_CXXFLAGS=--target=aarch64-pc-windows-msvc" + "CGO_LDFLAGS=--target=aarch64-pc-windows-msvc") else() set(GO_ARCH amd64) set(GO_LIB_MACHINE X64) + set(GO_CGO_ENV) endif() set(GO_OUTPUT_DLL ${GO_OUTPUT_DIR}/render.dll) @@ -24,8 +44,8 @@ set(GO_OUTPUT_LIB ${GO_OUTPUT_DIR}/render.lib) add_custom_command( OUTPUT ${GO_OUTPUT_DLL} ${GO_OUTPUT_LIB} COMMAND ${CMAKE_COMMAND} -E make_directory ${GO_OUTPUT_DIR} - COMMAND ${CMAKE_COMMAND} -E env GOOS=windows GOARCH=${GO_ARCH} CGO_ENABLED=1 - go build -o ${GO_OUTPUT_DLL} -buildmode=c-shared -trimpath -ldflags=-s\ -w render.go + COMMAND ${CMAKE_COMMAND} -E env GOOS=windows GOARCH=${GO_ARCH} CGO_ENABLED=1 ${GO_CGO_ENV} + ${GO_EXECUTABLE} build -o ${GO_OUTPUT_DLL} -buildmode=c-shared -trimpath -ldflags=-s\ -w render.go COMMAND lib /def:${GO_TEMPLATE_DIR}/render.def /out:${GO_OUTPUT_LIB} /machine:${GO_LIB_MACHINE} WORKING_DIRECTORY ${GO_TEMPLATE_DIR} DEPENDS ${GO_TEMPLATE_DIR}/render.go ${GO_TEMPLATE_DIR}/render.def From 78872a93e2634a470128b9fff1044b88e90422a4 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:34:26 -0700 Subject: [PATCH 14/15] Added tests --- src/windows/wslc/CMakeLists.txt | 2 +- src/windows/wslc/core/TemplateRenderer.cpp | 2 +- .../wslc/e2e/WSLCE2EContainerListTests.cpp | 21 +++++++++++++------ .../wslc/e2e/WSLCE2EImageListTests.cpp | 16 ++++++++------ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index cd5c3bfe0..b60cf215b 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -22,7 +22,7 @@ if("${TARGET_PLATFORM}" STREQUAL "arm64") "$ENV{VCToolsInstallDir}/Llvm/x64/bin" "$ENV{VCINSTALLDIR}/Tools/Llvm/x64/bin" "$ENV{ProgramFiles}/LLVM/bin" - "$ENV{ProgramFiles(x86)}/LLVM/bin") + "$ENV{ProgramFiles\(x86\)}/LLVM/bin") if(NOT GO_CLANG_EXECUTABLE) message(FATAL_ERROR "clang is required for arm64 cgo builds. Install the Windows Clang toolchain or add clang to PATH.") diff --git a/src/windows/wslc/core/TemplateRenderer.cpp b/src/windows/wslc/core/TemplateRenderer.cpp index ab311742f..5f7839487 100644 --- a/src/windows/wslc/core/TemplateRenderer.cpp +++ b/src/windows/wslc/core/TemplateRenderer.cpp @@ -4,7 +4,7 @@ Copyright (c) Microsoft. All rights reserved. Module Name: - GoTemplateRenderer.cpp + TemplateRenderer.cpp Abstract: diff --git a/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp index 94d4f7679..59bb4acd9 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp @@ -154,12 +154,6 @@ class WSLCE2EContainerListTests VERIFY_ARE_EQUAL(containerId, outputLine); } - WSLC_TEST_METHOD(WSLCE2E_Container_List_InvalidFormatOption) - { - const auto result = RunWslc(L"container list --format invalid"); - result.Verify({.Stderr = L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table.\r\n", .ExitCode = 1}); - } - WSLC_TEST_METHOD(WSLCE2E_Container_List_JsonFormat) { VerifyContainerIsNotListed(WslcContainerName); @@ -203,6 +197,21 @@ class WSLCE2EContainerListTests VERIFY_IS_TRUE(std::find(containerIds.begin(), containerIds.end(), containerId2) != containerIds.end()); } + WSLC_TEST_METHOD(WSLCE2E_Container_List_TemplateFormat) + { + // Create a container + RunWslcAndVerify(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()), {.Stderr = L"", .ExitCode = 0}); + RunWslcAndVerify(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()), {.Stderr = L"", .ExitCode = 0}); + + // List containers with json format + auto result = RunWslc(L"container list --all --format \"Name = {{.Name}}\""); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto output = result.GetStdoutLines(); + VERIFY_ARE_EQUAL(2U, output.size()); + VERIFY_ARE_EQUAL(std::format(L"Name = {}", WslcContainerName), output[0]); + VERIFY_ARE_EQUAL(std::format(L"Name = {}", WslcContainerName2), output[1]); + } + private: const std::wstring WslcContainerName = L"wslc-test-container"; const std::wstring WslcContainerName2 = L"wslc-test-container-2"; diff --git a/test/windows/wslc/e2e/WSLCE2EImageListTests.cpp b/test/windows/wslc/e2e/WSLCE2EImageListTests.cpp index 86df7ad53..216b1d2a4 100644 --- a/test/windows/wslc/e2e/WSLCE2EImageListTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EImageListTests.cpp @@ -79,12 +79,6 @@ class WSLCE2EImageListTests VERIFY_IS_TRUE(imageFound); } - WSLC_TEST_METHOD(WSLCE2E_Image_List_InvalidFormatOption) - { - const auto result = RunWslc(L"image list --format invalid"); - result.Verify({.Stderr = L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table.\r\n", .ExitCode = 1}); - } - WSLC_TEST_METHOD(WSLCE2E_Image_List_JsonFormat) { const auto result = RunWslc(L"image list --format json"); @@ -128,6 +122,16 @@ class WSLCE2EImageListTests VERIFY_IS_TRUE(foundHeader, L"Expected table header with REPOSITORY, TAG, IMAGE ID, CREATED, SIZE columns"); } + WSLC_TEST_METHOD(WSLCE2E_Image_List_TemplateFormat) + { + auto result = RunWslc(L"image list --format \"{{.Repository}}:{{.Tag}}\""); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto output = result.GetStdoutLines(); + VERIFY_ARE_EQUAL(2U, output.size()); + VERIFY_ARE_EQUAL(DebianImage.NameAndTag(), output[0]); + VERIFY_ARE_EQUAL(AlpineImage.NameAndTag(), output[1]); + } + private: const TestImage& DebianImage = DebianTestImage(); const TestImage& AlpineImage = AlpineTestImage(); From b46d6561927a6c90ea244fdd40f53221d617a609 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:34:50 -0700 Subject: [PATCH 15/15] Clang format --- test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp index 59bb4acd9..44ac3a901 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp @@ -200,8 +200,10 @@ class WSLCE2EContainerListTests WSLC_TEST_METHOD(WSLCE2E_Container_List_TemplateFormat) { // Create a container - RunWslcAndVerify(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()), {.Stderr = L"", .ExitCode = 0}); - RunWslcAndVerify(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()), {.Stderr = L"", .ExitCode = 0}); + RunWslcAndVerify( + std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()), {.Stderr = L"", .ExitCode = 0}); + RunWslcAndVerify( + std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()), {.Stderr = L"", .ExitCode = 0}); // List containers with json format auto result = RunWslc(L"container list --all --format \"Name = {{.Name}}\"");