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
8 changes: 7 additions & 1 deletion src/shared/inc/stringshared.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,15 @@ inline std::optional<uint64_t> ParseMemorySize(const T* String)
std::make_pair(Gigabytes, 1ULL << 30),
std::make_pair(Terabytes, 1ULL << 40)};

// Case-insensitive suffix matching to align with Docker CLI behavior (accepts both 512m and 512M).
for (const auto& [Suffix, Factor] : Units)
{
if ((Remainder == Suffix.substr(0, 1)) || (Remainder == Suffix))
if (Remainder.size() == 1 && IsEqual(Remainder, Suffix.substr(0, 1), true))
{
return Value * Factor;
}

if (IsEqual(Remainder, Suffix, true))
{
return Value * Factor;
}
Expand Down
3 changes: 3 additions & 0 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,9 @@ typedef struct _WSLCBuildImageOptions
WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker.
LPCSTR Target; // Target build stage name passed as --target to docker.
WSLCBuildImageFlags Flags; // WSLCBuildImageFlags
LONGLONG ShmSize; // Shared memory size in bytes passed as --shm-size to docker. 0 = default.
[unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits;
ULONG UlimitsCount;
Comment on lines +481 to +483
} WSLCBuildImageOptions;

typedef struct _WSLCTagImageOptions
Expand Down
2 changes: 2 additions & 0 deletions src/windows/wslc/commands/ImageBuildCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ std::vector<Argument> ImageBuildCommand::GetArguments() const
Argument::Create(ArgType::BuildTarget),
Argument::Create(ArgType::File),
Argument::Create(ArgType::NoCache),
Argument::Create(ArgType::ShmSize),
Argument::Create(ArgType::Tag, false, NO_LIMIT),
Argument::Create(ArgType::Ulimit, false, NO_LIMIT),
Argument::Create(ArgType::Verbose),
};
}
Expand Down
13 changes: 13 additions & 0 deletions src/windows/wslc/services/ImageService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ void ImageService::Build(
const std::wstring& target,
WSLCBuildImageFlags flags,
IProgressCallback* callback,
int64_t shmSize,
const std::vector<std::tuple<std::string, int64_t, int64_t>>& ulimits,
HANDLE cancelEvent)
{
auto absolutePath = std::filesystem::absolute(contextPath);
Expand Down Expand Up @@ -170,13 +172,24 @@ void ImageService::Build(
auto targetStr = wsl::windows::common::string::WideToMultiByte(target);

auto contextPathStr = absolutePath.wstring();

std::vector<WSLCUlimit> ulimitEntries;
ulimitEntries.reserve(ulimits.size());
for (const auto& [name, soft, hard] : ulimits)
{
ulimitEntries.push_back({name.c_str(), soft, hard});
}

WSLCBuildImageOptions options{
.ContextPath = contextPathStr.c_str(),
.DockerfileHandle = ToCOMInputHandle(dockerfileHandle),
.Tags = {tagPointers.data(), static_cast<ULONG>(tagPointers.size())},
.BuildArgs = {buildArgPointers.data(), static_cast<ULONG>(buildArgPointers.size())},
.Target = targetStr.empty() ? nullptr : targetStr.c_str(),
.Flags = flags,
.ShmSize = shmSize,
.Ulimits = ulimitEntries.empty() ? nullptr : ulimitEntries.data(),
.UlimitsCount = static_cast<ULONG>(ulimitEntries.size()),
};

THROW_IF_FAILED(session.Get()->BuildImage(&options, callback, cancelEvent));
Expand Down
2 changes: 2 additions & 0 deletions src/windows/wslc/services/ImageService.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ImageService
const std::wstring& target,
WSLCBuildImageFlags flags,
IProgressCallback* callback,
int64_t shmSize = 0,
const std::vector<std::tuple<std::string, int64_t, int64_t>>& ulimits = {},
HANDLE cancelEvent = nullptr);

static std::vector<wsl::windows::wslc::models::ImageInformation> List(
Expand Down
18 changes: 17 additions & 1 deletion src/windows/wslc/tasks/ImageTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,23 @@ void BuildImage(CLIExecutionContext& context)

auto cancelEvent = context.CreateCancelEvent();
BuildImageCallback callback(cancelEvent, context.Args.Contains(ArgType::Verbose));
services::ImageService::Build(session, contextPath, tags, buildArgs, dockerfilePath, target, flags, &callback, cancelEvent);

int64_t shmSize = 0;
if (context.Args.Contains(ArgType::ShmSize))
{
shmSize = validation::GetMemorySizeFromString(context.Args.Get<ArgType::ShmSize>());
}

std::vector<std::tuple<std::string, int64_t, int64_t>> ulimits;
if (context.Args.Contains(ArgType::Ulimit))
{
for (const auto& value : context.Args.GetAll<ArgType::Ulimit>())
{
ulimits.emplace_back(validation::ParseUlimit(value));
}
}

services::ImageService::Build(session, contextPath, tags, buildArgs, dockerfilePath, target, flags, &callback, shmSize, ulimits, cancelEvent);
}

void GetImages(CLIExecutionContext& context)
Expand Down
21 changes: 21 additions & 0 deletions src/windows/wslcsession/WSLCSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,27 @@ try
buildArgs.push_back(Options->BuildArgs.Values[i]);
}

