Skip to content
Merged
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
runs-on: macos-26
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- uses: actions/cache@v6
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: .build
key: spm-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Package.resolved') }}
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
runs-on: macos-26
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- name: Build the app and UI tests with zero warnings
Expand All @@ -85,7 +85,7 @@ jobs:
scripts/check-layout.sh \
"$RUNNER_TEMP/dd/Build/Products/Debug/iOS Developer Toolkit (Swift).app/Contents/MacOS/iOS Developer Toolkit (Swift)" \
"$RUNNER_TEMP/layout"
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
if: always()
with:
name: ui-test-results
Expand All @@ -99,10 +99,10 @@ jobs:
runs-on: macos-26
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- uses: actions/cache@v6
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: .build
key: spm-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Package.resolved') }}
Expand All @@ -118,13 +118,13 @@ jobs:
runs-on: macos-26
timeout-minutes: 90
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- name: Install the firmware helpers' build tools
run: brew install autoconf automake libtool pkg-config cmake
- name: Reuse the firmware helpers built from the same pinned sources
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: build-output/restore-helpers
key: restore-helpers-${{ hashFiles('scripts/build-restore-helpers.sh') }}-${{ env.XCODE_VERSION }}
Expand Down
146 changes: 139 additions & 7 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ permissions:
env:
XCODE_VERSION: ${{ vars.XCODE_VERSION }}

concurrency:
group: codeql-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: Analyze Swift
Expand All @@ -25,21 +29,149 @@ jobs:
permissions:
contents: read
packages: read
security-events: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- uses: github/codeql-action/init@v4
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
languages: swift
build-mode: manual
- name: Build the package and the app
run: |
swift build
swift build --arch x86_64
xcodebuild -project iOSDeveloperToolkit.xcodeproj -scheme iOSDeveloperToolkit \
-configuration Debug -destination 'platform=macOS' \
-derivedDataPath "$RUNNER_TEMP/dd" build
- uses: github/codeql-action/analyze@v4
-configuration Debug -destination 'platform=macOS,arch=x86_64' \
-derivedDataPath "$RUNNER_TEMP/dd" \
ARCHS=x86_64 ONLY_ACTIVE_ARCH=YES SWIFT_ENABLE_EXPLICIT_MODULES=NO \
CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO \
COMPILATION_CACHE_ENABLE_CACHING=NO SWIFT_ENABLE_COMPILE_CACHE=NO \
SWIFT_USE_INTEGRATED_DRIVER=NO build
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
upload: never
upload-database: false
output: ${{ runner.temp }}/codeql-sarif
category: "/language:swift"
- name: Reject Swift extraction errors
env:
SARIF_FILE: ${{ runner.temp }}/codeql-sarif/swift.sarif
run: |
if [[ ! -s "$SARIF_FILE" ]]; then
printf 'Swift SARIF is missing or empty.\n' >&2
exit 1
fi
error_count="$(jq -er '
if type != "object" or (.runs | type) != "array" or (.runs | length) == 0 then
error("Invalid Swift SARIF runs")
elif any(.runs[]; type != "object" or (.invocations | type) != "array" or (.invocations | length) == 0) then
error("Invalid Swift SARIF invocations")
elif any(.runs[].invocations[]; type != "object" or (.toolExecutionNotifications | type) != "array") then
error("Invalid Swift SARIF notifications")
elif any(.runs[].invocations[].toolExecutionNotifications[]; type != "object" or (.descriptor | type) != "object" or (.descriptor.id | type) != "string") then
error("Invalid Swift SARIF notification descriptor")
else
[.runs[].invocations[].toolExecutionNotifications[] | select(.descriptor.id == "swift/diagnostics/extraction-errors")] | length
end
' "$SARIF_FILE")"
if [[ "$error_count" != "0" ]]; then
printf 'Swift extraction reported %s compiler diagnostics; inspect the analysis diagnostics before publishing SARIF.\n' "$error_count" >&2
exit 1
fi
printf 'Swift extraction compiler diagnostics: 0\n'
- name: Upload SARIF artifact
if: success() || failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: codeql-swift
path: ${{ runner.temp }}/codeql-sarif
if-no-files-found: error
retention-days: 1

analyze-actions:
name: Analyze Actions
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
languages: actions
build-mode: none
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
upload: never
upload-database: false
output: ${{ runner.temp }}/codeql-sarif
category: /language:actions
- name: Upload SARIF artifact
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: codeql-actions
path: ${{ runner.temp }}/codeql-sarif
if-no-files-found: error
retention-days: 1

upload-analyze:
name: Upload Swift analysis
needs: analyze
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- name: Download SARIF artifact
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: codeql-swift
path: sarif
- name: Upload analysis to code scanning
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
sarif_file: sarif
category: /language:swift
wait-for-processing: true

