CLI: Update all non-help output to use Reporter (#41010)
* Update all non-help output to use Reporter
David Bennett committed
Jul 17, 2026 at 15:20 UTC
f642f80164ef138384551d8fdb021cdab693db5e
25 files changed
+269
-217
src/windows/wslc/commands/RegistryCommand.cpp
+30
-20
@@ -25,30 +25,40 @@ using namespace wsl::shared;
25
26
namespace {
27
28
-auto MaskInput()
28
+std::wstring Prompt(wsl::windows::wslc::Reporter& reporter, const std::wstring& label, bool maskInput)
29
{
30
+ // Write without a trailing newline so the cursor stays inline (matching Docker's behavior).
31
+ reporter.Info(L"{}", label);
32
+
33
HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
31
- DWORD mode = 0;
34
+ DWORD previousMode = 0;
35
+ const bool canMask = maskInput && (input != INVALID_HANDLE_VALUE) && GetConsoleMode(input, &previousMode);
36
+
37
+ // Armed before echo is disabled and built from a direct lambda (no allocation, can't throw) so a
38
+ // failure while masking still restores the console. Only acts once echo was actually disabled.
39
+ bool echoDisabled = false;
40
+ auto restoreConsole = wil::scope_exit([input, previousMode, &echoDisabled, &reporter]() {
41
+ if (!echoDisabled)
42
+ {
43
+ return;
44
+ }
45
+
46
+ SetConsoleMode(input, previousMode);
47
+ // Runs from a noexcept scope_exit destructor, possibly during unwinding, so swallow any
48
+ // output failure to avoid std::terminate.
49
+ try
50
+ {
51
+ reporter.Info(L"\n");
52
+ }
53
+ CATCH_LOG()
54
+ });
55
33
- if ((input != INVALID_HANDLE_VALUE) && GetConsoleMode(input, &mode))
56
+ if (canMask)
57
{
35
- THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(input, mode & ~ENABLE_ECHO_INPUT));
36
- return wil::scope_exit(std::function<void()>([input, mode] {
37
- SetConsoleMode(input, mode);
38
- std::wcerr << L'\n';
39
- }));
58
+ THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(input, previousMode & ~ENABLE_ECHO_INPUT));
59
+ echoDisabled = true;
60
}
61
42
- return wil::scope_exit(std::function<void()>([] {}));
43
-}
44
-
45
-std::wstring Prompt(const std::wstring& label, bool maskInput)
46
-{
47
- // Write without a trailing newline so the cursor stays inline (matching Docker's behavior).
48
- std::wcerr << label;
49
-
50
- auto restoreConsole = maskInput ? MaskInput() : wil::scope_exit(std::function<void()>([] {}));
51
-
62
std::wstring value;
63
std::getline(std::wcin, value);
64
@@ -127,7 +137,7 @@ void RegistryLoginCommand::ExecuteInternal(CLIExecutionContext& context) const
137
// Prompt for username if not provided.
138
if (!context.Args.Contains(ArgType::Username))
139
{
130
- context.Args.Add(ArgType::Username, Prompt(Localization::WSLCCLI_LoginUsernamePrompt(), false));
140
+ context.Args.Add(ArgType::Username, Prompt(context.Reporter, Localization::WSLCCLI_LoginUsernamePrompt(), false));
141
}
142
143
// Resolve password: --password, --password-stdin, or interactive prompt.
@@ -146,7 +156,7 @@ void RegistryLoginCommand::ExecuteInternal(CLIExecutionContext& context) const
156
}
157
else
158
{
149
- context.Args.Add(ArgType::Password, Prompt(Localization::WSLCCLI_LoginPasswordPrompt(), true));
159
+ context.Args.Add(ArgType::Password, Prompt(context.Reporter, Localization::WSLCCLI_LoginPasswordPrompt(), true));
160
}
161
}
162
src/windows/wslc/commands/RootCommand.cpp
+1
-1
@@ -105,7 +105,7 @@ void RootCommand::ExecuteInternal(CLIExecutionContext& context) const
105
{
106
if (context.Args.Contains(ArgType::Version))
107
{
108
- VersionCommand::PrintVersion();
108
+ VersionCommand::PrintVersion(context.Reporter);
109
return;
110
}
111
src/windows/wslc/commands/SettingsCommand.cpp
+1
-1
@@ -83,7 +83,7 @@ std::wstring SettingsResetCommand::LongDescription() const
83
void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const
84
{
85
settings::User().Reset();
86
- PrintMessage(Localization::WSLCCLI_SettingsResetConfirm());
86
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_SettingsResetConfirm());
87
}
88
89
} // namespace wsl::windows::wslc
src/windows/wslc/commands/VersionCommand.cpp
+6
-4
@@ -11,8 +11,10 @@ Abstract:
11
Implementation of the version command.
12
13
--*/
14
+
15
#include "VersionCommand.h"
16
#include "ArgumentValidation.h"
17
+#include "CLIExecutionContext.h"
18
#include "JsonUtils.h"
19
20
using namespace wsl::shared;
@@ -38,9 +40,9 @@ std::wstring VersionCommand::LongDescription() const
40
return Localization::WSLCCLI_VersionLongDesc();
41
}
42
41
-void VersionCommand::PrintVersion()
43
+void VersionCommand::PrintVersion(Reporter& reporter)
44
{
43
- wsl::windows::common::wslutil::PrintMessage(std::format(L"{} {}", s_ExecutableName, WSL_PACKAGE_VERSION));
45
+ reporter.Output(L"{} {}\n", s_ExecutableName, WSL_PACKAGE_VERSION);
46
}
47
48
void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const
@@ -57,11 +59,11 @@ void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const
59
{
60
nlohmann::json root;
61
root["Client"]["Version"] = std::string{WSL_PACKAGE_VERSION};
60
- wsl::windows::common::wslutil::PrintMessage(MultiByteToWide(root.dump(c_jsonPrettyPrintIndent)));
62
+ context.Reporter.Output(L"{}\n", MultiByteToWide(root.dump(c_jsonPrettyPrintIndent)));
63
break;
64
}
65
case FormatType::Table:
64
- PrintVersion();
66
+ PrintVersion(context.Reporter);
67
break;
68
default:
69
THROW_HR(E_UNEXPECTED);
src/windows/wslc/commands/VersionCommand.h
+3
-1
@@ -15,13 +15,15 @@ Abstract:
15
#include "Command.h"
16
17
namespace wsl::windows::wslc {
18
+struct Reporter;
19
struct VersionCommand final : public Command
20
{
21
constexpr static std::wstring_view CommandName = L"version";
22
VersionCommand(const std::wstring& parent) : Command(CommandName, parent)
23
{
24
}
24
- static void PrintVersion();
25
+
26
+ static void PrintVersion(Reporter& reporter);
27
std::vector<Argument> GetArguments() const override;
28
std::wstring ShortDescription() const override;
29
std::wstring LongDescription() const override;
src/windows/wslc/services/BuildImageCallback.cpp
+19
-23
@@ -20,6 +20,9 @@ namespace wsl::windows::wslc::services {
20
using wsl::windows::common::string::MultiByteToWide;
21
using namespace wsl::windows::common::vt;
22
23
+// Fallback width used when the console width can't be queried.
24
+constexpr int c_fallbackConsoleWidth = 79;
25
+
26
BuildImageCallback::~BuildImageCallback()
27
try
28
{
@@ -38,18 +41,12 @@ try
41
{
42
for (const auto& line : m_allLines)
43
{
41
- WriteTerminal(MultiByteToWide(line));
44
+ m_reporter.Info(L"{}", line);
45
}
46
}
47
}
48
CATCH_LOG()
49
47
-void BuildImageCallback::WriteTerminal(std::wstring_view content) const
48
-{
49
- DWORD written;
50
- LOG_IF_WIN32_BOOL_FALSE(WriteConsoleW(m_console, content.data(), static_cast<DWORD>(content.size()), &written, nullptr));
51
-}
52
-
50
bool BuildImageCallback::IsCancelled() const
51
{
52
return WaitForSingleObject(m_cancelEvent, 0) == WAIT_OBJECT_0;
@@ -60,7 +57,7 @@ void BuildImageCallback::CollapseWindow()
57
if (m_displayedLines > 0)
58
{
59
// Move cursor up to the start of the display area, then erase to end of screen.
63
- WriteTerminal(Cursor::Up(m_displayedLines) + Erase::ScreenForward);
60
+ m_reporter.Info(L"{}{}", Cursor::Up(m_displayedLines), Erase::ScreenForward);
61
m_displayedLines = 0;
62
}
63
@@ -103,7 +100,7 @@ try
100
// Skip pull progress updates when output is redirected, show only major steps
101
if (!isPullProgress)
102
{
106
- wprintf(L"%hs", status);
103
+ m_reporter.Info(L"{}", status);
104
}
105
return S_OK;
106
}
@@ -177,16 +174,16 @@ try
174
const auto newlines = wide.substr(bodyLength);
175
wide.resize(bodyLength);
176
180
- WriteTerminal(std::format(L"{}{}{}{}", Format::Fg::BrightGreen, wide, Format::Default, newlines));
177
+ // Pass the color sequences as arguments (not baked into the string) so Reporter strips
178
+ // them when --no-color is set. The trailing newlines are emitted after the reset.
179
+ m_reporter.Info(L"{}{}{}{}", Format::Fg::BrightGreen, wide, Format::Default, newlines);
180
return S_OK;
181
}
182
CATCH_RETURN();
183
184
void BuildImageCallback::Redraw()
185
{
187
- CONSOLE_SCREEN_BUFFER_INFO info{};
188
- THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfo(m_console, &info));
189
- const int consoleWidth = std::max(0, static_cast<int>(info.srWindow.Right) - info.srWindow.Left);
186
+ const int consoleWidth = m_reporter.GetConsoleWidth(Reporter::Level::Info).value_or(c_fallbackConsoleWidth);
187
188
const bool showPending = !m_pendingLine.empty();
189
const int pullCount = static_cast<int>(m_pullLines.size());
@@ -198,16 +195,15 @@ void BuildImageCallback::Redraw()
195
}
196
const int displayCount = completedCount + reservedLines;
197
201
- // Build the entire frame in one buffer to minimize console writes. Hide the cursor
202
- // during the redraw so the user doesn't see it bouncing through the cursor movement,
203
- // then show it again at the final position. The dim attribute (\033[2m) renders the
204
- // scrolling lines de-emphasized regardless of the user's theme.
198
+ // Build the frame body in one buffer to minimize console writes. The cursor moves,
199
+ // erases, and text lines it holds are non-color VT that only runs when a VT console is
200
+ // attached. The cursor hide/show wrapper and the dim intensity attribute are passed as
201
+ // Sequence arguments to Reporter (below) so it strips the color ones (Dim/Normal) when
202
+ // --no-color is set, while leaving the non-color cursor moves intact.
203
//
204
// m_frameBuffer is a member so its backing allocation is reused across frames -
205
// it grows to the high-water mark and is never freed between redraws.
206
m_frameBuffer.clear();
209
- m_frameBuffer += Cursor::Hide;
210
- m_frameBuffer += Format::Dim;
207
208
// Move cursor to the start of the display area and erase from there to the end of
209
// the screen. \033[J handles the case where the new display is shorter than the
@@ -252,10 +248,10 @@ void BuildImageCallback::Redraw()
248
appendLine(line);
249
}
250
255
- m_frameBuffer += Format::Normal;
256
- m_frameBuffer += Cursor::Show;
257
-
258
- WriteTerminal(m_frameBuffer);
251
+ // Emit the frame as a single atomic write. Cursor Hide/Show are non-color and always
252
+ // rendered here (VT is on); Format::Dim/Normal are color sequences that Reporter strips
253
+ // under --no-color. The buffered body carries the cursor moves, erases, and text lines.
254
+ m_reporter.Info(L"{}{}{}{}{}", Cursor::Hide, Format::Dim, std::wstring_view{m_frameBuffer}, Format::Normal, Cursor::Show);
255
m_displayedLines = displayCount;
256
}
257
src/windows/wslc/services/BuildImageCallback.h
+5
-7
@@ -12,6 +12,7 @@ Abstract:
12
13
--*/
14
#pragma once
15
+#include "Reporter.h"
16
#include "SessionService.h"
17
#include "VTSupport.h"
18
#include <deque>
@@ -23,7 +24,8 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback
24
{
25
public:
26
// The cancel event handle must remain valid for the lifetime of this callback.
26
- BuildImageCallback(HANDLE cancelEvent, bool verbose) : m_verbose(verbose), m_cancelEvent(cancelEvent)
27
+ BuildImageCallback(Reporter& reporter, HANDLE cancelEvent, bool verbose) :
28
+ m_reporter(reporter), m_verbose(verbose), m_cancelEvent(cancelEvent)
29
{
30
}
31
~BuildImageCallback();
@@ -37,16 +39,12 @@ private:
39
void CollapseWindow();
40
void Redraw();
41
void RedrawIfNeeded();
40
- // Use WriteConsoleW directly rather than wprintf: wprintf is noticeably slower for
41
- // the per-redraw scrolling display and produces visible flicker.
42
- void WriteTerminal(std::wstring_view content) const;
42
bool IsCancelled() const;
43
44
+ Reporter& m_reporter;
45
const bool m_verbose;
46
const HANDLE m_cancelEvent;
47
- HANDLE m_console = GetStdHandle(STD_OUTPUT_HANDLE);
48
- bool m_isConsole = wsl::windows::common::wslutil::IsConsoleHandle(m_console);
49
- wsl::windows::common::vt::EnableVirtualTerminal m_vtMode{m_console};
47
+ bool m_isConsole = m_reporter.IsVTEnabled(Reporter::Level::Info);
48
std::deque<std::string> m_lines;
49
// Each entry already contains the trailing newline so the bytes match what's replayed.
50
// TODO: Track logs per step so the destructor can replay only the failing step's
src/windows/wslc/services/ConsoleService.cpp
+3
-2
@@ -173,13 +173,14 @@ void ConsoleService::RelayNonTtyProcess(wil::unique_handle&& Stdin, wil::unique_
173
io.Run({});
174
}
175
176
-int ConsoleService::AttachToCurrentConsole(wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh)
176
+int ConsoleService::AttachToCurrentConsole(
177
+ Reporter& reporter, wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh)
178
{
179
if (WI_IsFlagSet(process.Flags(), WSLCProcessFlagsTty))
180
{
181
if (!RelayInteractiveTty(console, process, process.GetStdHandle(WSLCFDTty).get(), triggerRefresh))
182
{
182
- windows::common::wslutil::PrintMessage(L"[detached]", stderr);
183
+ reporter.Info(L"[detached]\n");
184
return 0;
185
}
186
}
src/windows/wslc/services/ConsoleService.h
+2
-1
@@ -16,13 +16,14 @@ Abstract:
16
#include <wslc.h>
17
#include <WSLCContainerLauncher.h>
18
#include <ConsoleState.h>
19
+#include "Reporter.h"
20
21
namespace wsl::windows::wslc::services {
22
class ConsoleService
23
{
24
public:
25
static int AttachToCurrentConsole(
25
- wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh = false);
26
+ Reporter& reporter, wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh = false);
27
static bool RelayInteractiveTty(
28
wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess& process, HANDLE tty, bool triggerRefresh = false);
29
static void RelayNonTtyProcess(wil::unique_handle&& Stdin, wil::unique_handle&& Stdout, wil::unique_handle&& Stderr);
src/windows/wslc/services/ContainerService.cpp
+17
-17
@@ -31,7 +31,6 @@ Abstract:
31
namespace wsl::windows::wslc::services {
32
using wsl::windows::common::ClientRunningWSLCProcess;
33
using wsl::windows::common::wslc_schema::InspectContainer;
34
-using wsl::windows::common::wslutil::PrintMessage;
34
using namespace wsl::windows::common::wslutil;
35
using namespace wsl::shared;
36
using namespace wsl::windows::wslc::models;
@@ -43,7 +42,7 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector<const
42
}
43
44
static wsl::windows::common::RunningWSLCContainer CreateInternal(
46
- Session& session, const std::string& image, const ContainerOptions& options, IWarningCallback* warningCallback = nullptr)
45
+ Reporter& reporter, Session& session, const std::string& image, const ContainerOptions& options, IWarningCallback* warningCallback = nullptr)
46
{
47
auto processFlags = WSLCProcessFlagsNone;
48
WI_SetFlagIf(processFlags, WSLCProcessFlagsStdin, options.Interactive);
@@ -248,9 +247,10 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(
247
if (result == WSLC_E_IMAGE_NOT_FOUND)
248
{
249
{
251
- // Attempt to pull the image if not found
252
- ImageProgressCallback callback;
253
- PrintMessage(Localization::WSLCCLI_ImageNotFoundPulling(wsl::shared::string::MultiByteToWide(image)), stderr);
250
+ // Implicit pull for run/create: progress goes to Info (stderr), keeping stdout for the
251
+ // container id/output.
252
+ ImageProgressCallback callback(reporter, Reporter::Level::Info);
253
+ reporter.Info(L"{}\n", Localization::WSLCCLI_ImageNotFoundPulling(wsl::shared::string::MultiByteToWide(image)));
254
ImageService imageService;
255
imageService.Pull(session, image, &callback);
256
}
@@ -324,7 +324,7 @@ std::wstring ContainerService::FormatRelativeTime(ULONGLONG timestamp)
324
return pluralize(elapsed / SecondsPerYear, L"year", L"years");
325
}
326
327
-int ContainerService::Attach(Session& session, const std::string& id)
327
+int ContainerService::Attach(Reporter& reporter, Session& session, const std::string& id)
328
{
329
wil::com_ptr<IWSLCContainer> container;
330
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
@@ -355,7 +355,7 @@ int ContainerService::Attach(Session& session, const std::string& id)
355
wsl::windows::common::ConsoleState console;
356
if (!ConsoleService::RelayInteractiveTty(console, runningProcess, stdinLogs.Release().get(), true))
357
{
358
- wsl::windows::common::wslutil::PrintMessage(L"[detached]", stderr);
358
+ reporter.Info(L"[detached]\n");
359
return 0; // Exit early if user detached
360
}
361
}
@@ -425,7 +425,7 @@ std::wstring ContainerService::FormatPorts(WSLCContainerState state, const std::
425
return result;
426
}
427
428
-int ContainerService::Run(Session& session, const std::string& image, ContainerOptions runOptions)
428
+int ContainerService::Run(Reporter& reporter, Session& session, const std::string& image, ContainerOptions runOptions)
429
{
430
// Reserve the CID file (fails if it already exists) before creating the container so a
431
// container isn't created when the caller-requested path can't be written. The file is
@@ -434,7 +434,7 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
434
auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
435
436
// Create the container
437
- auto runningContainer = CreateInternal(session, image, runOptions, warningCallback.Get());
437
+ auto runningContainer = CreateInternal(reporter, session, image, runOptions, warningCallback.Get());
438
auto& container = runningContainer.Get();
439
440
WSLCContainerId containerId{};
@@ -465,18 +465,18 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
465
// Handle attach if requested
466
if (attach)
467
{
468
- return ConsoleService::AttachToCurrentConsole(console, runningContainer.GetInitProcess());
468
+ return ConsoleService::AttachToCurrentConsole(reporter, console, runningContainer.GetInitProcess());
469
}
470
471
- PrintMessage(L"%hs", stdout, containerId);
471
+ reporter.Output(L"{}\n", wsl::shared::string::MultiByteToWide(containerId));
472
return 0;
473
}
474
475
-CreateContainerResult ContainerService::Create(Session& session, const std::string& image, ContainerOptions runOptions)
475
+CreateContainerResult ContainerService::Create(Reporter& reporter, Session& session, const std::string& image, ContainerOptions runOptions)
476
{
477
CidFile cidFile(runOptions.CidFile);
478
auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
479
- auto runningContainer = CreateInternal(session, image, runOptions, warningCallback.Get());
479
+ auto runningContainer = CreateInternal(reporter, session, image, runOptions, warningCallback.Get());
480
runningContainer.SetDeleteOnClose(false);
481
auto& container = runningContainer.Get();
482
WSLCContainerId id{};
@@ -485,7 +485,7 @@ CreateContainerResult ContainerService::Create(Session& session, const std::stri
485
return {.Id = id};
486
}
487
488
-int ContainerService::Start(Session& session, const std::string& id, bool attach)
488
+int ContainerService::Start(Reporter& reporter, Session& session, const std::string& id, bool attach)
489
{
490
wil::com_ptr<IWSLCContainer> container;
491
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
@@ -512,7 +512,7 @@ int ContainerService::Start(Session& session, const std::string& id, bool attach
512
THROW_IF_FAILED(process->GetFlags(&processFlags));
513
ClientRunningWSLCProcess runningProcess(std::move(process), processFlags);
514
515
- return ConsoleService::AttachToCurrentConsole(console, std::move(runningProcess), true);
515
+ return ConsoleService::AttachToCurrentConsole(reporter, console, std::move(runningProcess), true);
516
}
517
518
void ContainerService::Stop(Session& session, const std::string& id, StopContainerOptions options)
@@ -583,7 +583,7 @@ std::vector<ContainerInformation> ContainerService::List(
583
return result;
584
}
585
586
-int ContainerService::Exec(Session& session, const std::string& id, ContainerOptions options)
586
+int ContainerService::Exec(Reporter& reporter, Session& session, const std::string& id, ContainerOptions options)
587
{
588
wil::com_ptr<IWSLCContainer> container;
589
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
@@ -611,7 +611,7 @@ int ContainerService::Exec(Session& session, const std::string& id, ContainerOpt
611
processLauncher.SetWorkingDirectory(std::move(options.WorkingDirectory));
612
}
613
614
- return ConsoleService::AttachToCurrentConsole(console, processLauncher.Launch(*container));
614
+ return ConsoleService::AttachToCurrentConsole(reporter, console, processLauncher.Launch(*container));
615
}
616
617
InspectContainer ContainerService::Inspect(Session& session, const std::string& id)
src/windows/wslc/services/ContainerService.h
+6
-5
@@ -14,6 +14,7 @@ Abstract:
14
#pragma once
15
#include "SessionModel.h"
16
#include "ContainerModel.h"
17
+#include "Reporter.h"
18
#include <docker_schema.h>
19
#include <wslc_schema.h>
20
@@ -23,17 +24,17 @@ struct ContainerService
24
static std::wstring ContainerStateToString(WSLCContainerState state, ULONGLONG stateChangedAt = 0);
25
static std::wstring FormatRelativeTime(ULONGLONG timestamp);
26
static std::wstring FormatPorts(WSLCContainerState state, const std::vector<models::PortInformation>& ports);
26
- static int Attach(models::Session& session, const std::string& id);
27
- static int Run(models::Session& session, const std::string& image, models::ContainerOptions options);
28
- static models::CreateContainerResult Create(models::Session& session, const std::string& image, models::ContainerOptions options);
29
- static int Start(models::Session& session, const std::string& id, bool attach = false);
27
+ static int Attach(Reporter& reporter, models::Session& session, const std::string& id);
28
+ static int Run(Reporter& reporter, models::Session& session, const std::string& image, models::ContainerOptions options);
29
+ static models::CreateContainerResult Create(Reporter& reporter, models::Session& session, const std::string& image, models::ContainerOptions options);
30
+ static int Start(Reporter& reporter, models::Session& session, const std::string& id, bool attach = false);
31
static void Stop(models::Session& session, const std::string& id, models::StopContainerOptions options);
32
static void Kill(models::Session& session, const std::string& id, WSLCSignal signal = WSLCSignalSIGKILL);
33
static void Delete(models::Session& session, const std::string& id, bool force);
34
static std::vector<models::ContainerInformation> List(
35
models::Session& session, bool all = false, int limit = -1, const std::vector<std::pair<std::string, std::string>>& filters = {});
36
36
- static int Exec(models::Session& session, const std::string& id, models::ContainerOptions options);
37
+ static int Exec(Reporter& reporter, models::Session& session, const std::string& id, models::ContainerOptions options);
38
static void Export(models::Session& session, const std::string& id, const std::wstring& outputPath);
39
static void Export(models::Session& session, const std::string& id, HANDLE outputHandle);
40
static void CopyToContainer(models::Session& session, const std::string& id, const std::string& destPath, HANDLE inputHandle, ULONGLONG contentSize);
src/windows/wslc/services/ImageProgressCallback.cpp
+48
-28
@@ -21,23 +21,24 @@ namespace wsl::windows::wslc::services {
21
using namespace wsl::shared;
22
using namespace wsl::windows::common::vt;
23
24
-void ImageProgressCallback::WriteTerminal(std::wstring_view content) const
25
-{
26
- DWORD written;
27
- LOG_IF_WIN32_BOOL_FALSE(WriteConsoleW(m_console, content.data(), static_cast<DWORD>(content.size()), &written, nullptr));
28
-}
24
+// Fallback width for the in-place progress display when the console width can't be queried. This
25
+// value already includes the autowrap guard (visible width minus one) so a wrapped line can't
26
+// corrupt the cursor-based rendering.
27
+constexpr int c_fallbackConsoleWidth = 79;
28
29
auto ImageProgressCallback::MoveToLine(int line)
30
{
31
if (line > 0)
32
{
34
- WriteTerminal(Cursor::Up(line).Get());
33
+ m_reporter.Write(m_level, L"{}", Cursor::Up(line));
34
}
35
37
- return wil::scope_exit([line = line, this]() {
36
+ // scope_exit is noexcept and may fire during unwinding; scope_exit_log swallows output
37
+ // failures so a throw here can't call std::terminate.
38
+ return wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [line = line, this]() {
39
if (line > 1)
40
{
40
- WriteTerminal(Cursor::Down(line - 1).Get());
41
+ m_reporter.Write(m_level, L"{}", Cursor::Down(line - 1));
42
}
43
});
44
}
@@ -46,32 +47,57 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu
47
{
48
try
49
{
49
- if (!m_terminalMode.IsConsole())
50
+ // status is [unique] in the IDL, so it may be null; normalize before either path uses it.
51
+ status = (status != nullptr) ? status : "";
52
+
53
+ // The in-place progress display needs cursor movement, so when output is redirected fall
54
+ // back to a log stream: one line per new status, deduping the repeated byte-progress
55
+ // callbacks that share a status text.
56
+ if (!m_vtEnabled)
57
{
58
+ if (id == nullptr || *id == '\0')
59
+ {
60
+ m_reporter.Write(m_level, L"{}\n", status);
61
+ }
62
+ else
63
+ {
64
+ auto [it, inserted] = m_lastStatusById.try_emplace(id, status);
65
+ if (inserted || it->second != status)
66
+ {
67
+ it->second = status;
68
+ m_reporter.Write(m_level, L"{}: {}\n", id, status);
69
+ }
70
+ }
71
+
72
return S_OK;
73
}
74
75
+ // Hide the cursor while rendering so it doesn't bounce through the movements; scope_exit_log
76
+ // restores it on every exit path and can't call std::terminate during unwinding.
77
+ m_reporter.Write(m_level, L"{}", Cursor::Hide);
78
+ auto showCursor = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this]() { m_reporter.Write(m_level, L"{}", Cursor::Show); });
79
+
80
if (id == nullptr || *id == '\0') // Print all 'global' statuses on their own line
81
{
56
- WriteTerminal(std::format(L"{}\n", status));
82
+ m_reporter.Write(m_level, L"{}\n", status);
83
m_currentLine++;
84
return S_OK;
85
}
86
61
- auto info = Info();
87
+ const int visibleWidth = m_reporter.GetConsoleWidth(m_level).value_or(c_fallbackConsoleWidth);
88
89
auto it = m_statuses.find(id);
90
if (it == m_statuses.end())
91
{
92
// If this is the first time we see this ID, create a new line for it.
93
m_statuses.emplace(id, m_currentLine);
68
- WriteTerminal(GenerateStatusLine(status, id, current, total, info) + L'\n');
94
+ m_reporter.Write(m_level, L"{}\n", GenerateStatusLine(status, id, current, total, visibleWidth));
95
m_currentLine++;
96
}
97
else
98
{
99
auto revert = MoveToLine(m_currentLine - it->second);
74
- WriteTerminal(GenerateStatusLine(status, id, current, total, info) + L'\n');
100
+ m_reporter.Write(m_level, L"{}\n", GenerateStatusLine(status, id, current, total, visibleWidth));
101
}
102
103
return S_OK;
@@ -79,15 +105,12 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu
105
CATCH_RETURN();
106
}
107
82
-CONSOLE_SCREEN_BUFFER_INFO ImageProgressCallback::Info()
108
+std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, int visibleWidth)
109
{
84
- CONSOLE_SCREEN_BUFFER_INFO info{};
85
- THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info));
86
- return info;
87
-}
110
+ // status/id are [unique] in the IDL and may be null; treat null as empty before formatting.
111
+ const char* const safeStatus = (status != nullptr) ? status : "";
112
+ const char* const safeId = (id != nullptr) ? id : "";
113
89
-std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, const CONSOLE_SCREEN_BUFFER_INFO& info)
90
-{
114
std::wstring line;
115
if (total != 0)
116
{
@@ -121,21 +144,19 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id,
144
progress += std::format(L"/{}", wsl::shared::string::FormatBytes(total));
145
}
146
124
- line = std::format(L"{}: {} [{}] {}", id, status, bar, progress);
147
+ line = std::format(L"{}: {} [{}] {}", safeId, safeStatus, bar, progress);
148
}
149
else if (current != 0)
150
{
128
- line = std::format(L"{}: {} {}", id, status, wsl::shared::string::FormatBytes(current));
151
+ line = std::format(L"{}: {} {}", safeId, safeStatus, wsl::shared::string::FormatBytes(current));
152
}
153
else
154
{
132
- line = std::format(L"{}: {}", id, status);
155
+ line = std::format(L"{}: {}", safeId, safeStatus);
156
}
157
135
- // Use the visible window width (not the buffer width) to prevent wrapping.
136
- const auto visibleWidth = std::max(0, static_cast<int>(info.srWindow.Right) - info.srWindow.Left + 1);
137
-
138
- // Truncate to console width to prevent wrapping that would break cursor repositioning.
158
+ // Truncate to the console width to prevent wrapping that breaks cursor repositioning, then pad
159
+ // to erase any previously written characters on the line.
160
if (line.size() > static_cast<size_t>(visibleWidth))
161
{
162
line.resize(visibleWidth);
@@ -148,7 +169,6 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id,
169
}
170
}
171
151
- // Erase any previously written char on that line.
172
line.resize(visibleWidth, L' ');
173
174
return line;
src/windows/wslc/services/ImageProgressCallback.h
+14
-6
@@ -12,6 +12,7 @@ Abstract:
12
13
--*/
14
#pragma once
15
+#include "Reporter.h"
16
#include "SessionService.h"
17
#include "VTSupport.h"
18
#include <map>
@@ -24,17 +25,24 @@ class DECLSPEC_UUID("7A1D3376-835A-471A-8DC9-23653D9962D0") ImageProgressCallbac
25
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IProgressCallback, IFastRundown>
26
{
27
public:
28
+ // level selects the target stream: Output (stdout) for standalone pull/push, Info (stderr) for
29
+ // the implicit pull during run/create, matching Docker.
30
+ ImageProgressCallback(Reporter& reporter, Reporter::Level level) : m_reporter(reporter), m_level(level)
31
+ {
32
+ }
33
HRESULT OnProgress(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total) override;
34
35
private:
36
auto MoveToLine(int line);
31
- static CONSOLE_SCREEN_BUFFER_INFO Info();
32
- void WriteTerminal(std::wstring_view content) const;
33
- std::wstring GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, const CONSOLE_SCREEN_BUFFER_INFO& info);
37
+ std::wstring GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, int visibleWidth);
38
+ Reporter& m_reporter;
39
+ // Declared before m_vtEnabled, whose initializer reads it.
40
+ const Reporter::Level m_level;
41
std::map<std::string, int> m_statuses;
42
+ // Last status text per id; used only when redirected to dedupe repeated byte-progress callbacks.
43
+ std::map<std::string, std::string> m_lastStatusById;
44
int m_currentLine = 0;
36
- HANDLE m_console = GetStdHandle(STD_OUTPUT_HANDLE);
37
- wsl::windows::common::vt::EnableVirtualTerminal m_vtMode{m_console};
38
- wsl::windows::common::vt::ChangeTerminalMode m_terminalMode{m_console, false};
45
+ // The progress display only renders on a VT console.
46
+ bool m_vtEnabled = m_reporter.IsVTEnabled(m_level);
47
};
48
} // namespace wsl::windows::wslc::services
src/windows/wslc/services/SessionService.cpp
+10
-11
@@ -64,7 +64,7 @@ Session SessionService::OpenOrCreateDefaultSession()
64
return Session(std::move(session));
65
}
66
67
-int SessionService::Attach(const Session& session)
67
+int SessionService::Attach(Reporter& reporter, const Session& session)
68
{
69
// Configure console for interactive usage.
70
wsl::windows::common::ConsoleState console{};
@@ -113,12 +113,12 @@ int SessionService::Attach(const Session& session)
113
114
auto exitCode = process.GetExitCode();
115
116
- wslutil::PrintMessage(wsl::shared::Localization::MessageWslcShellExited(string::MultiByteToWide(shell), static_cast<int>(exitCode)), stdout);
116
+ reporter.Output(L"{}\n", wsl::shared::Localization::MessageWslcShellExited(string::MultiByteToWide(shell), static_cast<int>(exitCode)));
117
118
return static_cast<int>(exitCode);
119
}
120
121
-int SessionService::Enter(const std::wstring& storagePath, const std::wstring& displayName)
121
+int SessionService::Enter(Reporter& reporter, const std::wstring& storagePath, const std::wstring& displayName)
122
{
123
THROW_HR_IF(E_INVALIDARG, storagePath.empty());
124
THROW_HR_IF(E_INVALIDARG, displayName.empty());
@@ -129,7 +129,7 @@ int SessionService::Enter(const std::wstring& storagePath, const std::wstring& d
129
auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
130
THROW_IF_FAILED(sessionManager->EnterSession(displayName.c_str(), storagePath.c_str(), warningCallback.Get(), &session));
131
wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
132
- wsl::windows::common::wslutil::PrintMessage(Localization::MessageWslcCreatedSession(displayName), stderr);
132
+ reporter.Info(L"{}\n", Localization::MessageWslcCreatedSession(displayName));
133
134
const std::string shell = "/bin/sh";
135
wsl::windows::common::WSLCProcessLauncher launcher{shell, {shell, "--login"}, {"TERM=xterm-256color"}, WSLCProcessFlagsTty | WSLCProcessFlagsStdin};
@@ -138,7 +138,7 @@ int SessionService::Enter(const std::wstring& storagePath, const std::wstring& d
138
const auto windowSize = console.GetWindowSize();
139
launcher.SetTtySize(windowSize.Y, windowSize.X);
140
141
- return ConsoleService::AttachToCurrentConsole(console, launcher.Launch(*session.get()));
141
+ return ConsoleService::AttachToCurrentConsole(reporter, console, launcher.Launch(*session.get()));
142
}
143
144
std::vector<SessionInformation> SessionService::List()
@@ -161,7 +161,7 @@ std::vector<SessionInformation> SessionService::List()
161
return result;
162
}
163
164
-int SessionService::Run(const Session& session, const std::vector<std::string>& arguments)
164
+int SessionService::Run(Reporter& reporter, const Session& session, const std::vector<std::string>& arguments)
165
{
166
WI_ASSERT(!arguments.empty());
167
@@ -175,10 +175,10 @@ int SessionService::Run(const Session& session, const std::vector<std::string>&
175
THROW_IF_FAILED(result);
176
177
wsl::windows::common::ConsoleState console{};
178
- return ConsoleService::AttachToCurrentConsole(console, std::move(process.value()));
178
+ return ConsoleService::AttachToCurrentConsole(reporter, console, std::move(process.value()));
179
}
180
181
-int SessionService::TerminateSession(const Session& session)
181
+int SessionService::TerminateSession(Reporter& reporter, const Session& session)
182
{
183
HRESULT hr = session.Get()->Terminate();
184
if (FAILED(hr))
@@ -188,12 +188,11 @@ int SessionService::TerminateSession(const Session& session)
188
wil::unique_cotaskmem_string displayName;
189
if (SUCCEEDED(session.Get()->GetDisplayName(&displayName)) && displayName)
190
{
191
- wslutil::PrintMessage(
192
- Localization::MessageErrorCode(Localization::MessageWslcTerminateSessionFailed(displayName.get()), errorString), stderr);
191
+ reporter.Error(L"{}\n", Localization::MessageErrorCode(Localization::MessageWslcTerminateSessionFailed(displayName.get()), errorString));
192
}
193
else
194
{
196
- wslutil::PrintMessage(Localization::MessageErrorCode(Localization::MessageWslcTerminateDefaultSessionFailed(), errorString), stderr);
195
+ reporter.Error(L"{}\n", Localization::MessageErrorCode(Localization::MessageWslcTerminateDefaultSessionFailed(), errorString));
196
}
197
return 1;
198
}
src/windows/wslc/services/SessionService.h
+5
-4
@@ -14,6 +14,7 @@ Abstract:
14
#pragma once
15
16
#include "SessionModel.h"
17
+#include "Reporter.h"
18
#include <wslc.h>
19
20
namespace wsl::windows::wslc::services {
@@ -26,8 +27,8 @@ struct SessionInformation
27
28
struct SessionService
29
{
29
- static int Attach(const wsl::windows::wslc::models::Session& session);
30
- static int Enter(const std::wstring& storagePath, const std::wstring& displayName);
30
+ static int Attach(Reporter& reporter, const wsl::windows::wslc::models::Session& session);
31
+ static int Enter(Reporter& reporter, const std::wstring& storagePath, const std::wstring& displayName);
32
static std::vector<SessionInformation> List();
33
// Opens an existing session by name. Throws if not found.
34
static wsl::windows::wslc::models::Session OpenSession(const std::wstring& name);
@@ -36,8 +37,8 @@ struct SessionService
37
// Opens or creates the default session.
38
static wsl::windows::wslc::models::Session OpenOrCreateDefaultSession();
39
// Runs the given command and arguments in a session without a TTY, resolving the executable from PATH.
39
- static int Run(const wsl::windows::wslc::models::Session& session, const std::vector<std::string>& arguments);
40
- static int TerminateSession(const wsl::windows::wslc::models::Session& session);
40
+ static int Run(Reporter& reporter, const wsl::windows::wslc::models::Session& session, const std::vector<std::string>& arguments);
41
+ static int TerminateSession(Reporter& reporter, const wsl::windows::wslc::models::Session& session);
42
43
private:
44
// Common open-only session lookup with unified error handling.
src/windows/wslc/tasks/ContainerTasks.cpp
+30
-20
@@ -136,7 +136,7 @@ nlohmann::json ComputeContainerStatsJson(const wsl::windows::common::docker_sche
136
137
namespace wsl::windows::wslc::task {
138
139
-static bool TryInspectContainer(Session& session, const std::string& containerId, std::optional<wslc_schema::InspectContainer>& inspectData)
139
+static bool TryInspectContainer(Reporter& reporter, Session& session, const std::string& containerId, std::optional<wslc_schema::InspectContainer>& inspectData)
140
{
141
try
142
{
@@ -147,7 +147,7 @@ static bool TryInspectContainer(Session& session, const std::string& containerId
147
{
148
if (ex.GetErrorCode() == WSLC_E_CONTAINER_NOT_FOUND)
149
{
150
- PrintMessage(Localization::MessageWslcContainerNotFound(containerId.c_str()), stderr);
150
+ reporter.Error(L"{}\n", Localization::MessageWslcContainerNotFound(containerId.c_str()));
151
return false;
152
}
153
@@ -158,7 +158,7 @@ static bool TryInspectContainer(Session& session, const std::string& containerId
158
void AttachContainer::operator()(CLIExecutionContext& context) const
159
{
160
WI_ASSERT(context.Data.Contains(Data::Session));
161
- context.ExitCode = ContainerService::Attach(context.Data.Get<Data::Session>(), WideToMultiByte(m_containerId));
161
+ context.ExitCode = ContainerService::Attach(context.Reporter, context.Data.Get<Data::Session>(), WideToMultiByte(m_containerId));
162
}
163
164
void CreateContainer(CLIExecutionContext& context)
@@ -167,8 +167,11 @@ void CreateContainer(CLIExecutionContext& context)
167
WI_ASSERT(context.Args.Contains(ArgType::ImageId));
168
WI_ASSERT(context.Data.Contains(Data::ContainerOptions));
169
auto result = ContainerService::Create(
170
- context.Data.Get<Data::Session>(), WideToMultiByte(context.Args.Get<ArgType::ImageId>()), context.Data.Get<Data::ContainerOptions>());
171
- PrintMessage(MultiByteToWide(result.Id));
170
+ context.Reporter,
171
+ context.Data.Get<Data::Session>(),
172
+ WideToMultiByte(context.Args.Get<ArgType::ImageId>()),
173
+ context.Data.Get<Data::ContainerOptions>());
174
+ context.Reporter.Output(L"{}\n", MultiByteToWide(result.Id));
175
}
176
177
void ExecContainer(CLIExecutionContext& context)
@@ -177,7 +180,10 @@ void ExecContainer(CLIExecutionContext& context)
180
WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
181
WI_ASSERT(context.Data.Contains(Data::ContainerOptions));
182
context.ExitCode = ContainerService::Exec(
180
- context.Data.Get<Data::Session>(), WideToMultiByte(context.Args.Get<ArgType::ContainerId>()), context.Data.Get<Data::ContainerOptions>());
183
+ context.Reporter,
184
+ context.Data.Get<Data::Session>(),
185
+ WideToMultiByte(context.Args.Get<ArgType::ContainerId>()),
186
+ context.Data.Get<Data::ContainerOptions>());
187
}
188
189
void GetContainers(CLIExecutionContext& context)
@@ -222,7 +228,7 @@ void InspectContainers(CLIExecutionContext& context)
228
for (const auto& id : containerIds)
229
{
230
std::optional<wslc_schema::InspectContainer> inspectData;
225
- if (TryInspectContainer(session, WideToMultiByte(id), inspectData))
231
+ if (TryInspectContainer(context.Reporter, session, WideToMultiByte(id), inspectData))
232
{
233
result.push_back(*inspectData);
234
}
@@ -233,7 +239,7 @@ void InspectContainers(CLIExecutionContext& context)
239
}
240
241
auto json = ToJson(result, c_jsonPrettyPrintIndent);
236
- PrintMessage(MultiByteToWide(json));
242
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
243
}
244
245
void KillContainers(CLIExecutionContext& context)
@@ -250,7 +256,7 @@ void KillContainers(CLIExecutionContext& context)
256
for (const auto& id : containerIds)
257
{
258
ContainerService::Kill(session, WideToMultiByte(id), signal);
253
- PrintMessage(id);
259
+ context.Reporter.Output(L"{}\n", id);
260
}
261
}
262
@@ -547,7 +553,7 @@ void ListContainers(CLIExecutionContext& context)
553
// Print only the container ids
554
for (const auto& container : containers)
555
{
550
- PrintMessage(MultiByteToWide(container.Id));
556
+ context.Reporter.Output(L"{}\n", MultiByteToWide(container.Id));
557
}
558
559
return;
@@ -564,7 +570,7 @@ void ListContainers(CLIExecutionContext& context)
570
case FormatType::Json:
571
{
572
auto json = ToJson(containers, c_jsonPrettyPrintIndent);
567
- PrintMessage(MultiByteToWide(json));
573
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
574
break;
575
}
576
case FormatType::Table:
@@ -621,7 +627,7 @@ void RemoveContainers(CLIExecutionContext& context)
627
for (const auto& id : containerIds)
628
{
629
ContainerService::Delete(session, WideToMultiByte(id), force);
624
- PrintMessage(id);
630
+ context.Reporter.Output(L"{}\n", id);
631
}
632
}
633
@@ -631,7 +637,10 @@ void RunContainer(CLIExecutionContext& context)
637
WI_ASSERT(context.Args.Contains(ArgType::ImageId));
638
WI_ASSERT(context.Data.Contains(Data::ContainerOptions));
639
context.ExitCode = ContainerService::Run(
634
- context.Data.Get<Data::Session>(), WideToMultiByte(context.Args.Get<ArgType::ImageId>()), context.Data.Get<Data::ContainerOptions>());
640
+ context.Reporter,
641
+ context.Data.Get<Data::Session>(),
642
+ WideToMultiByte(context.Args.Get<ArgType::ImageId>()),
643
+ context.Data.Get<Data::ContainerOptions>());
644
}
645
646
void SetContainerOptionsFromArgs(CLIExecutionContext& context)
@@ -970,7 +979,7 @@ void ShowContainerStats(CLIExecutionContext& context)
979
{
980
case FormatType::Json:
981
{
973
- PrintMessage(MultiByteToWide(statsJson.dump(c_jsonPrettyPrintIndent)));
982
+ context.Reporter.Output(L"{}\n", MultiByteToWide(statsJson.dump(c_jsonPrettyPrintIndent)));
983
break;
984
}
985
case FormatType::Table:
@@ -1029,11 +1038,11 @@ void StartContainer(CLIExecutionContext& context)
1038
WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
1039
const auto& containerId = context.Args.Get<ArgType::ContainerId>();
1040
const bool attach = context.Args.Contains(ArgType::Attach);
1032
- context.ExitCode = ContainerService::Start(context.Data.Get<Data::Session>(), WideToMultiByte(containerId), attach);
1041
+ context.ExitCode = ContainerService::Start(context.Reporter, context.Data.Get<Data::Session>(), WideToMultiByte(containerId), attach);
1042
1043
if (!attach)
1044
{
1036
- PrintMessage(containerId);
1045
+ context.Reporter.Output(L"{}\n", containerId);
1046
}
1047
}
1048
@@ -1056,7 +1065,7 @@ void StopContainers(CLIExecutionContext& context)
1065
for (const auto& id : containersToStop)
1066
{
1067
ContainerService::Stop(context.Data.Get<Data::Session>(), WideToMultiByte(id), options);
1059
- PrintMessage(id);
1068
+ context.Reporter.Output(L"{}\n", id);
1069
}
1070
}
1071
@@ -1101,10 +1110,11 @@ void PruneContainers(CLIExecutionContext& context)
1110
1111
for (const auto& containerId : result.PrunedContainers)
1112
{
1104
- PrintMessage(MultiByteToWide(containerId));
1113
+ context.Reporter.Output(L"{}\n", MultiByteToWide(containerId));
1114
}
1115
1107
- PrintMessage(L"");
1108
- PrintMessage(Localization::WSLCCLI_ContainerPruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed)));
1116
+ context.Reporter.Output(L"\n");
1117
+ context.Reporter.Output(
1118
+ L"{}\n", Localization::WSLCCLI_ContainerPruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed)));
1119
}
1120
} // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/ImageTasks.cpp
+13
-14
@@ -71,7 +71,7 @@ namespace {
71
72
} // namespace
73
74
-static bool TryInspectImage(Session& session, const std::string& imageId, std::optional<wslc_schema::InspectImage>& inspectData)
74
+static bool TryInspectImage(Reporter& reporter, Session& session, const std::string& imageId, std::optional<wslc_schema::InspectImage>& inspectData)
75
{
76
try
77
{
@@ -82,7 +82,7 @@ static bool TryInspectImage(Session& session, const std::string& imageId, std::o
82
{
83
if (ex.GetErrorCode() == WSLC_E_IMAGE_NOT_FOUND)
84
{
85
- PrintMessage(Localization::MessageWslcImageNotFound(imageId.c_str()), stderr);
85
+ reporter.Error(L"{}\n", Localization::MessageWslcImageNotFound(imageId.c_str()));
86
return false;
87
}
88
@@ -117,15 +117,13 @@ void BuildImage(CLIExecutionContext& context)
117
target = context.Args.Get<ArgType::BuildTarget>();
118
}
119
120
- PrintMessage(std::format(L"Building image from directory: {}\n", contextPath), stdout);
121
-
120
WSLCBuildImageFlags flags = WSLCBuildImageFlagsNone;
121
WI_SetFlagIf(flags, WSLCBuildImageFlagsVerbose, context.Args.Contains(ArgType::Verbose));
122
WI_SetFlagIf(flags, WSLCBuildImageFlagsNoCache, context.Args.Contains(ArgType::NoCache));
123
WI_SetFlagIf(flags, WSLCBuildImageFlagsPull, context.Args.Contains(ArgType::BuildPull));
124
125
auto cancelEvent = context.CreateCancelEvent();
128
- BuildImageCallback callback(cancelEvent, context.Args.Contains(ArgType::Verbose));
126
+ BuildImageCallback callback(context.Reporter, cancelEvent, context.Args.Contains(ArgType::Verbose));
127
services::ImageService::Build(session, contextPath, tags, buildArgs, labels, dockerfilePath, target, flags, &callback, cancelEvent);
128
}
129
@@ -179,7 +177,7 @@ void ListImages(CLIExecutionContext& context)
177
case FormatType::Json:
178
{
179
auto json = ToJson(images, c_jsonPrettyPrintIndent);
182
- PrintMessage(MultiByteToWide(json));
180
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
181
break;
182
}
183
case FormatType::Table:
@@ -227,7 +225,7 @@ void PullImage(CLIExecutionContext& context)
225
auto& session = context.Data.Get<Data::Session>();
226
auto& imageId = context.Args.Get<ArgType::ImageId>();
227
230
- ImageProgressCallback callback;
228
+ ImageProgressCallback callback(context.Reporter, Reporter::Level::Output);
229
services::ImageService::Pull(session, WideToMultiByte(imageId), &callback);
230
}
231
@@ -238,7 +236,7 @@ void PushImage(CLIExecutionContext& context)
236
auto& session = context.Data.Get<Data::Session>();
237
auto& imageId = context.Args.Get<ArgType::ImageId>();
238
241
- ImageProgressCallback callback;
239
+ ImageProgressCallback callback(context.Reporter, Reporter::Level::Output);
240
services::ImageService::Push(session, WideToMultiByte(imageId), &callback);
241
}
242
@@ -304,7 +302,7 @@ void InspectImages(CLIExecutionContext& context)
302
for (const auto& id : imageIds)
303
{
304
std::optional<wslc_schema::InspectImage> inspectData;
307
- if (TryInspectImage(session, WideToMultiByte(id), inspectData))
305
+ if (TryInspectImage(context.Reporter, session, WideToMultiByte(id), inspectData))
306
{
307
result.push_back(*inspectData);
308
}
@@ -315,7 +313,7 @@ void InspectImages(CLIExecutionContext& context)
313
}
314
315
auto json = ToJson(result, c_jsonPrettyPrintIndent);
318
- PrintMessage(MultiByteToWide(json));
316
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
317
}
318
319
void SaveImage(CLIExecutionContext& context)
@@ -383,15 +381,16 @@ void PruneImages(CLIExecutionContext& context)
381
382
for (const auto& image : result.UntaggedImages)
383
{
386
- PrintMessage(Localization::WSLCCLI_ImagePruneUntagged(image));
384
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_ImagePruneUntagged(image));
385
}
386
387
for (const auto& image : result.DeletedImages)
388
{
391
- PrintMessage(Localization::WSLCCLI_ImagePruneDeleted(image));
389
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_ImagePruneDeleted(image));
390
}
391
394
- PrintMessage(L"");
395
- PrintMessage(Localization::WSLCCLI_ImagePruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed)));
392
+ context.Reporter.Output(L"\n");
393
+ context.Reporter.Output(
394
+ L"{}\n", Localization::WSLCCLI_ImagePruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed)));
395
}
396
} // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/InspectTasks.cpp
+2
-2
@@ -107,12 +107,12 @@ void Inspect(CLIExecutionContext& context)
107
}
108
else
109
{
110
- PrintMessage(Localization::WSLCCLI_ObjectNotFoundError(objectId), stderr);
110
+ context.Reporter.Error(L"{}\n", Localization::WSLCCLI_ObjectNotFoundError(objectId));
111
context.ExitCode = 1;
112
}
113
}
114
115
// Always print the array, even if it's empty or an error was encountered
116
- PrintMessage(MultiByteToWide(array.dump(c_jsonPrettyPrintIndent)));
116
+ context.Reporter.Output(L"{}\n", MultiByteToWide(array.dump(c_jsonPrettyPrintIndent)));
117
}
118
} // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/NetworkTasks.cpp
+12
-12
@@ -30,7 +30,7 @@ using namespace wsl::windows::wslc::services;
30
31
namespace wsl::windows::wslc::task {
32
33
-static bool TryInspectNetwork(Session& session, const std::string& networkName, std::optional<wslc_schema::Network>& inspectData)
33
+static bool TryInspectNetwork(Reporter& reporter, Session& session, const std::string& networkName, std::optional<wslc_schema::Network>& inspectData)
34
{
35
try
36
{
@@ -41,7 +41,7 @@ static bool TryInspectNetwork(Session& session, const std::string& networkName,
41
{
42
if (ex.GetErrorCode() == WSLC_E_NETWORK_NOT_FOUND)
43
{
44
- PrintMessage(Localization::MessageWslcNetworkNotFound(networkName.c_str()), stderr);
44
+ reporter.Error(L"{}\n", Localization::MessageWslcNetworkNotFound(networkName.c_str()));
45
return false;
46
}
47
@@ -49,7 +49,7 @@ static bool TryInspectNetwork(Session& session, const std::string& networkName,
49
}
50
}
51
52
-static bool TryDeleteNetwork(Session& session, const std::string& networkName, bool force)
52
+static bool TryDeleteNetwork(Reporter& reporter, Session& session, const std::string& networkName, bool force)
53
{
54
try
55
{
@@ -62,7 +62,7 @@ static bool TryDeleteNetwork(Session& session, const std::string& networkName, b
62
{
63
if (!force)
64
{
65
- PrintMessage(Localization::MessageWslcNetworkNotFound(networkName.c_str()), stderr);
65
+ reporter.Error(L"{}\n", Localization::MessageWslcNetworkNotFound(networkName.c_str()));
66
}
67
68
return false;
@@ -108,7 +108,7 @@ void CreateNetwork(CLIExecutionContext& context)
108
}
109
110
NetworkService::Create(context.Data.Get<Data::Session>(), options);
111
- PrintMessage(MultiByteToWide(options.Name));
111
+ context.Reporter.Output(L"{}\n", MultiByteToWide(options.Name));
112
}
113
114
void DeleteNetworks(CLIExecutionContext& context)
@@ -119,9 +119,9 @@ void DeleteNetworks(CLIExecutionContext& context)
119
const bool force = context.Args.Contains(ArgType::Force);
120
for (const auto& name : networkNames)
121
{
122
- if (TryDeleteNetwork(session, WideToMultiByte(name), force))
122
+ if (TryDeleteNetwork(context.Reporter, session, WideToMultiByte(name), force))
123
{
124
- PrintMessage(name);
124
+ context.Reporter.Output(L"{}\n", name);
125
}
126
else if (!force)
127
{
@@ -146,7 +146,7 @@ void InspectNetworks(CLIExecutionContext& context)
146
for (const auto& name : networkNames)
147
{
148
std::optional<wslc_schema::Network> inspectData;
149
- if (TryInspectNetwork(session, WideToMultiByte(name), inspectData))
149
+ if (TryInspectNetwork(context.Reporter, session, WideToMultiByte(name), inspectData))
150
{
151
result.push_back(*inspectData);
152
}
@@ -157,7 +157,7 @@ void InspectNetworks(CLIExecutionContext& context)
157
}
158
159
auto json = ToJson(result, c_jsonPrettyPrintIndent);
160
- PrintMessage(MultiByteToWide(json));
160
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
161
}
162
163
void ListNetworks(CLIExecutionContext& context)
@@ -169,7 +169,7 @@ void ListNetworks(CLIExecutionContext& context)
169
{
170
for (const auto& network : networks)
171
{
172
- PrintMessage(MultiByteToWide(network.Name));
172
+ context.Reporter.Output(L"{}\n", MultiByteToWide(network.Name));
173
}
174
175
return;
@@ -186,7 +186,7 @@ void ListNetworks(CLIExecutionContext& context)
186
case FormatType::Json:
187
{
188
auto json = ToJson(networks, c_jsonPrettyPrintIndent);
189
- PrintMessage(MultiByteToWide(json));
189
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
190
break;
191
}
192
case FormatType::Table:
@@ -224,7 +224,7 @@ void PruneNetworks(CLIExecutionContext& context)
224
225
for (const auto& networkName : result.PrunedNetworks)
226
{
227
- PrintMessage(Localization::WSLCCLI_NetworkPruneDeleted(MultiByteToWide(networkName)));
227
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_NetworkPruneDeleted(MultiByteToWide(networkName)));
228
}
229
}
230
src/windows/wslc/tasks/RegistryTasks.cpp
+2
-2
@@ -46,7 +46,7 @@ void Login(CLIExecutionContext& context)
46
auto [credUsername, credSecret] = RegistryService::Authenticate(session, serverAddress, username, password);
47
RegistryService::Store(serverAddress, credUsername, credSecret);
48
49
- PrintMessage(Localization::WSLCCLI_LoginSucceeded());
49
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_LoginSucceeded());
50
}
51
52
void Logout(CLIExecutionContext& context)
@@ -60,7 +60,7 @@ void Logout(CLIExecutionContext& context)
60
61
RegistryService::Erase(serverAddress);
62
63
- PrintMessage(Localization::WSLCCLI_LogoutSucceeded(MultiByteToWide(serverAddress)));
63
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_LogoutSucceeded(MultiByteToWide(serverAddress)));
64
}
65
66
} // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/SessionTasks.cpp
+5
-5
@@ -30,7 +30,7 @@ namespace wsl::windows::wslc::task {
30
void AttachToSession(CLIExecutionContext& context)
31
{
32
auto& session = context.Data.Get<Data::Session>();
33
- context.ExitCode = SessionService::Attach(session);
33
+ context.ExitCode = SessionService::Attach(context.Reporter, session);
34
}
35
36
void OpenSessionIfSpecified(CLIExecutionContext& context)
@@ -70,7 +70,7 @@ void ListSessions(CLIExecutionContext& context)
70
if (context.Args.Contains(ArgType::Verbose))
71
{
72
const wchar_t* plural = sessions.size() == 1 ? L"" : L"s";
73
- PrintMessage(std::format(L"[wslc] Found {} session{}", sessions.size(), plural), stdout);
73
+ context.Reporter.Output(L"[wslc] Found {} session{}\n", sessions.size(), plural);
74
}
75
76
TableOutput<3> table(
@@ -92,7 +92,7 @@ void ListSessions(CLIExecutionContext& context)
92
void TerminateSession(CLIExecutionContext& context)
93
{
94
auto& session = context.Data.Get<Data::Session>();
95
- context.ExitCode = SessionService::TerminateSession(session);
95
+ context.ExitCode = SessionService::TerminateSession(context.Reporter, session);
96
}
97
98
void RunInSession(CLIExecutionContext& context)
@@ -109,7 +109,7 @@ void RunInSession(CLIExecutionContext& context)
109
}
110
}
111
112
- context.ExitCode = SessionService::Run(session, arguments);
112
+ context.ExitCode = SessionService::Run(context.Reporter, session, arguments);
113
}
114
115
void EnterSession(CLIExecutionContext& context)
@@ -128,7 +128,7 @@ void EnterSession(CLIExecutionContext& context)
128
sessionName = wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
129
}
130
131
- context.ExitCode = SessionService::Enter(storagePath.wstring(), sessionName);
131
+ context.ExitCode = SessionService::Enter(context.Reporter, storagePath.wstring(), sessionName);
132
}
133
134
} // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/VolumeTasks.cpp
+14
-14
@@ -30,7 +30,7 @@ using namespace wsl::windows::wslc::services;
30
31
namespace wsl::windows::wslc::task {
32
33
-static bool TryInspectVolume(Session& session, const std::string& volumeName, std::optional<wslc_schema::InspectVolume>& inspectData)
33
+static bool TryInspectVolume(Reporter& reporter, Session& session, const std::string& volumeName, std::optional<wslc_schema::InspectVolume>& inspectData)
34
{
35
try
36
{
@@ -41,7 +41,7 @@ static bool TryInspectVolume(Session& session, const std::string& volumeName, st
41
{
42
if (ex.GetErrorCode() == WSLC_E_VOLUME_NOT_FOUND)
43
{
44
- PrintMessage(Localization::MessageWslcVolumeNotFound(volumeName.c_str()), stderr);
44
+ reporter.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str()));
45
return false;
46
}
47
@@ -49,7 +49,7 @@ static bool TryInspectVolume(Session& session, const std::string& volumeName, st
49
}
50
}
51
52
-static bool TryDeleteVolume(Session& session, const std::string& volumeName, bool force)
52
+static bool TryDeleteVolume(Reporter& reporter, Session& session, const std::string& volumeName, bool force)
53
{
54
try
55
{
@@ -62,7 +62,7 @@ static bool TryDeleteVolume(Session& session, const std::string& volumeName, boo
62
{
63
if (!force)
64
{
65
- PrintMessage(Localization::MessageWslcVolumeNotFound(volumeName.c_str()), stderr);
65
+ reporter.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str()));
66
}
67
68
return false;
@@ -100,7 +100,7 @@ void CreateVolume(CLIExecutionContext& context)
100
}
101
102
auto result = VolumeService::Create(context.Data.Get<Data::Session>(), options);
103
- PrintMessage(MultiByteToWide(result.Name));
103
+ context.Reporter.Output(L"{}\n", MultiByteToWide(result.Name));
104
}
105
106
void DeleteVolumes(CLIExecutionContext& context)
@@ -111,9 +111,9 @@ void DeleteVolumes(CLIExecutionContext& context)
111
const bool force = context.Args.Contains(ArgType::Force);
112
for (const auto& name : volumeNames)
113
{
114
- if (TryDeleteVolume(session, WideToMultiByte(name), force))
114
+ if (TryDeleteVolume(context.Reporter, session, WideToMultiByte(name), force))
115
{
116
- PrintMessage(name);
116
+ context.Reporter.Output(L"{}\n", name);
117
}
118
else if (!force)
119
{
@@ -138,7 +138,7 @@ void InspectVolumes(CLIExecutionContext& context)
138
for (const auto& name : volumeNames)
139
{
140
std::optional<wslc_schema::InspectVolume> inspectData;
141
- if (TryInspectVolume(session, WideToMultiByte(name), inspectData))
141
+ if (TryInspectVolume(context.Reporter, session, WideToMultiByte(name), inspectData))
142
{
143
result.push_back(*inspectData);
144
}
@@ -149,7 +149,7 @@ void InspectVolumes(CLIExecutionContext& context)
149
}
150
151
auto json = ToJson(result, c_jsonPrettyPrintIndent);
152
- PrintMessage(MultiByteToWide(json));
152
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
153
}
154
155
void ListVolumes(CLIExecutionContext& context)
@@ -161,7 +161,7 @@ void ListVolumes(CLIExecutionContext& context)
161
{
162
for (const auto& volume : volumes)
163
{
164
- PrintMessage(MultiByteToWide(volume.Name));
164
+ context.Reporter.Output(L"{}\n", MultiByteToWide(volume.Name));
165
}
166
167
return;
@@ -178,7 +178,7 @@ void ListVolumes(CLIExecutionContext& context)
178
case FormatType::Json:
179
{
180
auto json = ToJson(volumes, c_jsonPrettyPrintIndent);
181
- PrintMessage(MultiByteToWide(json));
181
+ context.Reporter.Output(L"{}\n", MultiByteToWide(json));
182
break;
183
}
184
case FormatType::Table:
@@ -217,10 +217,10 @@ void PruneVolumes(CLIExecutionContext& context)
217
218
for (const auto& volumeName : result.PrunedVolumes)
219
{
220
- PrintMessage(Localization::WSLCCLI_VolumePruneDeleted(MultiByteToWide(volumeName)));
220
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_VolumePruneDeleted(MultiByteToWide(volumeName)));
221
}
222
223
- PrintMessage(L"");
224
- PrintMessage(Localization::WSLCCLI_VolumePruneSpaceReclaimed(wsl::shared::string::FormatBytes(result.SpaceReclaimed)));
223
+ context.Reporter.Output(L"\n");
224
+ context.Reporter.Output(L"{}\n", Localization::WSLCCLI_VolumePruneSpaceReclaimed(wsl::shared::string::FormatBytes(result.SpaceReclaimed)));
225
}
226
} // namespace wsl::windows::wslc::task
test/windows/wslc/e2e/WSLCE2EImageBuildTests.cpp
+13
-13
@@ -71,7 +71,7 @@ class WSLCE2EImageBuildTests
71
72
auto buildResult = RunWslc(
73
std::format(L"build \"{}\" -f \"{}\" -t {}", contextDir.wstring(), dockerfilePath.wstring(), BuiltImage.NameAndTag()));
74
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
74
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
75
76
auto inspectData = InspectImage(BuiltImage.NameAndTag());
77
VERIFY_IS_TRUE(inspectData.RepoTags.has_value());
@@ -110,7 +110,7 @@ class WSLCE2EImageBuildTests
110
dockerfilePath.wstring(),
111
BuiltImageTag1.NameAndTag(),
112
BuiltImageTag2.NameAndTag()));
113
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
113
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
114
115
// Verify both tags are present by inspecting each one
116
auto inspectData1 = InspectImage(BuiltImageTag1.NameAndTag());
@@ -148,13 +148,13 @@ class WSLCE2EImageBuildTests
148
149
// Build with --pull --verbose. When --pull causes docker to resolve the base image
150
// from the registry, the FROM step includes a @sha256: digest (e.g.
151
- // "FROM docker.io/library/debian:latest@sha256:..."). Without --pull, no digest appears.
151
+ // "FROM docker.io/library/debian:latest@sha256:..."). Build progress goes to stderr.
152
auto buildResult = RunWslc(std::format(
153
L"build \"{}\" -f \"{}\" -t {} --pull --verbose", contextDir.wstring(), dockerfilePath.wstring(), BuiltImagePull.NameAndTag()));
154
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
154
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
155
156
- VERIFY_IS_TRUE(buildResult.Stdout.has_value());
157
- VERIFY_IS_TRUE(buildResult.Stdout->find(L"@sha256:") != std::wstring::npos);
156
+ VERIFY_IS_TRUE(buildResult.Stderr.has_value());
157
+ VERIFY_IS_TRUE(buildResult.Stderr->find(L"@sha256:") != std::wstring::npos);
158
}
159
160
WSLC_TEST_METHOD(WSLCE2E_Image_Build_Target_Success)
@@ -180,7 +180,7 @@ class WSLCE2EImageBuildTests
180
181
auto buildResult = RunWslc(std::format(
182
L"build \"{}\" -f \"{}\" -t {} --target build-stage", contextDir.wstring(), dockerfilePath.wstring(), BuiltImageTarget.NameAndTag()));
183
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
183
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
184
185
auto inspectData = InspectImage(BuiltImageTarget.NameAndTag());
186
VERIFY_IS_TRUE(inspectData.RepoTags.has_value());
@@ -214,7 +214,7 @@ class WSLCE2EImageBuildTests
214
contextDir.wstring(),
215
dockerfilePath.wstring(),
216
BuiltImageLabel.NameAndTag()));
217
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
217
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
218
219
auto inspectData = InspectImage(BuiltImageLabel.NameAndTag());
220
VERIFY_IS_TRUE(inspectData.Config.has_value());
@@ -250,7 +250,7 @@ class WSLCE2EImageBuildTests
250
contextDir.wstring(),
251
dockerfilePath.wstring(),
252
BuiltImageLabelOverride.NameAndTag()));
253
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
253
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
254
255
auto inspectData = InspectImage(BuiltImageLabelOverride.NameAndTag());
256
VERIFY_IS_TRUE(inspectData.Config.has_value());
@@ -345,19 +345,19 @@ class WSLCE2EImageBuildTests
345
346
// Seed the cache.
347
auto firstBuild = RunWslc(buildCmd);
348
- firstBuild.Verify({.Stderr = L"", .ExitCode = 0});
348
+ firstBuild.Verify({.Stdout = L"", .ExitCode = 0});
349
const auto firstId = InspectImage(BuiltImageNoCache.NameAndTag()).Id;
350
VERIFY_ARE_NOT_EQUAL(std::string{}, firstId);
351
352
// A repeated build without --no-cache should hit the cache and produce the same id.
353
auto cachedBuild = RunWslc(buildCmd);
354
- cachedBuild.Verify({.Stderr = L"", .ExitCode = 0});
354
+ cachedBuild.Verify({.Stdout = L"", .ExitCode = 0});
355
const auto cachedId = InspectImage(BuiltImageNoCache.NameAndTag()).Id;
356
VERIFY_ARE_EQUAL(firstId, cachedId, L"Repeated build without --no-cache should reuse the cached layer");
357
358
// --no-cache must re-run the non-deterministic step, producing a new id.
359
auto noCacheBuild = RunWslc(buildCmd + L" --no-cache");
360
- noCacheBuild.Verify({.Stderr = L"", .ExitCode = 0});
360
+ noCacheBuild.Verify({.Stdout = L"", .ExitCode = 0});
361
const auto noCacheId = InspectImage(BuiltImageNoCache.NameAndTag()).Id;
362
VERIFY_ARE_NOT_EQUAL(firstId, noCacheId, L"--no-cache must rebuild the non-deterministic RUN step");
363
}
@@ -382,7 +382,7 @@ private:
382
WriteTestFileContent(testRoot / fileName, "FROM debian:latest\nCMD [\"echo\", \"build-ok\"]\n");
383
384
auto buildResult = RunWslc(std::format(L"build \"{}\" -t {}", testRoot.wstring(), image.NameAndTag()));
385
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
385
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
386
387
auto inspectData = InspectImage(image.NameAndTag());
388
VERIFY_IS_TRUE(inspectData.RepoTags.has_value());
test/windows/wslc/e2e/WSLCE2EImageInspectTests.cpp
+1
-1
@@ -104,7 +104,7 @@ class WSLCE2EImageInspectTests
104
105
auto buildResult = RunWslc(std::format(
106
L"build \"{}\" -f \"{}\" -t {}", contextDir.wstring(), dockerfilePath.wstring(), BuiltExposeImage.NameAndTag()));
107
- buildResult.Verify({.Stderr = L"", .ExitCode = 0});
107
+ buildResult.Verify({.Stdout = L"", .ExitCode = 0});
108
109
auto inspectData = InspectImage(BuiltExposeImage.NameAndTag());
110
VERIFY_IS_TRUE(inspectData.Config.has_value());
test/windows/wslc/e2e/WSLCE2EPushPullTests.cpp
+7
-3
@@ -70,15 +70,19 @@ class WSLCE2EPushPullTests
70
71
auto tagCleanup = wil::scope_exit([&]() { RunWslc(std::format(L"image delete --force {}", registryImage)); });
72
73
- // Push should succeed.
73
+ // Standalone push/pull send progress to stdout (Docker parity), leaving stderr empty.
74
auto result = RunWslc(std::format(L"push {}", registryImage));
75
- result.Verify({.ExitCode = 0});
75
+ result.Verify({.Stderr = L"", .ExitCode = 0});
76
+ VERIFY_IS_TRUE(result.Stdout.has_value());
77
+ VERIFY_IS_FALSE(result.Stdout->empty());
78
79
// Delete the local copy and pull it back.
80
RunWslcAndVerify(std::format(L"image delete --force {}", registryImage), {.ExitCode = 0});
81
82
result = RunWslc(std::format(L"pull {}", registryImage));
83
result.Verify({.Stderr = L"", .ExitCode = 0});
84
+ VERIFY_IS_TRUE(result.Stdout.has_value());
85
+ VERIFY_IS_FALSE(result.Stdout->empty());
86
87
// Verify the image is now present.
88
auto registryRepo = registryImage.substr(0, registryImage.rfind(L':'));
@@ -93,7 +97,7 @@ class WSLCE2EPushPullTests
97
{
98
auto result = RunWslc(L"push does-not-exist:latest");
99
auto errorMessage = L"An image does not exist locally with the tag: does-not-exist\r\nError code: E_FAIL\r\n";
96
- result.Verify({.Stdout = L"", .Stderr = errorMessage, .ExitCode = 1});
100
+ result.Verify({.Stderr = errorMessage, .ExitCode = 1});
101
}
102
103
WSLC_TEST_METHOD(WSLCE2E_Image_Pull_NonExistentImage)