Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions src/windows/wslc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,52 @@ 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_SOURCE_DIR}/tools/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")
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 ${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 lib /def:${GO_TEMPLATE_DIR}/render.def /out:${GO_OUTPUT_LIB} /machine:${GO_LIB_MACHINE}
WORKING_DIRECTORY ${GO_TEMPLATE_DIR}
Comment on lines +13 to +30
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CGO cross-compilation is often not supported without an appropriate cross C compiler. This command sets GOARCH based on TARGET_PLATFORM (including arm64) while forcing CGO_ENABLED=1; building an ARM64 WSLC binary on an x64 host is likely to fail. Consider either building this DLL only for native builds, or wiring up CC/CXX for the required cross compiler, or disabling templates on cross-arch builds.

Copilot uses AI. Check for mistakes.
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})
Comment on lines +9 to +36
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WSLC build now unconditionally depends on Go + cgo (and a working C toolchain for cgo). This is a significant build/CI dependency: consider guarding it behind a CMake option and using find_program(go) with a clear error message, or supplying a prebuilt artifact, so developers/CI without Go+cgo aren’t blocked when building unrelated WSLC changes.

Copilot uses AI. Check for mistakes.

Comment on lines +24 to +37
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds an unconditional build-time dependency on the Go toolchain (and CGO) via an ALL custom target. On developer machines/CI images without Go installed, the entire WSLC build will fail with a non-obvious error. Please add an explicit find_program(go ...)/version check (with a clear fatal error), or gate this behind a CMake option / prebuilt artifact so the core build doesn’t silently pick up a new toolchain requirement.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +37
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotemplate_lib is added with ALL, so building WSLC will always require a working Go toolchain (and cgo’s C compiler toolchain) available on the build machine. Consider adding an explicit find_program(go ...)/toolchain check with a clear failure message, or gating this behind an option so developers/CI environments without Go+cgo don’t fail unexpectedly.

Copilot uses AI. Check for mistakes.
# 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})

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)
Expand All @@ -24,7 +61,11 @@ add_executable(wslc $<TARGET_OBJECTS:wslclib>)

target_link_libraries(wslc wslclib)

add_custom_command(TARGET wslc POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GO_OUTPUT_DLL} $<TARGET_FILE_DIR:wslc>/render.dll
)
Comment on lines +64 to +66
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wslc.exe now has a runtime dependency on render.dll, but only a post-build copy step is added. Please ensure the DLL is also included wherever binaries are packaged/deployed (e.g., MSI/MSIX inputs) so installed builds don't fail to start due to a missing DLL. One way is to copy it into the common ${BIN} output directory as part of the gotemplate build/installation step and explicitly include it in packaging manifests.

Copilot uses AI. Check for mistakes.

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})
24 changes: 5 additions & 19 deletions src/windows/wslc/arguments/ArgumentValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ void Argument::Validate(const ArgMap& execArgs) const
{
switch (m_argType)
{
case ArgType::Format:
validation::ValidateFormatTypeFromString(execArgs.GetAll<ArgType::Format>(), m_name);
break;

case ArgType::Signal:
validation::ValidateWSLCSignalFromString(execArgs.GetAll<ArgType::Signal>(), m_name);
break;
Expand Down Expand Up @@ -144,29 +140,19 @@ WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring
return static_cast<WSLCSignal>(signalValue);
}

void ValidateFormatTypeFromString(const std::vector<std::wstring>& 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"))
{
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;
Comment thread
AmelBawa-msft marked this conversation as resolved.
Comment on lines 151 to +155
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetFormatTypeFromString now treats any value other than the exact strings "json" and "table" as FormatType::Template. This means typos or previously-invalid values (e.g. --format xml) will silently be interpreted as a template (often rendering as a constant string), rather than producing a clear validation error. Consider reserving a dedicated marker/flag for templates (or otherwise distinguishing templates from format keywords) so truly unsupported format values still fail fast with a user-friendly error.

Copilot uses AI. Check for mistakes.
}

} // namespace wsl::windows::wslc::validation
3 changes: 1 addition & 2 deletions src/windows/wslc/arguments/ArgumentValidation.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ T GetIntegerFromString(const std::wstring& value, const std::wstring& argName =
void ValidateWSLCSignalFromString(const std::vector<std::wstring>& values, const std::wstring& argName);
WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName = {});

void ValidateFormatTypeFromString(const std::vector<std::wstring>& 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<std::wstring>& values);

Expand Down
1 change: 0 additions & 1 deletion src/windows/wslc/commands/ContainerCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
12 changes: 0 additions & 12 deletions src/windows/wslc/commands/ContainerListCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArgType::Format>();
if (!IsEqual(format, L"json") && !IsEqual(format, L"table"))
{
throw CommandException(Localization::WSLCCLI_InvalidFormatError());
}
}
}

