wslc compose POC - #41378
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a proof-of-concept wslc compose command and a minimal backing service-side compose session object, along with tests and supporting plumbing (session helpers, CLI wiring, and MSI/IDL updates).
Changes:
- Add a new
composecommand tree (create/up/start/attach/stop) and a CLI service implementation to drive compose sessions. - Add an
IWSLCComposeSessionCOM interface plus a minimalWSLCComposeSessionimplementation andIWSLCSession::CreateComposeSession. - Add new Windows tests for compose, and refactor shared WSLC test helpers into
test/windows/Common.*.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/windows/WSLCTests.cpp | Refactors session/image setup to shared test helpers. |
| test/windows/WSLCComposeTests.cpp | Adds new TAEF tests covering compose session lifecycle and container name networking. |
| test/windows/wslc/WSLCCLICommandUnitTests.cpp | Adds a unit test verifying the compose subcommand tree. |
| test/windows/Common.h | Declares new shared WSLC test helper APIs in wsl::test. |
| test/windows/Common.cpp | Implements shared WSLC test helpers (session creation, default settings, load images). |
| test/windows/CMakeLists.txt | Adds WSLCComposeTests.cpp to the Windows test build. |
| src/windows/wslcsession/WSLCSession.h | Adds compose session entry points and supporting internals to WSLCSession. |
| src/windows/wslcsession/WSLCSession.cpp | Implements CreateComposeSession and compose container creation; refactors pull-image internals. |
| src/windows/wslcsession/WSLCComposeSession.h | Introduces the COM class implementing IWSLCComposeSession. |
| src/windows/wslcsession/WSLCComposeSession.cpp | Implements the minimal compose session methods (list/start/stop; attach stubbed). |
| src/windows/wslcsession/CMakeLists.txt | Adds new sources/headers and links yaml-cpp for compose parsing support. |
| src/windows/wslc/services/ConsoleService.h | Adds an overload to relay non-tty output to provided handles. |
| src/windows/wslc/services/ConsoleService.cpp | Implements the new overload and makes stdout/stderr relays conditional. |
| src/windows/wslc/services/ComposeService.h | Adds a CLI-side compose service wrapper. |
| src/windows/wslc/services/ComposeService.cpp | Implements CLI-side compose operations (create/up/start/attach/stop). |
| src/windows/wslc/commands/RootCommand.cpp | Registers compose under the root command. |
| src/windows/wslc/commands/ComposeCommand.h | Declares the compose command hierarchy. |
| src/windows/wslc/commands/ComposeCommand.cpp | Implements compose commands and argument plumbing. |
| src/windows/service/inc/wslc.idl | Adds IWSLCComposeSession and IWSLCSession::CreateComposeSession. |
| src/windows/common/WSLCContainerLauncher.h | Exposes reusable option-storage creation for container launchers. |
| src/windows/common/WSLCContainerLauncher.cpp | Refactors container option building into CreateOptions() to preserve pointer lifetimes. |
| msipackage/package.wix.in | Registers the new compose session interface IID. |
| localization/strings/en-US/Resources.resw | Adds localized strings for compose command help/args and an invalid-file error message. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # Session and container implementation | ||
| ComposeSpec.cpp | ||
| ServiceContainerLauncher.cpp | ||
| WSLCSession.cpp | ||
| WSLCSessionRuntime.cpp | ||
| WSLCComposeSession.cpp | ||
| WSLCContainer.cpp |
| int ComposeService::Attach(Terminal& Terminal, models::Session& Session, const std::wstring& Path) | ||
| { | ||
| [[maybe_unused]] auto operation = Session.BeginContainerOperation(); | ||
| auto composeSession = Open(Session, Path); |
| HRESULT WSLCComposeSession::Attach() | ||
| { | ||
| // TODO | ||
| return S_OK; | ||
| } |
| VERIFY_SUCCEEDED(composeSession->Attach()); | ||
| VERIFY_SUCCEEDED(composeSession->Stop(10)); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
src/windows/wslcsession/ComposeSpec.cpp:223
- YAML::LoadFile is called with Path.string(), which can corrupt non-ASCII/Unicode Windows paths (and fail to open the compose file). Use an explicit UTF-8 conversion for the filesystem path before passing it to yaml-cpp.
ComposeSpec ParseComposeFile(const std::filesystem::path& Path)
{
const auto root = YAML::LoadFile(Path.string());
const auto services = root["services"];
if (!services || !services.IsMap() || services.size() == 0)
{
ThrowInvalidComposeFile(Path, L"the file must contain a non-empty services map");
}
src/windows/wslcsession/ComposeSpec.cpp:358
- ComposeSpec::Parse can reach the end of a non-void function without an explicit return, which can trigger build warnings (and some toolchains treat it as an error) even though ThrowInvalidComposeFile is [[noreturn]]. Add an explicit unreachable/return after the catch to make control-flow obvious to the compiler.
ComposeSpec ComposeSpec::Parse(const std::filesystem::path& Path)
{
try
{
return ParseComposeFile(Path);
}
catch (const YAML::Exception& exception)
{
ThrowInvalidComposeFile(Path, wsl::shared::string::MultiByteToWide(exception.what()));
}
}
src/windows/wslc/services/ComposeService.cpp:80
- ComposeService::Attach unconditionally adds relay handles for stdout/stderr even if the container attach call returned null/invalid handles. ConsoleService::RelayNonTtyProcess was updated to guard against missing stdout/stderr, so this path should do the same to avoid relaying from invalid handles.
THROW_IF_FAILED(container->Attach(nullptr, &stdinHandle, &stdoutHandle, &stderrHandle));
// TODO: Add support for stdin, tty processes, stop on ctrl-c.
io.AddHandle(std::make_unique<wsl::windows::common::io::RelayHandle<wsl::windows::common::io::ReadHandle>>(
stdoutHandle.Release(), GetStdHandle(STD_OUTPUT_HANDLE)));
io.AddHandle(std::make_unique<wsl::windows::common::io::RelayHandle<wsl::windows::common::io::ReadHandle>>(
stderrHandle.Release(), GetStdHandle(STD_ERROR_HANDLE)));
}
| std::string networkName = Spec.ProjectName + "_default"; // TODO: Implement this properly. | ||
|
|
||
| // Create a network for the compose session. | ||
| // TODO: open an existing network instead of deleting. | ||
|
|
||
| auto networkCleanup = DeleteNetworkImpl(networkName.c_str()); | ||
| THROW_HR_IF_MSG( | ||
| networkCleanup, | ||
| FAILED(networkCleanup) && networkCleanup != WSLC_E_NETWORK_NOT_FOUND, | ||
| "Failed to delete network %hs", | ||
| networkName.c_str()); | ||
|
|
||
| WSLCNetworkOptions networkOptions{}; | ||
| networkOptions.Name = networkName.c_str(); | ||
| THROW_IF_FAILED(CreateNetworkImpl(&networkOptions)); | ||
|
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 4 comments.
Suppressed comments (1)
src/windows/wslcsession/WSLCSession.cpp:2358
- CreateComposeContainers deletes any existing network named _default before creating a new one. If another compose session/container set is using that network (or the user created one with the same name), this can disrupt running workloads or cause compose session creation to fail (e.g., network in use).
std::string networkName = Spec.ProjectName + "_default"; // TODO: Implement this properly.
// Create a network for the compose session.
// TODO: open an existing network instead of deleting.
auto networkCleanup = DeleteNetworkImpl(networkName.c_str());
THROW_HR_IF_MSG(
networkCleanup,
FAILED(networkCleanup) && networkCleanup != WSLC_E_NETWORK_NOT_FOUND,
"Failed to delete network %hs",
networkName.c_str());
WSLCNetworkOptions networkOptions{};
networkOptions.Name = networkName.c_str();
THROW_IF_FAILED(CreateNetworkImpl(&networkOptions));
| private: | ||
| std::mutex m_lock; | ||
| WSLCSession* m_session{}; | ||
| std::wstring m_configPath; | ||
| ComposeSpec m_spec; | ||
| std::vector<Microsoft::WRL::ComPtr<IWSLCContainer>> m_containers; |
| auto& entry = result[index]; | ||
| wil::unique_cotaskmem_ansistring name; | ||
| THROW_IF_FAILED(m_containers[index]->GetName(&name)); | ||
| THROW_IF_FAILED(m_containers[index]->GetId(entry.Id)); | ||
| THROW_IF_FAILED(m_containers[index]->GetState(&entry.State)); |
| const auto command = settings["command"]; | ||
| if (command && command.IsScalar()) | ||
| { | ||
| // TODO: Implement proper parsing | ||
| definition.Command = shared::string::Split(command.as<std::string>(), ' '); | ||
| } | ||
| else | ||
| { | ||
| definition.Command = ParseComposeStringList(Path, serviceName, command, "command"); | ||
| } |
| io.AddHandle(std::make_unique<wsl::windows::common::io::RelayHandle<wsl::windows::common::io::ReadHandle>>( | ||
| stdoutHandle.Release(), GetStdHandle(STD_OUTPUT_HANDLE))); | ||
|
|
||
| io.AddHandle(std::make_unique<wsl::windows::common::io::RelayHandle<wsl::windows::common::io::ReadHandle>>( | ||
| stderrHandle.Release(), GetStdHandle(STD_ERROR_HANDLE))); |
Summary of the Pull Request
This change is a very simplified implementation for
wslc compose. With this, a simple compose.yml file like this runs successfully:PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed