This is an automated email from the ASF dual-hosted git repository.
spetz pushed a commit to branch swift-release-pipeline
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/swift-release-pipeline by this
push:
new 72ab520aa fix review
72ab520aa is described below
commit 72ab520aa02d53aaf01abec7c4848d9a23a1be6a
Author: spetz <[email protected]>
AuthorDate: Tue Nov 3 16:00:55 2026 +0100
fix review
---
.github/actions/swift/post-merge/action.yml | 13 ------
.github/actions/swift/pre-merge/action.yml | 35 +++++++---------
.github/actions/utils/setup-swift/action.yml | 36 ++++++++++------
.github/workflows/_test_examples.yml | 8 ++++
.github/workflows/publish.yml | 62 +++++++++++++++++++++++++++-
AGENTS.md | 2 +-
Package.swift | 2 +-
foreign/swift/Package.swift | 2 +-
foreign/swift/README.md | 23 +++++++----
scripts/bump-version.sh | 46 ++++++---------------
10 files changed, 139 insertions(+), 90 deletions(-)
diff --git a/.github/actions/swift/post-merge/action.yml
b/.github/actions/swift/post-merge/action.yml
index f849b277e..80ae46352 100644
--- a/.github/actions/swift/post-merge/action.yml
+++ b/.github/actions/swift/post-merge/action.yml
@@ -67,17 +67,6 @@ runs:
done
echo "✅ Root manifest and legal files present"
- - name: Setup Swift
- uses: ./.github/actions/utils/setup-swift
- with:
- cache-key: "release"
- package-path: "."
-
- # An unpinned release build checks dependency resolution before the tag
exists.
- - name: Build the published package
- shell: bash
- run: swift build -c release --product Iggy
-
- name: Summary
shell: bash
env:
@@ -101,7 +90,5 @@ runs:
echo ""
if [ "$DRY_RUN" = "true" ]; then
echo "**Note:** Dry run, no tag will be created"
- else
- echo "**Note:** The publish workflow creates the tag after this
step"
fi
} >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/actions/swift/pre-merge/action.yml
b/.github/actions/swift/pre-merge/action.yml
index ef88b400f..112a4b1b2 100644
--- a/.github/actions/swift/pre-merge/action.yml
+++ b/.github/actions/swift/pre-merge/action.yml
@@ -29,6 +29,7 @@ runs:
# The macOS lane uses the Swift that ships with Xcode on the runner, and
# the golden lane only runs the Rust generator.
- name: Setup Swift
+ id: setup-swift
if: inputs.task != 'build-macos' && inputs.task != 'golden'
uses: ./.github/actions/utils/setup-swift
@@ -53,25 +54,12 @@ runs:
if: inputs.task == 'lint'
run: |
set -euo pipefail
- root_manifest=$(swift package --package-path . dump-package)
- sdk_manifest=$(swift package --package-path foreign/swift dump-package)
- if ! jq -e '.products | length == 1 and .[0].name == "Iggy"' <<<
"$root_manifest" >/dev/null \
- || ! jq -e '.targets | length == 1 and .[0].name == "Iggy"' <<<
"$root_manifest" >/dev/null; then
- echo "❌ Root Package.swift must declare only the Iggy library and
target"
- exit 1
- fi
- root_path=$(jq -r '.targets[] | select(.name == "Iggy") | .path' <<<
"$root_manifest")
- if [ "$root_path" != "foreign/swift/Sources/Iggy" ]; then
- echo "❌ Root Package.swift builds Iggy from '$root_path', expected
foreign/swift/Sources/Iggy"
- exit 1
- fi
- sdk_path=$(jq -r '.targets[] | select(.name == "Iggy") | .path //
"Sources/Iggy"' <<< "$sdk_manifest")
- if [ "$sdk_path" != "Sources/Iggy" ]; then
- echo "❌ SDK Package.swift builds Iggy from '$sdk_path', expected
Sources/Iggy"
- exit 1
- fi
- projection='del(.name, .packageKind) | .products |= map(select(.name
== "Iggy")) | .targets |= map(select(.name == "Iggy") | del(.path))'
- if ! diff <(jq -S "$projection" <<< "$root_manifest") <(jq -S
"$projection" <<< "$sdk_manifest"); then
+ # Each side maps its expected Iggy path to one sentinel, so an extra
root product or target, or a moved path, shows in the diff.
+ root_manifest=$(swift package --package-path . dump-package \
+ | jq -S 'del(.name, .packageKind) | .targets |= map(if .name ==
"Iggy" and .path == "foreign/swift/Sources/Iggy" then .path = "SDK" else .
end)')
+ sdk_manifest=$(swift package --package-path foreign/swift dump-package
\
+ | jq -S 'del(.name, .packageKind) | .products |= map(select(.name ==
"Iggy")) | .targets |= map(select(.name == "Iggy") | if (.path //
"Sources/Iggy") == "Sources/Iggy" then .path = "SDK" else . end)')
+ if ! diff <(echo "$root_manifest") <(echo "$sdk_manifest"); then
echo "❌ Package.swift and foreign/swift/Package.swift disagree, see
the diff above"
exit 1
fi
@@ -95,6 +83,15 @@ runs:
cd foreign/swift
swift test --parallel
+ # The lanes share one cache key, so only the test lane saves: the e2e lane
+ # builds the same debug products, and every lane reuses the checkouts.
+ - name: Save SwiftPM build directory
+ if: inputs.task == 'test' && steps.setup-swift.outputs.cache-hit !=
'true'
+ uses: actions/cache/[email protected]
+ with:
+ path: foreign/swift/.build
+ key: ${{ steps.setup-swift.outputs.cache-key }}
+
- name: Build
shell: bash
if: inputs.task == 'build'
diff --git a/.github/actions/utils/setup-swift/action.yml
b/.github/actions/utils/setup-swift/action.yml
index 06cd9734f..e6daddf2c 100644
--- a/.github/actions/utils/setup-swift/action.yml
+++ b/.github/actions/utils/setup-swift/action.yml
@@ -16,7 +16,7 @@
# under the License.
name: setup-swift
-description: Install the Swift toolchain and cache the SwiftPM dependency
checkouts and build products
+description: Install the Swift toolchain and restore the SwiftPM build
directory
inputs:
cache-key:
description: "Cache key segment naming the package that builds in the job;
one constant per package so every lane shares one cache"
@@ -26,6 +26,18 @@ inputs:
description: "Directory of the SwiftPM package whose build directory is
cached"
required: false
default: "foreign/swift"
+ read-cache:
+ description: "Whether to restore the SwiftPM build directory"
+ required: false
+ default: "true"
+
+outputs:
+ cache-key:
+ description: "Key the caller saves the build directory under with
actions/cache/save after its build"
+ value: ${{ steps.cache.outputs.cache-primary-key }}
+ cache-hit:
+ description: "Whether the restored build directory matched the key exactly"
+ value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: "composite"
@@ -80,18 +92,18 @@ runs:
shell: bash
run: swift --version
- # Package.resolved pins the dependency graph, so it keys the cache; the
- # build directories hold the compiled dependencies, and SwiftPM's
- # incremental build reuses them across source changes.
- - name: Cache SwiftPM dependencies and build products
- uses: actions/[email protected]
+ # Package.resolved pins the dependency graph, so it keys the cache. The
+ # whole build directory is restored: without workspace-state.json SwiftPM
+ # recreates every checkout, and .build/debug is only a symlink into
+ # .build/out. A nested composite action hands the post step of
+ # actions/cache the wrong context (actions/runner#2009), so the caller
+ # saves with actions/cache/save once its build succeeds.
+ - name: Restore SwiftPM build directory
+ id: cache
+ if: inputs.read-cache == 'true'
+ uses: actions/cache/[email protected]
with:
- path: |
- ${{ inputs.package-path }}/.build/checkouts
- ${{ inputs.package-path }}/.build/repositories
- ${{ inputs.package-path }}/.build/artifacts
- ${{ inputs.package-path }}/.build/debug
- ${{ inputs.package-path }}/.build/release
+ path: ${{ inputs.package-path }}/.build
key: swiftpm-${{ runner.os }}-${{ steps.swift.outputs.version }}-${{
inputs.cache-key }}-${{ hashFiles(format('{0}/Package.resolved',
inputs.package-path)) }}
restore-keys: |
swiftpm-${{ runner.os }}-${{ steps.swift.outputs.version }}-${{
inputs.cache-key }}-
diff --git a/.github/workflows/_test_examples.yml
b/.github/workflows/_test_examples.yml
index f6b022061..fd160960a 100644
--- a/.github/workflows/_test_examples.yml
+++ b/.github/workflows/_test_examples.yml
@@ -92,6 +92,7 @@ jobs:
download-deps: "false"
- name: Setup Swift for examples
+ id: setup-swift
if: matrix.task == 'examples-swift'
uses: ./.github/actions/utils/setup-swift
with:
@@ -103,6 +104,13 @@ jobs:
if: matrix.task == 'examples-swift'
run: swift build --package-path examples/swift/getting-started
-Xswiftc -warnings-as-errors
+ - name: Save Swift build directory
+ if: matrix.task == 'examples-swift' &&
steps.setup-swift.outputs.cache-hit != 'true'
+ uses: actions/cache/[email protected]
+ with:
+ path: examples/swift/getting-started/.build
+ key: ${{ steps.setup-swift.outputs.cache-key }}
+
- name: Test Go examples
if: matrix.task == 'examples-go'
working-directory: examples/go
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f2aa6521e..0dab46d0c 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -185,6 +185,7 @@ jobs:
docker_components: ${{ steps.mk.outputs.docker_components }}
count: ${{ steps.mk.outputs.count }}
has_python: ${{ steps.mk.outputs.has_python }}
+ has_swift: ${{ steps.mk.outputs.has_swift }}
has_docker: ${{ steps.mk.outputs.has_docker }}
steps:
- name: Download latest copy script from master
@@ -365,6 +366,8 @@ jobs:
core.setOutput('has_python', 'false');
}
+ core.setOutput('has_swift', String(targets.some(t => t.key ===
'sdk-swift')));
+
check-tags:
name: Existing tags
needs: [validate, plan]
@@ -662,6 +665,50 @@ jobs:
use_latest_ci: ${{ inputs.use_latest_ci }}
commit: ${{ needs.validate.outputs.commit }}
+ # Without a lockfile SwiftPM runs the newest dependency manifests,
unsandboxed
+ # on Linux, so the build a consumer would get runs without a write token.
+ build-swift-package:
+ name: Build Swift package
+ needs: [validate, plan, check-tags]
+ if: |
+ needs.validate.outputs.has_targets == 'true' &&
+ needs.plan.outputs.has_swift == 'true'
+ runs-on: ubuntu-26.04
+ permissions:
+ contents: read
+ steps:
+ - name: Download latest copy script from master
+ if: inputs.use_latest_ci
+ run: |
+ curl -fsSL --retry 5 --retry-all-errors
"https://raw.githubusercontent.com/${{ github.repository
}}/master/scripts/copy-latest-from-master.sh" \
+ -o /tmp/copy-latest-from-master.sh
+ chmod +x /tmp/copy-latest-from-master.sh
+ echo "✅ Downloaded latest copy script from master"
+
+ - name: Checkout at commit
+ uses: actions/[email protected]
+ with:
+ ref: ${{ needs.validate.outputs.commit }}
+ persist-credentials: false
+
+ - name: Save and apply latest CI from master
+ if: inputs.use_latest_ci
+ run: |
+ /tmp/copy-latest-from-master.sh save \
+ .github \
+ scripts
+
+ /tmp/copy-latest-from-master.sh apply
+
+ # A cold resolve is what a consumer gets, so this build reads no cache.
+ - name: Setup Swift
+ uses: ./.github/actions/utils/setup-swift
+ with:
+ read-cache: "false"
+
+ - name: Build the published package
+ run: swift build -c release --product Iggy
+
# Sequential Rust crate publishing to handle dependencies properly
publish-rust-crates:
name: Publish Rust crates
@@ -992,13 +1039,14 @@ jobs:
# Note: This job runs in parallel with Docker publishing - no dependency
between them
publish:
name: ${{ matrix.name }}
- needs: [validate, plan, check-tags, build-python-wheels,
publish-rust-crates]
+ needs: [validate, plan, check-tags, build-python-wheels,
build-swift-package, publish-rust-crates]
if: |
!cancelled() &&
needs.check-tags.result == 'success' &&
needs.validate.outputs.has_targets == 'true' &&
fromJson(needs.plan.outputs.non_docker_targets).include[0].key != 'noop'
&&
(needs.build-python-wheels.result == 'success' ||
needs.build-python-wheels.result == 'skipped') &&
+ (needs.build-swift-package.result == 'success' ||
needs.build-swift-package.result == 'skipped') &&
(needs.publish-rust-crates.result == 'success' ||
needs.publish-rust-crates.result == 'skipped')
runs-on: ubuntu-26.04
permissions:
@@ -1310,6 +1358,7 @@ jobs:
plan,
check-tags,
build-python-wheels,
+ build-swift-package,
publish-rust-crates,
publish-docker,
docker-manifests,
@@ -1443,6 +1492,16 @@ jobs:
echo
fi
+ if [ "${{ needs.plan.outputs.has_swift }}" = "true" ]; then
+ echo "### Swift Package Build"
+ case "${{ needs.build-swift-package.result }}" in
+ success) echo "✅ **Swift package built from the root
manifest**" ;;
+ failure) echo "❌ **Swift package build failed**" ;;
+ skipped) echo "⏭️ **Swift package build was skipped**" ;;
+ esac
+ echo
+ fi
+
# Rust crates publishing status
if [ -n "${{ inputs.publish_crates }}" ]; then
echo "### Rust Crates Publishing (Sequential)"
@@ -1502,6 +1561,7 @@ jobs:
validate,
plan,
build-python-wheels,
+ build-swift-package,
publish-rust-crates,
publish-docker,
docker-manifests,
diff --git a/AGENTS.md b/AGENTS.md
index 68ff45dba..708a7ba45 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -115,7 +115,7 @@ iggy/
│ ├── shard/ Shard runtime
│ ├── harness_derive/ #[iggy_harness] proc macro
│ └── metadata, partitions, server_common, configs_derive, clock, tools
-├── foreign/ Java / .NET / Python / Go / C++ / Node.js SDKs
+├── foreign/ Java / .NET / Python / Go / C++ / Node.js / Swift
SDKs
├── bdd/ Cross-language BDD (Gherkin)
├── examples/ Per-SDK runnable examples
├── helm/ Helm charts
diff --git a/Package.swift b/Package.swift
index 0486d6b1f..e73930e11 100644
--- a/Package.swift
+++ b/Package.swift
@@ -36,7 +36,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio.git", from:
"2.80.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from:
"2.29.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
- .package(url: "https://github.com/apple/swift-crypto.git",
"3.0.0"..<"4.0.0"),
+ .package(url: "https://github.com/apple/swift-crypto.git",
"3.0.0"..<"6.0.0"),
],
targets: [
.target(
diff --git a/foreign/swift/Package.swift b/foreign/swift/Package.swift
index 413814b1a..eae3d7120 100644
--- a/foreign/swift/Package.swift
+++ b/foreign/swift/Package.swift
@@ -34,7 +34,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio.git", from:
"2.80.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from:
"2.29.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
- .package(url: "https://github.com/apple/swift-crypto.git",
"3.0.0"..<"4.0.0"),
+ .package(url: "https://github.com/apple/swift-crypto.git",
"3.0.0"..<"6.0.0"),
],
targets: [
.target(
diff --git a/foreign/swift/README.md b/foreign/swift/README.md
index 44888721d..e804516ef 100644
--- a/foreign/swift/README.md
+++ b/foreign/swift/README.md
@@ -36,28 +36,32 @@ The SDK depends on
[SwiftNIO](https://github.com/apple/swift-nio),
## Installation
-Swift Package Manager resolves a package from the root of a git repository, so
the Iggy repository root carries a `Package.swift` that publishes this SDK.
Depend on the Iggy repository itself, replacing `0.1.10` with a released
version:
+Swift Package Manager resolves a package from the root of a git repository, so
the Iggy repository root carries a `Package.swift` that publishes this SDK.
Depend on the Iggy repository itself.
+
+There is no stable Swift release yet. Until there is, pin a development
snapshot exactly, replacing `0.1.10-edge.1` with the newest `vX.Y.Z-edge.N` tag:
```swift
// Package.swift
dependencies: [
- .package(url: "https://github.com/apache/iggy.git", from: "0.1.10")
+ .package(url: "https://github.com/apache/iggy.git", exact: "0.1.10-edge.1")
],
targets: [
.target(name: "MyApp", dependencies: [.product(name: "Iggy", package:
"iggy")])
]
```
-Stable Swift releases use `vX.Y.Z` tags after PMC approval. An `X.Y.Z-edge.N`
tag is a development snapshot of unreleased changes, not an Apache release. A
version range such as `from: "0.1.10"` never selects a snapshot. The first
resolve clones the whole Iggy repository, which SwiftPM then caches.
+Stable Swift releases use `vX.Y.Z` tags after PMC approval. An `X.Y.Z-edge.N`
tag is a development snapshot of unreleased changes, not an Apache release, and
a version range never selects one. Once a stable release exists, depend on it
with `.upToNextMinor(from: "X.Y.Z")`: while the SDK is 0.x, `from:` would also
take the next minor release. A requirement must start at the first stable
`v0.1.x` tag, because the repository's `0.0.4` to `0.0.52` tags predate the
Swift SDK and have no root [...]
+
+The first resolve mirror-clones the whole Iggy repository, about 145 MiB with
every pull request ref, and checks out its 67 MiB tree for a 0.8 MiB SDK.
SwiftPM keeps the clone in its cache, so later resolves fetch only new commits.
-From a checkout of this repository, or a vendored copy, depend on it by path:
+From a checkout of this repository, depend on its root by path. SwiftPM names
a path dependency after its directory, so the package is `iggy` for a checkout
in `../iggy`:
```swift
dependencies: [
- .package(name: "apache-iggy", path: "../iggy/foreign/swift")
+ .package(path: "../iggy")
],
targets: [
- .target(name: "MyApp", dependencies: [.product(name: "Iggy", package:
"apache-iggy")])
+ .target(name: "MyApp", dependencies: [.product(name: "Iggy", package:
"iggy")])
]
```
@@ -537,19 +541,20 @@ swift format lint --strict --recursive Sources Tests
Package.swift
swift build -Xswiftc -warnings-as-errors
```
-The same configuration covers the examples and the BDD runner, from the
repository root:
+The same configuration covers the examples, the BDD runner and the root
manifest, from the repository root:
```bash
for package in examples/swift/getting-started bdd/swift/tests; do
swift format lint --strict --recursive --configuration
foreign/swift/.swift-format "$package/Sources" "$package/Package.swift"
done
+swift format lint --strict --configuration foreign/swift/.swift-format
Package.swift
```
### Releasing
-`IggyVersion.sdkVersion` in `Sources/Iggy/Iggy.swift` is the SDK version.
Change it only with `scripts/bump-version.sh sdk-swift` from the repository
root, which also regenerates the golden vectors that embed it: `--edge` for the
next development snapshot, `--strip-edge` for the version that goes to a
release vote.
+`IggyVersion.sdkVersion` in `Sources/Iggy/Iggy.swift` is the SDK version.
Change it only with `scripts/bump-version.sh sdk-swift` from the repository
root, which also regenerates the golden vectors that embed it: `--edge` for the
next development snapshot, `--patch --edge` for the first snapshot after a
stable release, `--strip-edge` for the version that goes to a release vote.
-After a merge to master, CI tags an untagged `X.Y.Z-edge.N` version as
`vX.Y.Z-edge.N`. Developers who choose to test unreleased changes can select an
existing snapshot with an exact requirement such as `exact: "0.1.10-edge.1"`.
+After a merge to master, CI tags an untagged `X.Y.Z-edge.N` version as
`vX.Y.Z-edge.N`.
For a stable release, the PMC vote must cover the Swift version and source
archive, including the root manifest. The release manager verifies the vote
result and publishes the signed source release through Apache's release
process. The workflow does not verify the vote automatically. Merging a plain
`X.Y.Z` version to master does not publish a stable Swift tag.
diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh
index fc755c2ea..08dee36ef 100755
--- a/scripts/bump-version.sh
+++ b/scripts/bump-version.sh
@@ -492,20 +492,6 @@ preflight_consistency_check() {
echo "$first_canonical"
}
-# Swift golden vectors embed the SDK version. A failed run leaves the fixture
untouched.
-regenerate_swift_golden() {
- local generated
- generated=$(mktemp "${REPO_ROOT}/${SWIFT_GOLDEN_FIXTURE}.XXXXXX") ||
return 1
- if ! cargo run --quiet --manifest-path
"${REPO_ROOT}/${SWIFT_GOLDEN_GENERATOR}" -- "$generated"; then
- rm -f "$generated"
- return 1
- fi
- if ! chmod 644 "$generated" || ! mv -f "$generated"
"${REPO_ROOT}/${SWIFT_GOLDEN_FIXTURE}"; then
- rm -f "$generated"
- return 1
- fi
-}
-
# Main bump logic.
cmd_bump() {
local component="$1"
@@ -589,17 +575,9 @@ cmd_bump() {
echo -e "${CYAN}New:${NC} ${new_ver}"
echo ""
- local swift_backup=""
- if [[ "$component" == "sdk-swift" && $apply -eq 1 ]]; then
- if ! command -v cargo > /dev/null 2>&1; then
- echo -e "${RED}sdk-swift needs cargo to regenerate
${SWIFT_GOLDEN_FIXTURE}${NC}" >&2
- return 1
- fi
- swift_backup=$(mktemp) || return 1
- if ! cp "${REPO_ROOT}/${SWIFT_VERSION_FILE}" "$swift_backup"; then
- rm -f "$swift_backup"
- return 1
- fi
+ if [[ "$component" == "sdk-swift" ]] && ! command -v cargo > /dev/null
2>&1; then
+ echo -e "${RED}sdk-swift needs cargo to regenerate
${SWIFT_GOLDEN_FIXTURE}${NC}" >&2
+ return 1
fi
local changed_files=()
@@ -628,18 +606,20 @@ cmd_bump() {
fi
done < <(get_version_files "$component")
- if [[ -n "$swift_backup" ]]; then
- if [[ ${#changed_files[@]} -gt 0 ]]; then
- echo " Regenerating ${SWIFT_GOLDEN_FIXTURE}"
- if ! regenerate_swift_golden; then
- cp "$swift_backup" "${REPO_ROOT}/${SWIFT_VERSION_FILE}"
- rm -f "$swift_backup"
+ # Swift golden vectors embed the SDK version. A failed generator run
leaves the fixture untouched.
+ if [[ "$component" == "sdk-swift" && ${#changed_files[@]} -gt 0 ]]; then
+ printf " %-55s %s\n" "$SWIFT_GOLDEN_FIXTURE" "regenerated"
+ changed_files+=("$SWIFT_GOLDEN_FIXTURE")
+ if [[ $apply -eq 1 ]]; then
+ trap 'write_version "$SWIFT_VERSION_FILE" swift "$current"; exit
130' INT TERM
+ if ! cargo run --quiet --manifest-path
"${REPO_ROOT}/${SWIFT_GOLDEN_GENERATOR}" --
"${REPO_ROOT}/${SWIFT_GOLDEN_FIXTURE}"; then
+ trap - INT TERM
+ write_version "$SWIFT_VERSION_FILE" swift "$current"
echo -e "${RED}Golden vector generation failed, restored
${SWIFT_VERSION_FILE}${NC}" >&2
return 1
fi
- changed_files+=("$SWIFT_GOLDEN_FIXTURE")
+ trap - INT TERM
fi
- rm -f "$swift_backup"
fi
echo ""