Skip to content

Commit 61b7f63

Browse files
committed
2 parents 09116cc + 275d1da commit 61b7f63

127 files changed

Lines changed: 3812 additions & 1320 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/common.yml

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ on:
1010
type: boolean
1111
description: "Publish this build for release"
1212
default: false
13+
coverage:
14+
type: boolean
15+
description: "Run tests with code coverage enabled"
16+
default: false
17+
pr_number:
18+
type: string
19+
description: "PR number for coverage artifact naming (required when coverage is true)"
20+
default: ""
1321

1422
jobs:
1523
buildAndTest:
@@ -64,13 +72,23 @@ jobs:
6472
env:
6573
DEVELOPER_DIR: "/Applications/Xcode-latest.app/Contents/Developer"
6674

75+
- name: Validate coverage inputs
76+
if: ${{ inputs.coverage }}
77+
env:
78+
PR_NUMBER: ${{ inputs.pr_number }}
79+
run: |
80+
if [ -z "${PR_NUMBER}" ]; then
81+
echo "::error::pr_number input is required when coverage is true"
82+
exit 1
83+
fi
84+
6785
- name: Create package
6886
run: |
6987
mkdir -p outputs
7088
mv "bin/${BUILD_CONFIGURATION}/container-installer-unsigned.pkg" outputs
7189
mv "bin/${BUILD_CONFIGURATION}/bundle/container-dSYM.zip" outputs
7290
73-
- name: Test the container project
91+
- name: Set up test environment
7492
run: |
7593
APP_ROOT=$(mktemp -d -p "${RUNNER_TEMP}")
7694
LOG_ROOT="${APP_ROOT}/logs"
@@ -82,10 +100,53 @@ jobs:
82100
echo no_proxy=${no_proxy}
83101
echo "APP_ROOT=${APP_ROOT}" >> $GITHUB_ENV
84102
echo "LOG_ROOT=${LOG_ROOT}" >> $GITHUB_ENV
85-
make APP_ROOT="${APP_ROOT}" LOG_ROOT="${LOG_ROOT}" test install-kernel integration
103+
104+
- name: Test the container project
105+
if: ${{ !inputs.coverage }}
106+
run: make APP_ROOT="${APP_ROOT}" LOG_ROOT="${LOG_ROOT}" test install-kernel integration
107+
env:
108+
DEVELOPER_DIR: "/Applications/Xcode-latest.app/Contents/Developer"
109+
110+
- name: Test the container project with coverage
111+
if: ${{ inputs.coverage }}
112+
run: make APP_ROOT="${APP_ROOT}" LOG_ROOT="${LOG_ROOT}" install-kernel coverage
86113
env:
87114
DEVELOPER_DIR: "/Applications/Xcode-latest.app/Contents/Developer"
88115

