Add --label support to wslc image build (#40837)
John Stephens committed
Jun 23, 2026 at 17:40 UTC
2e5a2ae3c6b876d10e885972b02c65e36ccafc31
7 files changed
+92
-1
src/windows/service/inc/wslc.idl
+1
@@ -493,6 +493,7 @@ typedef struct _WSLCBuildImageOptions
493
WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker.
494
LPCSTR Target; // Target build stage name passed as --target to docker.
495
WSLCBuildImageFlags Flags; // WSLCBuildImageFlags
496
+ WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker.
497
} WSLCBuildImageOptions;
498
499
typedef struct _WSLCTagImageOptions
src/windows/wslc/commands/ImageBuildCommand.cpp
+1
@@ -32,6 +32,7 @@ std::vector<Argument> ImageBuildCommand::GetArguments() const
32
Argument::Create(ArgType::BuildPull),
33
Argument::Create(ArgType::BuildTarget),
34
Argument::Create(ArgType::File),
35
+ Argument::Create(ArgType::Label, false, NO_LIMIT),
36
Argument::Create(ArgType::NoCache),
37
Argument::Create(ArgType::Tag, false, NO_LIMIT),
38
Argument::Create(ArgType::Verbose),
src/windows/wslc/services/ImageService.cpp
+6
@@ -119,6 +119,7 @@ void ImageService::Build(
119
const std::wstring& contextPath,
120
const std::vector<std::wstring>& tags,
121
const std::vector<std::wstring>& buildArgs,
122
+ const std::vector<std::wstring>& labels,
123
const std::wstring& dockerfilePath,
124
const std::wstring& target,
125
WSLCBuildImageFlags flags,
@@ -167,6 +168,10 @@ void ImageService::Build(
168
std::vector<LPCSTR> buildArgPointers;
169
toMultiByte(buildArgs, buildArgStrings, buildArgPointers);
170
171
+ std::vector<std::string> labelStrings;
172
+ std::vector<LPCSTR> labelPointers;
173
+ toMultiByte(labels, labelStrings, labelPointers);
174
+
175
auto targetStr = wsl::windows::common::string::WideToMultiByte(target);
176
177
auto contextPathStr = absolutePath.wstring();
@@ -177,6 +182,7 @@ void ImageService::Build(
182
.BuildArgs = {buildArgPointers.data(), static_cast<ULONG>(buildArgPointers.size())},
183
.Target = targetStr.empty() ? nullptr : targetStr.c_str(),
184
.Flags = flags,
185
+ .Labels = {labelPointers.data(), static_cast<ULONG>(labelPointers.size())},
186
};
187
188
THROW_IF_FAILED(session.Get()->BuildImage(&options, callback, cancelEvent));
src/windows/wslc/services/ImageService.h
+1
@@ -26,6 +26,7 @@ public:
26
const std::wstring& contextPath,
27
const std::vector<std::wstring>& tags,
28
const std::vector<std::wstring>& buildArgs,
29
+ const std::vector<std::wstring>& labels,
30
const std::wstring& dockerfilePath,
31
const std::wstring& target,
32
WSLCBuildImageFlags flags,
src/windows/wslc/tasks/ImageTasks.cpp
+6
-1
@@ -63,6 +63,11 @@ void BuildImage(CLIExecutionContext& context)
63
64
auto tags = context.Args.GetAll<ArgType::Tag>();
65
auto buildArgs = context.Args.GetAll<ArgType::BuildArg>();
66
+ auto labels = context.Args.GetAll<ArgType::Label>();
67
+ for (const auto& label : labels)
68
+ {
69
+ validation::ParseLabel(label);
70
+ }
71
72
std::wstring dockerfilePath;
73
if (context.Args.Contains(ArgType::File))
@@ -85,7 +90,7 @@ void BuildImage(CLIExecutionContext& context)
90
91
auto cancelEvent = context.CreateCancelEvent();
92
BuildImageCallback callback(cancelEvent, context.Args.Contains(ArgType::Verbose));
88
- services::ImageService::Build(session, contextPath, tags, buildArgs, dockerfilePath, target, flags, &callback, cancelEvent);
93
+ services::ImageService::Build(session, contextPath, tags, buildArgs, labels, dockerfilePath, target, flags, &callback, cancelEvent);
94
}
95
96
void GetImages(CLIExecutionContext& context)
src/windows/wslcsession/WSLCSession.cpp
+8
@@ -873,6 +873,7 @@ try
873
RETURN_HR_IF(E_INVALIDARG, *Options->ContextPath == L'\0');
874
RETURN_HR_IF(E_INVALIDARG, Options->Tags.Count > 0 && Options->Tags.Values == nullptr);
875
RETURN_HR_IF(E_INVALIDARG, Options->BuildArgs.Count > 0 && Options->BuildArgs.Values == nullptr);
876
+ RETURN_HR_IF(E_INVALIDARG, Options->Labels.Count > 0 && Options->Labels.Values == nullptr);
877
THROW_HR_IF_MSG(
878
E_INVALIDARG,
879
WI_IsAnyFlagSet(static_cast<WSLCBuildImageFlags>(Options->Flags), ~WSLCBuildImageFlagsValid),
@@ -935,6 +936,13 @@ try
936
buildArgs.push_back("--build-arg");
937
buildArgs.push_back(Options->BuildArgs.Values[i]);
938
}
939
+ for (ULONG i = 0; i < Options->Labels.Count; i++)
940
+ {
941
+ RETURN_HR_IF_NULL(E_INVALIDARG, Options->Labels.Values[i]);
942
+ RETURN_HR_IF(E_INVALIDARG, Options->Labels.Values[i][0] == '-');
943
+ buildArgs.push_back("--label");
944
+ buildArgs.push_back(Options->Labels.Values[i]);
945
+ }
946
947
buildArgs.push_back("-f");
948
buildArgs.push_back("-");
test/windows/wslc/e2e/WSLCE2EImageBuildTests.cpp
+69
@@ -182,6 +182,71 @@ class WSLCE2EImageBuildTests
182
VERIFY_IS_TRUE(!inspectData.Config.value().Cmd.has_value() || inspectData.Config.value().Cmd.value() != finalStageCmd);
183
}
184
185
+ WSLC_TEST_METHOD(WSLCE2E_Image_Build_Label_Success)
186
+ {
187
+ auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-label";
188
+ auto cleanup = SetupTestDirectory(testRoot);
189
+
190
+ auto contextDir = testRoot / L"context";
191
+ std::error_code ec;
192
+ std::filesystem::create_directories(contextDir, ec);
193
+ THROW_HR_IF(E_FAIL, ec.value() != 0 || !std::filesystem::exists(contextDir));
194
+
195
+ auto dockerfilePath = testRoot / L"Dockerfile";
196
+ WriteTestFileContent(dockerfilePath, "FROM debian:latest\nCMD [\"echo\", \"label-ok\"]\n");
197
+
198
+ // Use both the short alias (-l) and long form (--label) to confirm both parse paths.
199
+ auto buildResult = RunWslc(std::format(
200
+ L"build \"{}\" -f \"{}\" -t {} -l first=one --label second=two",
201
+ contextDir.wstring(),
202
+ dockerfilePath.wstring(),
203
+ BuiltImageLabel.NameAndTag()));
204
+ buildResult.Verify({.Stderr = L"", .ExitCode = 0});
205
+
206
+ auto inspectData = InspectImage(BuiltImageLabel.NameAndTag());
207
+ VERIFY_IS_TRUE(inspectData.Config.has_value());
208
+ VERIFY_IS_TRUE(inspectData.Config.value().Labels.has_value());
209
+ const auto& labels = inspectData.Config.value().Labels.value();
210
+
211
+ auto firstIt = labels.find("first");
212
+ VERIFY_IS_TRUE(firstIt != labels.end());
213
+ VERIFY_ARE_EQUAL(std::string("one"), firstIt->second);
214
+
215
+ auto secondIt = labels.find("second");
216
+ VERIFY_IS_TRUE(secondIt != labels.end());
217
+ VERIFY_ARE_EQUAL(std::string("two"), secondIt->second);
218
+ }
219
+
220
+ WSLC_TEST_METHOD(WSLCE2E_Image_Build_LabelOverridesDockerfile_Success)
221
+ {
222
+ auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-label-override";
223
+ auto cleanup = SetupTestDirectory(testRoot);
224
+
225
+ auto contextDir = testRoot / L"context";
226
+ std::error_code ec;
227
+ std::filesystem::create_directories(contextDir, ec);
228
+ THROW_HR_IF(E_FAIL, ec.value() != 0 || !std::filesystem::exists(contextDir));
229
+
230
+ auto dockerfilePath = testRoot / L"Dockerfile";
231
+ WriteTestFileContent(
232
+ dockerfilePath, "FROM debian:latest\nLABEL conflict=from-dockerfile\nCMD [\"echo\", \"label-override-ok\"]\n");
233
+
234
+ auto buildResult = RunWslc(std::format(
235
+ L"build \"{}\" -f \"{}\" -t {} --label conflict=from-cli",
236
+ contextDir.wstring(),
237
+ dockerfilePath.wstring(),
238
+ BuiltImageLabelOverride.NameAndTag()));
239
+ buildResult.Verify({.Stderr = L"", .ExitCode = 0});
240
+
241
+ auto inspectData = InspectImage(BuiltImageLabelOverride.NameAndTag());
242
+ VERIFY_IS_TRUE(inspectData.Config.has_value());
243
+ VERIFY_IS_TRUE(inspectData.Config.value().Labels.has_value());
244
+ const auto& labels = inspectData.Config.value().Labels.value();
245
+ auto it = labels.find("conflict");
246
+ VERIFY_IS_TRUE(it != labels.end());
247
+ VERIFY_ARE_EQUAL(std::string("from-cli"), it->second);
248
+ }
249
+
250
WSLC_TEST_METHOD(WSLCE2E_Image_Build_DockerfileInContextDir_Success)
251
{
252
BuildFromContextFile(L"Dockerfile", BuiltImageDockerfile);
@@ -289,6 +354,8 @@ private:
354
const TestImage BuiltImageDockerfile{L"wslc-e2e-build-dockerfile-ctx", L"latest", L""};
355
const TestImage BuiltImageContainerfile{L"wslc-e2e-build-containerfile-ctx", L"latest", L""};
356
const TestImage BuiltImageNoCache{L"wslc-e2e-build-no-cache", L"latest", L""};
357
+ const TestImage BuiltImageLabel{L"wslc-e2e-build-label", L"latest", L""};
358
+ const TestImage BuiltImageLabelOverride{L"wslc-e2e-build-label-override", L"latest", L""};
359
360
void BuildFromContextFile(const std::wstring& fileName, const TestImage& image)
361
{
@@ -316,6 +383,8 @@ private:
383
EnsureImageIsDeleted(BuiltImageDockerfile);
384
EnsureImageIsDeleted(BuiltImageContainerfile);
385
EnsureImageIsDeleted(BuiltImageNoCache);
386
+ EnsureImageIsDeleted(BuiltImageLabel);
387
+ EnsureImageIsDeleted(BuiltImageLabelOverride);
388
}
389
};
390
} // namespace WSLCE2ETests