Skip to content
Merged
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ swift build -c release # binary: .build/release/adevcontainer
```

## Tests
Plain `swift test` may report “no tests found” on Command Line Tools hosts, which lack `XCTest.framework`; the suite of record is `swift run adevcontainerTests`.
Plain `swift test` may report “no tests found” on Command Line Tools hosts, which lack `XCTest.framework`; the suite of record is `swift run adevcontainerTests`. On a TTY, clone tests can prompt for git identity; `swift run adevcontainerTests < /dev/null` runs the suite without prompts.

- Integration skips cleanly if Apple `container` is unavailable
- Override image: `ADEVCONTAINER_TEST_IMAGE`
Expand Down
108 changes: 73 additions & 35 deletions Sources/ADevContainerLib/Commands/CloneCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ public enum CloneCommand {

try enforceHostRequirements(config: resolved.config, host: hostResources)

do {
try LifecycleRunner.runInitializeCommand(
config: resolved.config,
hostWorkspace: checkoutDir
)
} catch {
throw BringUpRecovery.eligible(error)
}

// 5. Volume-mode identity (git URL + config rel path — not checkout path)
let identity = ContainerIdentity.volumeModeIdentity(
gitURL: url,
Expand Down Expand Up @@ -343,9 +352,18 @@ public enum CloneCommand {
knownOCIUser = featuresResult.baseImageUser
}
knownMetadataUsers = featuresResult.metadataUsers
} else if !options.skipPull {
StatusPrinter.status("Pulling image", item: effectiveConfig.image)
try? runtime.pullImage(effectiveConfig.image, platform: platform)
} else {
if !options.skipPull {
StatusPrinter.status("Pulling image", item: effectiveConfig.image)
try? runtime.pullImage(effectiveConfig.image, platform: platform)
}
let applied = try FeatureContributionMerge.applyFromImage(
imageRef: effectiveConfig.image,
to: effectiveConfig,
runtime: runtime
)
effectiveConfig = applied.config
knownMetadataUsers = applied.users
}

// Expand `${devcontainerId}` with volume-mode create name (not bind-mode resolve name).
Expand Down Expand Up @@ -497,9 +515,9 @@ public enum CloneCommand {
runtime: runtime
)

// 11. Create-path lifecycle hooks (same matrix as up)
// 11. Create-path lifecycle hooks (same matrix as up), split at waitFor.
do {
try LifecycleRunner.runCreatePath(
try LifecycleRunner.runCreatePathThroughWaitFor(
containerId: id,
config: effectiveConfig,
runtime: runtime
Expand All @@ -510,13 +528,6 @@ public enum CloneCommand {
throw BringUpRecovery.eligible(error)
}

// Settings apply after create-path hooks; not gated on --vscode. Soft-fail never deletes.
_ = VSCodeCustomizationsApply.applySettingsIfNeeded(
containerId: id,
config: effectiveConfig,
runtime: runtime
)

let result = CloneResult(
outcome: "success",
containerId: id,
Expand All @@ -526,30 +537,57 @@ public enum CloneCommand {
gitUrl: identity.normalizedGitURL,
workspaceVolume: identity.workspaceVolumeName
)
// Extensions apply when pending (not gated on `--vscode`) → open → postAttach.
_ = VSCodeCustomizationsApply.applyExtensionsIfNeeded(
containerId: id,
config: effectiveConfig,
runtime: runtime
)
let openOutcome = VSCodeOpen.openIfRequested(
options.openVSCode,
target: VSCodeOpenTarget(
containerId: result.containerId,
image: effectiveConfig.image,
remoteWorkspaceFolder: result.remoteWorkspaceFolder,
containerName: result.containerName ?? identity.containerName,
remoteUser: result.remoteUser

// Settings + Ready / JSON / open / postAttach at the waitFor point.
var readyError: Error?
do {
_ = VSCodeCustomizationsApply.applySettingsIfNeeded(
containerId: id,
config: effectiveConfig,
runtime: runtime
)
)
try LifecycleRunner.applyPostAttachGate(
openOutcome: openOutcome,
containerId: id,
config: effectiveConfig,
runtime: runtime
)
// Connection hints: entry point after success JSON (Ready → JSON → blank → connect).
StatusPrinter.status("Ready")
_ = VSCodeCustomizationsApply.applyExtensionsIfNeeded(
containerId: id,
config: effectiveConfig,
runtime: runtime
)
let openOutcome = VSCodeOpen.openIfRequested(
options.openVSCode,
target: VSCodeOpenTarget(
containerId: result.containerId,
image: effectiveConfig.image,
remoteWorkspaceFolder: result.remoteWorkspaceFolder,
containerName: result.containerName ?? identity.containerName,
remoteUser: result.remoteUser
)
)
try LifecycleRunner.applyPostAttachGate(
openOutcome: openOutcome,
kind: .cliAttach,
containerId: id,
config: effectiveConfig,
runtime: runtime
)
StatusPrinter.status("Ready")
try SuccessPresentation.emitSuccessJSONIfRequested(
result.jsonString(),
jsonOutput: options.jsonOutput
)
} catch {
readyError = error
}

do {
try LifecycleRunner.runCreatePathAfterWaitFor(
containerId: id,
config: effectiveConfig,
runtime: runtime
)
} catch {
try? runtime.deleteVolume(name: identity.workspaceVolumeName)
throw BringUpRecovery.eligible(error)
}
if let readyError { throw readyError }
return result
}

Expand Down
20 changes: 18 additions & 2 deletions Sources/ADevContainerLib/Commands/ExecCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public enum ExecCommand {
let user = (labeledUser?.isEmpty == false) ? labeledUser : nil
let labeledWorkdir = info.labels[ContainerIdentity.labelWorkspaceFolder]
let workdir = (labeledWorkdir?.isEmpty == false) ? labeledWorkdir : nil
// containerEnv not stored on labels; Features PATH was baked at create.
let env: [String: String] = [:]

guard info.isRunning else {
throw CLIError(
Expand All @@ -43,6 +41,24 @@ public enum ExecCommand {
)
}

// Exec is not attach — probe (unless none) and merge; never run postAttach.
var env: [String: String] = [:]
if var loaded = try ConfigReader.read(
labels: info.labels,
containerId: info.id,
runtime: runtime,
mode: .bestEffort
) {
if let user { loaded.remoteUser = user }
if let workdir { loaded.workspaceFolder = workdir }
try LifecycleRunner.applyUserEnvProbe(
containerId: info.id,
config: &loaded,
runtime: runtime
)
env = loaded.containerEnv
}

let cmd = options.command.isEmpty ? ["bash"] : options.command
let result = try runtime.exec(
nameOrId: info.id,
Expand Down
Loading
Loading