116+
- name: Extract coverage percentages
117+
if: ${{ inputs.coverage }}
118+
env:
119+
PR_NUMBER: ${{ inputs.pr_number }}
120+
run: |
121+
mkdir -p pr-coverage
122+
jq -r '.data[0].totals.lines.percent | . * 100 | round | . / 100' coverage-reports/unit/coverage-summary.json > pr-coverage/unit-line-coverage.txt
123+
jq -r '.data[0].totals.lines.percent | . * 100 | round | . / 100' coverage-reports/integration/coverage-summary.json > pr-coverage/integration-line-coverage.txt
124+
jq -r '.data[0].totals.lines.percent | . * 100 | round | . / 100' coverage-reports/combined/coverage-summary.json > pr-coverage/combined-line-coverage.txt
125+
echo "${PR_NUMBER}" > pr-coverage/pr-number.txt
126+
echo "Coverage data:"
127+
cat pr-coverage/*.txt
128+
129+
- name: Upload coverage data
130+
if: ${{ inputs.coverage }}
131+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
132+
with:
133+
name: pr-coverage-${{ inputs.pr_number }}
134+
path: pr-coverage/
135+
retention-days: 1
136+
if-no-files-found: warn
137+
138+
- name: Upload coverage HTML reports
139+
if: ${{ inputs.coverage }}
140+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
141+
with:
142+
name: coverage-html-reports
143+
path: |
144+
coverage-reports/unit/html/
145+
coverage-reports/integration/html/
146+
coverage-reports/combined/html/
147+
retention-days: 14
148+
if-no-files-found: ignore
149+
89150
- name: Archive test logs
90151
if: always()
91152
run: |
@@ -103,6 +164,7 @@ jobs:
103164
rm -rf "${APP_ROOT}"
104165
echo "Removed data directory ${APP_ROOT}"
105166
fi
167+
rm -rf coverage-reports pr-coverage
106168
107169
- name: Upload logs if present
108170
if: always()

.github/workflows/pr-build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ jobs:
4444
uses: ./.github/workflows/common.yml
4545
with:
4646
release: false
47+
coverage: true
48+
pr_number: ${{ github.event.pull_request.number }}
4749
secrets: inherit
4850
permissions:
4951
contents: read
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: PR Coverage Comment
2+
3+
on:
4+
workflow_run:
5+
workflows: ["container project - PR build"]
6+
types:
7+
- completed
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
comment:
14+
name: Post coverage comment
15+
runs-on: ubuntu-latest
16+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
17+
permissions:
18+
contents: read
19+
actions: read
20+
pull-requests: write
21+
22+
steps:
23+
- name: Download coverage artifact
24+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
25+
with:
26+
github-token: ${{ secrets.GITHUB_TOKEN }}
27+
run-id: ${{ github.event.workflow_run.id }}
28+
pattern: pr-coverage-*
29+
merge-multiple: false
30+
id: download-artifact
31+
continue-on-error: true
32+
33+
- name: Check artifact exists
34+
if: steps.download-artifact.outcome == 'success'
35+
id: check-artifact
36+
run: |
37+
if ls pr-coverage-*/pr-number.txt 1>/dev/null 2>&1; then
38+
echo "found=true" >> $GITHUB_OUTPUT
39+
else
40+
echo "No coverage artifact found — coverage job may have failed."
41+
echo "found=false" >> $GITHUB_OUTPUT
42+
fi
43+
44+
- name: Validate and post comment
45+
if: steps.check-artifact.outputs.found == 'true'
46+
env:
47+
GH_TOKEN: ${{ github.token }}
48+
REPO: ${{ github.repository }}
49+
run: |
50+
PR_NUMBER=$(cat pr-coverage-*/pr-number.txt)
51+
grep -qxE '[0-9]+' <<< "$PR_NUMBER" || { echo "Invalid PR number: ${PR_NUMBER}"; exit 1; }
52+
53+
UNIT=$(cat pr-coverage-*/unit-line-coverage.txt)
54+
grep -qxE '[0-9]+(\.[0-9]+)?' <<< "$UNIT" || { echo "Invalid unit coverage: ${UNIT}"; exit 1; }
55+
56+
INTEGRATION=$(cat pr-coverage-*/integration-line-coverage.txt)
57+
grep -qxE '[0-9]+(\.[0-9]+)?' <<< "$INTEGRATION" || { echo "Invalid integration coverage: ${INTEGRATION}"; exit 1; }
58+
59+
COMBINED=$(cat pr-coverage-*/combined-line-coverage.txt)
60+
grep -qxE '[0-9]+(\.[0-9]+)?' <<< "$COMBINED" || { echo "Invalid combined coverage: ${COMBINED}"; exit 1; }
61+
62+
MARKER="<!-- coverage-bot -->"
63+
BODY=$(cat <<EOF
64+
${MARKER}
65+
## Code Coverage
66+
67+
| Tier | Line Coverage |
68+
|------|--------------|
69+
| Unit | ${UNIT}% |
70+
| Integration | ${INTEGRATION}% |
71+
| Combined | ${COMBINED}% |
72+
EOF
73+
)
74+
75+
EXISTING_COMMENT_ID=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --paginate --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -1)
76+
77+
if [ -n "${EXISTING_COMMENT_ID}" ]; then
78+
gh api "repos/${REPO}/issues/comments/${EXISTING_COMMENT_ID}" -X PATCH -f body="${BODY}"
79+
echo "Updated existing coverage comment ${EXISTING_COMMENT_ID}"
80+
else
81+
gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "${BODY}"
82+
echo "Created new coverage comment"
83+
fi

BUILDING.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,11 @@ to prepare your build environment.
8080
>
8181
> **Note:** If you have already run `swift package edit`, whether intentionally or by accident, follow the steps in the next section to restore the normal `containerization` dependency. Otherwise, the modified `Package.swift` file will not work, and the project may fail to build.
8282

83-
5. If you want `container` to use any changes you made in the `vminit` subproject of Containerization, update the system property to use the locally built init filesystem image:
83+
5. If you want `container` to use any changes you made in the `vminit` subproject of Containerization, set the init image in your runtime configuration file at `~/.config/container/runtime-config.toml`:
8484