if (Options->ShmSize > 0)
{
buildArgs.push_back("--shm-size");
buildArgs.push_back(std::to_string(Options->ShmSize));
}
Comment on lines +908 to +912

for (ULONG i = 0; i < Options->UlimitsCount; i++)
{
RETURN_HR_IF_NULL(E_INVALIDARG, Options->Ulimits);
RETURN_HR_IF_NULL(E_INVALIDARG, Options->Ulimits[i].Name);
buildArgs.push_back("--ulimit");
Comment on lines +916 to +918
if (Options->Ulimits[i].Soft == Options->Ulimits[i].Hard)
{
buildArgs.push_back(std::format("{}={}", Options->Ulimits[i].Name, Options->Ulimits[i].Soft));
}
else
{
buildArgs.push_back(std::format("{}={}:{}", Options->Ulimits[i].Name, Options->Ulimits[i].Soft, Options->Ulimits[i].Hard));
}
}

buildArgs.push_back("-f");
buildArgs.push_back("-");
buildArgs.push_back(mountPath);
Expand Down
4 changes: 4 additions & 0 deletions test/windows/wslc/CommandLineTestCases.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ COMMAND_LINE_TEST_CASE(L"image build C:\\context -t test --build-arg KEY=VALUE -
COMMAND_LINE_TEST_CASE(L"image build C:\\context --no-cache", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build C:\\context --no-cache --verbose", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build C:\\context -t test --no-cache", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build C:\\context --shm-size 256m", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build C:\\context --ulimit nofile=1024:2048", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build C:\\context --ulimit nofile=1024:2048 --ulimit nproc=128", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build C:\\context --shm-size 512m --ulimit memlock=67108864", L"build", true)
COMMAND_LINE_TEST_CASE(L"image build", L"build", false)
COMMAND_LINE_TEST_CASE(L"build C:\\context", L"build", true)
COMMAND_LINE_TEST_CASE(L"build C:\\context -t test", L"build", true)
Expand Down
40 changes: 40 additions & 0 deletions test/windows/wslc/WSLCCLIArgumentUnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,46 @@ class WSLCCLIArgumentUnitTests
VERIFY_THROWS(validation::ValidateGpus({L"0"}, L"gpusArg"), ArgumentException);
VERIFY_THROWS(validation::ValidateGpus({L"gpu0"}, L"gpusArg"), ArgumentException);
VERIFY_THROWS(validation::ValidateGpus({L""}, L"gpusArg"), ArgumentException);

// Verify memory size parsing (case-insensitive, matching Docker CLI behavior)
auto memoryBytes = validation::GetMemorySizeFromString(L"512m");
VERIFY_ARE_EQUAL(memoryBytes, 512LL * 1024 * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"512M");
VERIFY_ARE_EQUAL(memoryBytes, 512LL * 1024 * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"1g");
VERIFY_ARE_EQUAL(memoryBytes, 1LL * 1024 * 1024 * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"1G");
VERIFY_ARE_EQUAL(memoryBytes, 1LL * 1024 * 1024 * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"256k");
VERIFY_ARE_EQUAL(memoryBytes, 256LL * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"256K");
VERIFY_ARE_EQUAL(memoryBytes, 256LL * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"1024");
VERIFY_ARE_EQUAL(memoryBytes, 1024LL);
memoryBytes = validation::GetMemorySizeFromString(L"512mb");
VERIFY_ARE_EQUAL(memoryBytes, 512LL * 1024 * 1024);
memoryBytes = validation::GetMemorySizeFromString(L"512MB");
VERIFY_ARE_EQUAL(memoryBytes, 512LL * 1024 * 1024);
VERIFY_THROWS(validation::GetMemorySizeFromString(L"invalidsize"), ArgumentException);
VERIFY_THROWS(validation::GetMemorySizeFromString(L""), ArgumentException);
VERIFY_THROWS(validation::GetMemorySizeFromString(L"abc123"), ArgumentException);
VERIFY_NO_THROW(validation::ValidateMemorySize({L"512m", L"1G", L"256K"}, L"memoryArg"));
VERIFY_THROWS(validation::ValidateMemorySize({L"512m", L"notvalid"}, L"memoryArg"), ArgumentException);

// Verify CPU (nano-cpus) parsing
auto nanoCpus = validation::GetNanoCpusFromString(L"2.0");
VERIFY_ARE_EQUAL(nanoCpus, 2'000'000'000LL);
nanoCpus = validation::GetNanoCpusFromString(L"0.5");
VERIFY_ARE_EQUAL(nanoCpus, 500'000'000LL);
nanoCpus = validation::GetNanoCpusFromString(L"1");
VERIFY_ARE_EQUAL(nanoCpus, 1'000'000'000LL);
nanoCpus = validation::GetNanoCpusFromString(L"1.5");
VERIFY_ARE_EQUAL(nanoCpus, 1'500'000'000LL);
VERIFY_THROWS(validation::GetNanoCpusFromString(L"notanumber"), ArgumentException);
VERIFY_THROWS(validation::GetNanoCpusFromString(L""), ArgumentException);
VERIFY_THROWS(validation::GetNanoCpusFromString(L"-1.0"), ArgumentException);
VERIFY_NO_THROW(validation::ValidateNanoCpus({L"1.0", L"2.5", L"0.25"}, L"cpusArg"));
VERIFY_THROWS(validation::ValidateNanoCpus({L"1.0", L"abc"}, L"cpusArg"), ArgumentException);
}

// Test: Verify EnumVariantMap behavior with ArgTypes.
Expand Down
60 changes: 60 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EImageBuildTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,37 @@ class WSLCE2EImageBuildTests
VERIFY_ARE_NOT_EQUAL(firstId, noCacheId, L"--no-cache must rebuild the non-deterministic RUN step");
}

WSLC_TEST_METHOD(WSLCE2E_Image_Build_ShmSizeAndUlimit_Success)
{
auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-resource-limits";
auto cleanup = SetupTestDirectory(testRoot);

auto contextDir = testRoot / L"context";
std::error_code ec;
std::filesystem::create_directories(contextDir, ec);
THROW_HR_IF(E_FAIL, ec.value() != 0 || !std::filesystem::exists(contextDir));

auto dockerfilePath = testRoot / L"Dockerfile";
<<<<<<< HEAD
WriteTestFileContent(dockerfilePath, "FROM debian:latest\nCMD [\"echo\", \"resource-limit-ok\"]\n");
=======
WriteTestFile(dockerfilePath, "FROM debian:latest\nCMD [\"echo\", \"resource-limit-ok\"]\n");
>>>>>>> 498a328b28c528527d20fddbceabf5b63805c034

// Build with --shm-size and --ulimit to verify they are accepted and piped through.
auto buildResult = RunWslc(std::format(
L"build \"{}\" -f \"{}\" -t {} --shm-size 256m --ulimit nofile=1024:2048",
contextDir.wstring(),
dockerfilePath.wstring(),
BuiltImageResourceLimits.NameAndTag()));
buildResult.Verify({.Stderr = L"", .ExitCode = 0});

auto inspectData = InspectImage(BuiltImageResourceLimits.NameAndTag());
VERIFY_IS_TRUE(inspectData.RepoTags.has_value());
VERIFY_ARE_EQUAL(1u, inspectData.RepoTags.value().size());
VERIFY_ARE_EQUAL(BuiltImageResourceLimits.NameAndTag(), wsl::shared::string::MultiByteToWide(inspectData.RepoTags.value()[0]));
}

private:
const TestImage BuiltImage{L"wslc-e2e-build-empty-context", L"latest", L""};
const TestImage BuiltImageTag1{L"wslc-e2e-build-args-tags", L"v1", L""};
Expand All @@ -289,6 +320,7 @@ class WSLCE2EImageBuildTests
const TestImage BuiltImageDockerfile{L"wslc-e2e-build-dockerfile-ctx", L"latest", L""};
const TestImage BuiltImageContainerfile{L"wslc-e2e-build-containerfile-ctx", L"latest", L""};
const TestImage BuiltImageNoCache{L"wslc-e2e-build-no-cache", L"latest", L""};
const TestImage BuiltImageResourceLimits{L"wslc-e2e-build-resource-limits", L"latest", L""};

void BuildFromContextFile(const std::wstring& fileName, const TestImage& image)
{
Expand Down Expand Up @@ -316,6 +348,34 @@ class WSLCE2EImageBuildTests
EnsureImageIsDeleted(BuiltImageDockerfile);
EnsureImageIsDeleted(BuiltImageContainerfile);
EnsureImageIsDeleted(BuiltImageNoCache);
EnsureImageIsDeleted(BuiltImageResourceLimits);
<<<<<<< HEAD
=======
}

static auto SetupTestDirectory(const std::filesystem::path& testRoot)
{
std::error_code ec;
std::filesystem::remove_all(testRoot, ec);
THROW_HR_IF_MSG(E_FAIL, ec.value() != 0 && std::filesystem::exists(testRoot), "%hs", ec.message().c_str());

std::filesystem::create_directories(testRoot, ec);
THROW_HR_IF_MSG(E_FAIL, ec.value() != 0 || !std::filesystem::exists(testRoot), "%hs", ec.message().c_str());

return wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [testRoot]() {
std::error_code removeError;
std::filesystem::remove_all(testRoot, removeError);
});
}

static void WriteTestFile(const std::filesystem::path& path, const std::string& content)
{
std::ofstream file(path);
THROW_HR_IF(E_FAIL, !file.is_open());
file << content;
THROW_HR_IF(E_FAIL, !file.good());
file.close();
>>>>>>> 498a328b28c528527d20fddbceabf5b63805c034
}
};
} // namespace WSLCE2ETests
Loading