// clang-format off
void ContainerListCommand::ExecuteInternal(CLIExecutionContext& context) const
{
Expand Down
67 changes: 67 additions & 0 deletions src/windows/wslc/core/TemplateRenderer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

GoTemplateRenderer.cpp
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File header Module Name says GoTemplateRenderer.cpp, but the file is TemplateRenderer.cpp. Please update the header to match the actual filename/module to avoid confusion when grepping or auditing.

Suggested change
GoTemplateRenderer.cpp
TemplateRenderer.cpp

Copilot uses AI. Check for mistakes.

Abstract:

Implementation of the Go template renderer.

Comment on lines +5 to +12
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment’s Module Name says "GoTemplateRenderer.cpp" but the file is TemplateRenderer.cpp. Please update the comment to match the actual filename to avoid confusion when grepping or auditing.

Copilot uses AI. Check for mistakes.
--*/

#include "TemplateRenderer.h"
#include <string>

// 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 {

using namespace wsl::shared::string;

TemplateRenderer::RenderResult TemplateRenderer::TryRender(const std::string& templateStr, const std::string& jsonData, std::wstring& output)
{
try
{
char* rawOutput = nullptr;
auto success = TryRenderGoTemplate(const_cast<char*>(templateStr.c_str()), const_cast<char*>(jsonData.c_str()), &rawOutput);

Comment on lines +21 to +36
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extern declarations use mutable char* and require const_cast when calling from std::string. Since the Go function does not mutate the inputs, declare the parameters as const char* in the C++ extern (and adjust the Go signature/usage if needed) to avoid casting away const.

Copilot uses AI. Check for mistakes.
std::string result(rawOutput ? rawOutput : "");
FreeGoString(rawOutput);

output = MultiByteToWide(result);
return static_cast<RenderResult>(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
42 changes: 42 additions & 0 deletions src/windows/wslc/core/TemplateRenderer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*++

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 <string>

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.
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment for TryRender says it “Returns true on success… false on failure”, but the function actually returns a RenderResult enum. Update the comment to match the API contract (including what output contains for each RenderResult).

Suggested change
// Returns true on success with the rendered output, false on failure with an error message.
// Returns RenderResult::Success on success, in which case output contains the rendered output.
// Returns a failure RenderResult on failure, in which case output contains an error message describing the failure.

Copilot uses AI. Check for mistakes.
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
1 change: 1 addition & 0 deletions src/windows/wslc/services/ContainerModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum class FormatType
{
Table,
Json,
Template,
};
Comment on lines 25 to 29
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding Template to the shared FormatType enum affects multiple commands (e.g., both container list and image list) but only container list implements FormatType::Template. If templates are intended to be container-only, consider introducing a separate format enum/argument for that command to avoid forcing all other format consumers to handle Template.

Copilot uses AI. Check for mistakes.

struct ContainerOptions
Expand Down
15 changes: 15 additions & 0 deletions src/windows/wslc/tasks/ContainerTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Module Name:
Implementation of container command related execution logic.

--*/

#include "Argument.h"
#include "ArgumentValidation.h"
#include "CLIExecutionContext.h"
Expand All @@ -20,6 +21,7 @@ Module Name:
#include "SessionModel.h"
#include "SessionService.h"
#include "TableOutput.h"
#include "TemplateRenderer.h"
#include <wil/result_macros.h>
#include <wslc_schema.h>

Expand All @@ -29,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
Expand Down Expand Up @@ -167,6 +170,18 @@ void ListContainers(CLIExecutionContext& context)
table.Complete();
break;
}
case FormatType::Template:
{
auto templateStr = WideToMultiByte(context.Args.Get<ArgType::Format>());
for (const auto& container : containers)
{
auto json = ToJson(container, c_jsonPrettyPrintIndent);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the template rendering loop, each container is serialized as pretty-printed JSON (c_jsonPrettyPrintIndent) and then parsed by the Go renderer. Since this JSON isn’t directly printed, consider using compact JSON to reduce overhead when listing large numbers of containers.

Suggested change
auto json = ToJson(container, c_jsonPrettyPrintIndent);
auto json = ToJson(container);

Copilot uses AI. Check for mistakes.
std::wstring result;
TemplateRenderer::Render(templateStr, json, result);
PrintMessage(result);
}
break;
}
default:
THROW_HR(E_UNEXPECTED);
}
Expand Down
14 changes: 14 additions & 0 deletions src/windows/wslc/tasks/ImageTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ Module Name:
#include "PullImageCallback.h"
#include "TableOutput.h"
#include "Task.h"
#include "TemplateRenderer.h"
#include <format>

using namespace wsl::shared;
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)
Expand Down Expand Up @@ -124,6 +126,18 @@ void ListImages(CLIExecutionContext& context)
table.Complete();
break;
}
case FormatType::Template:
{
auto templateStr = WideToMultiByte(context.Args.Get<ArgType::Format>());
for (const auto& image : images)
{
auto json = ToJson(image, c_jsonPrettyPrintIndent);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the template rendering loop, each item is converted to pretty-printed JSON (c_jsonPrettyPrintIndent) and then parsed again by the Go renderer. Since the JSON here is only an internal transport format (not user-visible), consider generating compact JSON to reduce CPU/allocations when listing many images.

Suggested change
auto json = ToJson(image, c_jsonPrettyPrintIndent);
auto json = ToJson(image);

Copilot uses AI. Check for mistakes.
std::wstring result;
TemplateRenderer::Render(templateStr, json, result);
PrintMessage(result);
}
break;
}
default:
THROW_HR(E_UNEXPECTED);
}
Expand Down
3 changes: 3 additions & 0 deletions src/windows/wslc/tools/gotemplate/render.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
EXPORTS
TryRenderGoTemplate
FreeGoString
Comment on lines +1 to +3
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with other .def files in the repo (which typically specify LIBRARY ), consider adding a LIBRARY directive here. This helps avoid ambiguity about the import library’s expected DLL name.

Copilot uses AI. Check for mistakes.
Loading
Loading