85-
```bash
86-
container system property set image.init vminit:latest
85+
```toml
86+
[vminit]
87+
image = "vminit:latest"
8788
```
8889

8990
6. Build `container`.
@@ -101,11 +102,7 @@ to prepare your build environment.
101102

102103
To revert to using the Containerization dependency from your `Package.swift`:
103104

104-
1. If you were using the local init filesystem, revert the system property to its default value:
105-
106-
```bash
107-
container system property clear image.init
108-
```
105+
1. If you were using the local init filesystem, remove the `init` override from your `~/.config/container/runtime-config.toml` (or delete the `[vminit]` section if no other image settings are present).
109106

110107
2. Use the Swift package manager to restore the normal `containerization` dependency and update your `Package.resolved` file. If you are using Xcode, revert your `Package.swift` change instead of using `swift package unedit`.
111108

@@ -133,14 +130,20 @@ To test changes that require the `container-builder-shim` project:
133130

134131
1. Clone the [container-builder-shim](https://github.com/apple/container-builder-shim) repository and navigate to its directory.
135132

136-
2. After making the necessary changes, build the custom builder image, set it as the active builder image, and remove the existing `buildkit` container so the new image will be used:
133+
2. After making the necessary changes, build the custom builder image, set it as the active builder image in `~/.config/container/runtime-config.toml`, and remove the existing `buildkit` container so the new image will be used:
137134

138135
```bash
139136
container build -t builder .
140-
container system property set image.builder builder:latest
141137
container rm -f buildkit
142138
```
143139

140+
Add the following to your `~/.config/container/runtime-config.toml`:
141+
142+
```toml
143+
[build]
144+
image = "builder:latest"
145+
```
146+
144147
3. Run the `container` build as usual:
145148

146149
```bash

