Skip to content

Conversation

@ParkSeongGeun
Copy link
Contributor

@ParkSeongGeun ParkSeongGeun commented Dec 23, 2025

Type of Change

  • Bug fix (Coordinated with engine-side fix)
  • New feature
  • Breaking change
  • Documentation update

Motivation and Context

Initially, this PR aimed to resolve relative paths within the CLI parser. However, following maintainer feedback (@jglogan), we've adopted a "Cleaner Approach" by moving the path resolution logic directly into the executor (vmexec) within the apple/containerization repository.

By handling the resolution at the engine level, we ensure that the OCI bundle configuration remains clean and consistent with industry standards (Docker/Podman), where the executor handles binary lookups relative to the WORKDIR.

Changes in this PR:

  • Reverted CLI-side resolution: Keeps the Parser.swift logic simple, passing the raw entrypoint/cmd strings to the engine.
  • Added Passthrough Test: Introduced testProcessEntrypointRelativePathPassthrough in ParserTest.swift to ensure that relative paths (e.g., ./uname) are handed off to the executor without any unintended modifications by the CLI.

Testing

  • Unit Tests: Ran swift test --filter Passthrough to confirm the CLI correctly passes relative paths untouched.
  • Integration Tests: Verified on macOS 26.0 using a local build of containerization (via swift package edit).
    • Scenario: container run --rm --workdir /bin --entrypoint ./uname alpine
    • Result: Successfully executed and returned Linux. This confirms the end-to-end flow where the CLI passes ./uname and the engine resolves it to /bin/uname.

Coordination

The actual root-cause fix is submitted here: apple/containerization#473

Copy link
Contributor

@jglogan jglogan left a comment

Choose a reason for hiding this comment

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

@ParkSeongGeun Thanks! It's a good start but a few things:

  • Please rebase against main to resolve conflicts from the ContainerClient refactor.
  • Extract your resolution code into a private function.
  • Add handling for just command with no slashes at all (see comment with example that fails in this branch with container but works with the Docker CLI in Colima).