upload-analyze-actions:
name: Upload Actions analysis
needs: analyze-actions
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- name: Download SARIF artifact
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: codeql-actions
path: sarif
- name: Upload analysis to code scanning
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
sarif_file: sarif
category: /language:actions
wait-for-processing: true

upload-gate:
name: Code scanning uploads
needs: [upload-analyze, upload-analyze-actions]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Require every analysis upload to succeed
env:
SWIFT_UPLOAD_RESULT: ${{ needs.upload-analyze.result }}
ACTIONS_UPLOAD_RESULT: ${{ needs.upload-analyze-actions.result }}
run: |
if [[ "$SWIFT_UPLOAD_RESULT" != "success" ]] || [[ "$ACTIONS_UPLOAD_RESULT" != "success" ]]; then
printf 'Code scanning upload failed or was skipped: swift=%s; actions=%s\n' "$SWIFT_UPLOAD_RESULT" "$ACTIONS_UPLOAD_RESULT" >&2
exit 1
fi
4 changes: 2 additions & 2 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: actions/dependency-review-action@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5
13 changes: 7 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,30 @@ jobs:
runs-on: macos-26
timeout-minutes: 90
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- name: Test before packaging
run: swift test -Xswiftc -warnings-as-errors
- name: Install the firmware helpers' build tools
run: brew install autoconf automake libtool pkg-config cmake
- name: Reuse the firmware helpers built from the same pinned sources
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: build-output/restore-helpers
key: restore-helpers-${{ hashFiles('scripts/build-restore-helpers.sh') }}-${{ env.XCODE_VERSION }}
- name: Build, sign, and verify the release
# Fails unless the tag (without "v") matches ToolkitVersion.current and MARKETING_VERSION.
run: scripts/build-release.sh "${GITHUB_REF_NAME#v}" release-assets
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: release-assets
path: release-assets/*
if-no-files-found: error

publish:
name: Attest and publish
environment: github-release
needs: build
runs-on: ubuntu-latest
permissions:
Expand All @@ -50,7 +51,7 @@ jobs:
contents: write
id-token: write
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: release-assets
path: release-assets
Expand All @@ -67,11 +68,11 @@ jobs:
echo "zip=$zip" >> "$GITHUB_OUTPUT"
echo "sbom=$sbom" >> "$GITHUB_OUTPUT"
- name: Attest build provenance
uses: actions/attest-build-provenance@v4.2.2
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: ${{ steps.files.outputs.zip }}
- name: Attest SBOM
uses: actions/attest@v4.2.2
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: ${{ steps.files.outputs.zip }}
sbom-path: ${{ steps.files.outputs.sbom }}
Expand Down
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Repository workflow

- Follow CONTRIBUTING.md, SECURITY.md, and docs/release-verification.md.
- Preserve the five required main checks: Swift package tests; App build, UI tests, and layout; Real simulator end-to-end test; Release packaging; Analyze Swift.
- Run the applicable commands in CONTRIBUTING.md. Preserve warnings-as-errors, real simulator execution, UI tests, rendered layout checks, and release verification.
- Verify a successful Swift CodeQL analysis at the candidate revision. An older scan or successful Actions analysis does not validate new Swift code.
- Releases are version 1.x or later from main. Confirm the tag, ToolkitVersion.current, MARKETING_VERSION, and exact candidate revision before publishing.
- A v[1-9]* tag push starts release publication through .github/workflows/release.yml; obtain authorization covering publication before pushing it, and retain owner approval on github-release.
- Source extraction and analysis use read-only tokens with upload: never and upload-database: false. Separate upload-only jobs publish SARIF; Code scanning uploads must succeed for every configured language.
4 changes: 3 additions & 1 deletion App/iOSDeveloperToolkit/Views/ActionsViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ struct ActionsView: View {
TextField("Search guided commands", text: $search)
.textFieldStyle(.roundedBorder)
.accessibilityIdentifier("command-search")
Picker("Category", selection: Binding(get: { filter }, set: selectFilter)) {
Picker("Category", selection: Binding(get: { filter }, set: { selectFilter($0) })) {
Text("All categories").tag(GuidedCommandFilter.all)
ForEach(GuidedCommandCategory.allCases) { category in
Text(category.rawValue)
Expand All @@ -113,6 +113,8 @@ struct ActionsView: View {
}
}
.labelsHidden()
// Separate the control's identity from its menu items' identifiers.
.accessibilityElement(children: .contain)
.accessibilityIdentifier("command-categories")
List(selection: $model.selectedActionID) {
ForEach(GuidedCommandCategory.allCases.filter { category in actions.contains { category.contains($0) } }) { category in
Expand Down
Loading