Makefile

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@ LLVM_COV_IGNORE := \
166166
--ignore-filename-regex=".pb.swift" \
167167
--ignore-filename-regex=".proto" \
168168
--ignore-filename-regex=".grpc.swift"
169-
# swift test overwrites profraw data on each invocation, so we copy to a safe spot
170-
SAVE_PROFRAW = mkdir -p $(COVERAGE_OUTPUT_DIR)/integration/$(1) && cp $(COV_DATA_DIR)/*.profraw $(COVERAGE_OUTPUT_DIR)/integration/$(1)/
171169

172170
# Generate JSON + HTML coverage reports and a coverage-percent.txt from a profdata file.
173171
# $(1) = profdata path, $(2) = tier name (unit/integration/combined)
@@ -184,7 +182,7 @@ define GENERATE_COV_REPORTS
184182
-output-dir=$(COVERAGE_OUTPUT_DIR)/$(2)/html \
185183
$(TEST_BINARY)
186184
@echo Extracting $(2) coverage percentages...
187-
@jq -r '"line coverage: \(.data[0].totals.lines.percent * 100 | round / 100)%\nfunction coverage: \(.data[0].totals.functions.percent * 100 | round / 100)%"' \
185+
@jq -r '"line coverage: \(.data[0].totals.lines.percent | . * 100 | round | . / 100)%\nfunction coverage: \(.data[0].totals.functions.percent | . * 100 | round | . / 100)%"' \
188186
$(COVERAGE_OUTPUT_DIR)/$(2)/coverage-summary.json > $(COVERAGE_OUTPUT_DIR)/$(2)/coverage-percent.txt
189187
@cat $(COVERAGE_OUTPUT_DIR)/$(2)/coverage-percent.txt
190188
endef
@@ -215,9 +213,18 @@ INTEGRATION_TEST_SUITES := \
215213
TestCLINotFound \
216214
TestCLINoParallelCases
217215

216+
empty :=
217+
space := $(empty) $(empty)
218+
INTEGRATION_FILTER := $(subst $(space),|,$(strip $(INTEGRATION_TEST_SUITES)))
219+
220+
.PHONY: coverage-build
221+
coverage-build:
222+
@echo Building tests with coverage instrumentation...
223+
@$(SWIFT) build --build-tests --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION)
224+
218225
.PHONY: coverage
219226
# Merge the raw coverage data generated from coverage-unit and coverage-integration into one unified report
220-
coverage: coverage-unit coverage-integration
227+
coverage: coverage-build coverage-unit coverage-integration
221228
@echo Merging combined coverage profdata...
222229
@mkdir -p $(COVERAGE_OUTPUT_DIR)/combined
223230
@xcrun llvm-profdata merge -sparse \
@@ -231,12 +238,9 @@ coverage-unit:
231238
@echo Running unit test coverage...
232239
@rm -f $(COV_DATA_DIR)/*.profraw
233240
@mkdir -p $(COVERAGE_OUTPUT_DIR)/unit
234-
# Run the test suite with profiler (exclude integration tests)
235-
@$(SWIFT) test --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --skip TestCLI
236-
# Move the profiling data to a staging area for later consumption in full coverage aggregation
241+
@$(SWIFT) test --skip-build --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --skip TestCLI
237242
@echo Merging unit coverage profdata...
238243
@xcrun llvm-profdata merge -sparse $(COV_DATA_DIR)/*.profraw -o $(COVERAGE_OUTPUT_DIR)/unit/default.profdata
239-
# Generate both JSON (for machines) and html (for humans)
240244
$(call GENERATE_COV_REPORTS,$(COVERAGE_OUTPUT_DIR)/unit/default.profdata,unit)
241245

242246
.PHONY: coverage-integration
@@ -249,17 +253,16 @@ coverage-integration: all
249253
@bin/container --debug system start --timeout 60 $(SYSTEM_START_OPTS) && \
250254
echo "Starting CLI integration tests with coverage" && \
251255
{ \
252-
exit_code=0; \
253256
export CLITEST_LOG_ROOT=$(LOG_ROOT) ; \
254-
$(foreach suite,$(INTEGRATION_TEST_SUITES), \
255-
$(SWIFT) test --no-parallel --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter $(suite) || exit_code=1 ; $(call SAVE_PROFRAW,$(suite)) ; \
256-
) \
257+
$(SWIFT) test --skip-build --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter "$(INTEGRATION_FILTER)" ; \
258+
exit_code=$$? ; \
259+
cp $(COV_DATA_DIR)/*.profraw $(COVERAGE_OUTPUT_DIR)/integration/ ; \
257260
echo Ensuring apiserver stopped after the coverage integration tests ; \
258261
scripts/ensure-container-stopped.sh ; \
259262
exit $${exit_code} ; \
260263
}
261264
@echo Merging integration coverage profdata...
262-
@xcrun llvm-profdata merge -sparse $(COVERAGE_OUTPUT_DIR)/integration/*/*.profraw -o $(COVERAGE_OUTPUT_DIR)/integration/default.profdata
265+
@xcrun llvm-profdata merge -sparse $(COVERAGE_OUTPUT_DIR)/integration/*.profraw -o $(COVERAGE_OUTPUT_DIR)/integration/default.profdata
263266
$(call GENERATE_COV_REPORTS,$(COVERAGE_OUTPUT_DIR)/integration/default.profdata,integration)
264267

265268
.PHONY: integration
@@ -275,11 +278,9 @@ integration: init-block
275278
@bin/container --debug system start --timeout 60 --enable-kernel-install $(SYSTEM_START_OPTS) && \
276279
echo "Starting CLI integration tests" && \
277280
{ \
278-
exit_code=0; \
279281
CLITEST_LOG_ROOT=$(LOG_ROOT) && export CLITEST_LOG_ROOT ; \
280-
$(foreach suite,$(INTEGRATION_TEST_SUITES), \
281-
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter $(suite) || exit_code=1 ; \
282-
) \
282+
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter "$(INTEGRATION_FILTER)" ; \
283+
exit_code=$$? ; \
283284
echo Ensuring apiserver stopped after the CLI integration tests ; \
284285
scripts/ensure-container-stopped.sh ; \
285286
exit $${exit_code} ; \

NOTICE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ Unless required by applicable law or agreed to in writing, software distributed
4444
## Runtime Library Exception to the Apache 2.0 License: ##
4545
As an exception, if you use this Software to compile your source code and portions of this Software are embedded into the binary product as a result, you may redistribute such product without providing attribution as would otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
4646

47+
**Apple Inc. and the SwiftConfiguration project authors ( swift-configuration )**
48+
Copyright © 2025 Apple Inc. and the SwiftConfiguration project authors
49+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
50+
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
51+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
52+
4753
**Apple Inc. and the Swift Logging API project authors ( swift-log )**
4854
Copyright © 2018-2019 Apple Inc. and the Swift Logging API project authors
4955
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.

Package.resolved

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)