Skip to content

Commit 13a2f1a

Browse files
authored
Update builder-shim to 0.6.3 for metadata only Dockerfile support (#825)
- Fixes #736. - BuildKit returns nil ref for Dockerfiles containing only metadata (`ENV/ARG/LABEL`) directives without filesystem operations (`RUN/COPY/ADD`). Previously, this caused builds to fail with "no build directives" error. - Builder-shim 0.6.3 [fixes this](apple/container-builder-shim#47) by creating a minimal marker layer when ref is nil but image config is valid, satisfying OCI manifest requirements. - Also, added some tests for this behavior.
1 parent c909cb3 commit 13a2f1a

2 files changed

Lines changed: 164 additions & 1 deletion

File tree

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import PackageDescription
2222

2323
let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0"
2424
let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified"
25-
let builderShimVersion = "0.6.1"
25+
let builderShimVersion = "0.6.3"
2626
let scVersion = "0.12.1"
2727

2828
let package = Package(
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2025 Apple Inc. and the container project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import Foundation
18+
import Testing
19+
20+
extension TestCLIBuildBase {
21+
class CLIBuilderEnvOnlyTest: TestCLIBuildBase {
22+
override init() throws {
23+
try super.init()
24+
}
25+
26+
deinit {
27+
try? builderDelete(force: true)
28+
}
29+
30+
@Test func testBuildEnvironmentOnlyImageFromScratch() throws {
31+
let tempDir: URL = try createTempDir()
32+
let dockerfile =
33+
"""
34+
FROM scratch
35+
36+
ARG BUILD_DATE
37+
ARG VERSION=1.0.0
38+
39+
ENV TERM=xterm \\
40+
BUILD_DATE=${BUILD_DATE} \\
41+
APP_VERSION=${VERSION} \\
42+
PATH=/usr/local/bin:/usr/bin:/bin
43+
44+
LABEL maintainer="test@example.com" \\
45+
version="${VERSION}"
46+
"""
47+
48+
try createContext(tempDir: tempDir, dockerfile: dockerfile)
49+
let imageName = "test-env-only:\(UUID().uuidString)"
50+
try self.build(tag: imageName, tempDir: tempDir, buildArgs: ["BUILD_DATE=2025-01-01", "VERSION=2.0.0"])
51+
#expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)")
52+
}
53+
54+
@Test func testBuildEnvironmentOnlyImageFromAlpine() throws {
55+
let tempDir: URL = try createTempDir()
56+
let dockerfile =
57+
"""
58+
FROM ghcr.io/linuxcontainers/alpine:3.20
59+
60+
ENV APP_NAME=myapp \\
61+
APP_VERSION=1.0.0 \\
62+
APP_ENV=production
63+
64+
LABEL maintainer="test@example.com" \\
65+
version="1.0.0" \\
66+
description="Test environment-only image"
67+
"""
68+
69+
try createContext(tempDir: tempDir, dockerfile: dockerfile)
70+
let imageName = "test-alpine-env:\(UUID().uuidString)"
71+
try self.build(tag: imageName, tempDir: tempDir)
72+
#expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)")
73+
}
74+
75+
@Test func testMultiStageBuildWithEnvOnlyBase() throws {
76+
let tempDir: URL = try createTempDir()
77+
let baseImageName = "test-env-base:\(UUID().uuidString)"
78+
79+
// First, create an environment-only base image
80+
let baseDockerfile =
81+
"""
82+
FROM scratch
83+
84+
ARG JOBS=6
85+
ARG ARCH=amd64
86+
87+
ENV MAKEOPTS="-j${JOBS}" \\
88+
ARCH="${ARCH}" \\
89+
PATH=/usr/local/bin:/usr/bin
90+
"""
91+
92+
try createContext(tempDir: tempDir, dockerfile: baseDockerfile)
93+
try self.build(tag: baseImageName, tempDir: tempDir, buildArgs: ["JOBS=8", "ARCH=arm64"])
94+
#expect(try self.inspectImage(baseImageName) == baseImageName, "expected base image to build successfully")
95+
96+
// Now create a downstream image that uses it
97+
let downstreamTempDir: URL = try createTempDir()
98+
let downstreamDockerfile =
99+
"""
100+
FROM \(baseImageName)
101+
102+
# Verify environment is inherited - note: can't use RUN with scratch base
103+
LABEL test="env-inherited"
104+
"""
105+
106+
try createContext(tempDir: downstreamTempDir, dockerfile: downstreamDockerfile)
107+
let downstreamImageName = "test-env-child:\(UUID().uuidString)"
108+
try self.build(tag: downstreamImageName, tempDir: downstreamTempDir)
109+
#expect(
110+
try self.inspectImage(downstreamImageName) == downstreamImageName,
111+
"expected downstream image to build successfully"
112+
)
113+
}
114+
115+
@Test func testComplexArgAndEnvCombinations() throws {
116+
let tempDir: URL = try createTempDir()
117+
let dockerfile =
118+
"""
119+
FROM scratch
120+
121+
ARG JOBS=6
122+
ARG MAXLOAD=7.00
123+
ARG ARCH=amd64
124+
ARG PROFILE_PATH=23.0/split-usr/no-multilib
125+
ARG CHOST=x86_64-pc-linux-gnu
126+
ARG CFLAGS=-O2 -pipe
127+
128+
ENV JOBS="${JOBS}" \\
129+
MAXLOAD="${MAXLOAD}" \\
130+
GENTOO_PROFILE="default/linux/${ARCH}/${PROFILE_PATH}" \\
131+
CHOST="${CHOST}" \\
132+
MAKEOPTS="-j${JOBS}" \\
133+
CFLAGS="${CFLAGS}" \\
134+
CXXFLAGS="${CFLAGS}"
135+
136+
LABEL maintainer="test@example.com"
137+
"""
138+
139+
try createContext(tempDir: tempDir, dockerfile: dockerfile)
140+
let imageName = "test-complex-env:\(UUID().uuidString)"
141+
try self.build(tag: imageName, tempDir: tempDir, buildArgs: ["JOBS=12", "ARCH=arm64"])
142+
#expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)")
143+
}
144+
145+
@Test func testLabelOnlyDockerfile() throws {
146+
let tempDir: URL = try createTempDir()
147+
let dockerfile =
148+
"""
149+
FROM scratch
150+
151+
LABEL maintainer="test@example.com" \\
152+
version="1.0.0" \\
153+
description="Test image with only labels" \\
154+
org.opencontainers.image.title="Test Image"
155+
"""
156+
157+
try createContext(tempDir: tempDir, dockerfile: dockerfile)
158+
let imageName = "test-label-only:\(UUID().uuidString)"
159+
try self.build(tag: imageName, tempDir: tempDir)
160+
#expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)")
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)