if !arguments.isEmpty {
result.append(contentsOf: arguments)
} else {
if let cmd = config?.cmd, !hasEntrypointOverride, !cmd.isEmpty {
result.append(contentsOf: cmd)
if let first = cmd.first, !first.hasPrefix("/") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we're repeating the same code in three places - extract method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jglogan This is my mistake. I've refactored the path resolution logic into a private helper function called resolveExecutablePath

.standardized
.path
result = [resolved]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't cover all cases. Please add handling for bare commands:

% container run --rm -w / --entrypoint ls alpine
Error: internalError: "failed to find target executable /ls"

% docker run --rm -w / --entrypoint ls alpine   
bin
dev
etc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jglogan Thank you for catching this case.

I've updated the logic to ensure that bare commands (those without any slashes) are not resolved against the working directory.

@ParkSeongGeun ParkSeongGeun force-pushed the fix/relative-path-entrypoint branch 2 times, most recently from 2f49058 to 9601f9d Compare January 8, 2026 11:10
@ParkSeongGeun
Copy link
Contributor Author

@jglogan Thanks for the thorough review

I've rebased the branch on the latest main and addressed all the feedback.

  1. Refactored the path resolution logic into a helper function.
  2. Added handling for bare commands to be resolved via PATH (executables without slashes are now ignored by the resolver).
  3. Updated unit tests to cover these scenarios.

@ParkSeongGeun ParkSeongGeun requested a review from jglogan January 8, 2026 11:32
@jglogan
Copy link
Contributor

jglogan commented Jan 8, 2026

@ParkSeongGeun I think this works, but I'm curious as to the root cause for this error in your original failure case:

% container run -it --rm -w /bin --entrypoint ./uname alpine:latest
Error: internalError: "failed to find target executable ./uname"

We might be able to address this down in containerization instead, such that we don't need to modify the path in the Bundle that gets generated. I feel that that is the cleaner approach.

Let me know if you're interested in looking at that. The bit of vmexec where the failure occurs is https://github.com/apple/containerization/blob/main/vminitd/Sources/vmexec/vmexec.swift#L74.

It might be as simple as pointing an AI assistant at a context of container, containerization, a failing command example, and this line of code and letting it crank out an answer for you. Or, troubleshoot it the old school way!

In either case see https://github.com/apple/container/blob/main/BUILDING.md for how to build container using a local copy of the containerization repo. I also noticed that the instructions for getting container to use your locally built init filesystem isn't there (you need this to see any changes that you make to vminitd or vmexec), so I filed #1030 to describe that procedure while I fix the docs.

@ParkSeongGeun
Copy link
Contributor Author

@jglogan I agree that fixing this problem in containerization (vmexec) is more cleaner approach.

I'll set up the local environment settings and wait for your guidance in #1030 on how to use a custom init filesystem.

It's interesting to dive deeper into the root cause.

Thanks for your detailed explanation!

@jglogan
Copy link
Contributor

jglogan commented Jan 9, 2026

@ParkSeongGeun The missing instructions were in the PR. I just merged them into BUILDING.md.

TL;DR:

  1. Check out a copy of containerization.
  2. From your container project: `swift package edit --path /path/to/your/containerization containerization
  3. container system property set image.init vminit:latest
  4. In your container project: make all install
  5. container system stop ; container system start

When you build container in step 4, the build runs scripts/install-init.sh that will do make init in your containerization project and install the resulting init image into your container content store.

The system property change tells container to use that local init filesystem image instead of the release image hosted on GHCR.

Don't forget to unedit the package and clear the system property to get back to using the normal containerization dependency and init fs.

@ParkSeongGeun
Copy link
Contributor Author

@jglogan

Apologies for the delay in getting back to you. I spent the last few days setting up a local development environment for both repositories and working through the guest VM build process to ensure the root cause fix was thoroughly verified.

As you suggested, I moved the path resolution logic directly into the executor (vmexec) in the apple/containerization repository. This ensures that the OCI bundle remains clean and the engine handles binary lookups relative to the working directory.

In this PR, I have

  • Reverted the CLI-side normalization in Parser.swift to keep the logic focused on being a clean passthrough.
  • Added a new unit test, testProcessEntrypointRelativePathPassthrough, in ParserTest.swift to ensure the CLI correctly hands off raw relative paths to the engine.

Verified the End-to-End flow on macOS 26.0 with the local engine build.

  • Running container run --workdir /bin --entrypoint ./uname alpine now successfully returns Linux.

I've submitted the engine-side fix here: apple/containerization#473

@jglogan
Copy link
Contributor

jglogan commented Jan 12, 2026

@ParkSeongGeun Your test case should fail with the current 0.20.0, so add a change to 0.20.1 in Package.swift and then we should have a clean build.

Signed-off-by: ParkSeongGeun <phd0801@naver.com>
@ParkSeongGeun ParkSeongGeun force-pushed the fix/relative-path-entrypoint branch from ff7d1a5 to 82f0182 Compare January 12, 2026 03:19
@ParkSeongGeun
Copy link
Contributor Author

@jglogan Now I've updated Package.swift to version 0.20.1 and performed a targeted update of the Package.resolved file!

Copy link
Contributor

@jglogan jglogan left a comment

Choose a reason for hiding this comment

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

@ParkSeongGeun thanks for taking the time to learn a bit about the containerization side of things! Don't forget to revert your swift package edit and clear the init image property value when you want to go back to using the normal package dependency.

@jglogan jglogan merged commit e465b10 into apple:main Jan 12, 2026
2 checks passed
@ParkSeongGeun
Copy link
Contributor Author

@jglogan Thank you so much for your help!

I was interested in implementing virtualization with Swift, and I had a great experience with this contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: relative path in 'entrypoint' is not accepted

2 participants