Skip to content

Commit e83842e

Browse files
committed
feat: build kit xcframeworks from SPM packages instead of Xcode projects
Remove kit-level .xcodeproj files and tvOS-specific directories, replacing the xcodeproj-based xcframework build with a script that archives directly from each kit's Package.swift using xcodebuild -skipPackagePluginValidation. - Add Scripts/build_kit_xcframework.sh for SPM-based xcframework generation - Simplify Kits/matrix.json to scheme + module + platforms[] per kit - Update release-publish.yml and build-kits.yml to use the new format - Update all 29 kit Package.swift files with BUILD_XCFRAMEWORK dynamic type and explicit package identity for local path dependencies - Delete 29 kit .xcodeproj directories and 5 tvOS header directories - Update KIT_XCFRAMEWORK_RELEASE_WORKFLOW.md to document the new approach
1 parent 6a56e49 commit e83842e

File tree

110 files changed

+489
-16061
lines changed

Some content is hidden

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

110 files changed

+489
-16061
lines changed

.github/workflows/build-kits.yml

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,32 +78,30 @@ jobs:
7878
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
7979

8080
- name: Resolve SPM dependencies
81+
working-directory: ${{ matrix.kit.local_path }}
8182
run: |
82-
SCHEME=$(echo '${{ toJson(matrix.kit.schemes) }}' | jq -r '.[0].scheme')
83-
PROJECT="$(ls -d ${{ matrix.kit.local_path }}/*.xcodeproj | head -1)"
83+
xcodebuild -resolvePackageDependencies \
84+
-skipPackagePluginValidation \
85+
-scheme "${{ matrix.kit.scheme }}" \
86+
-derivedDataPath DerivedData
8487
85-
xcodebuild -resolvePackageDependencies -project "$PROJECT" \
86-
-scheme "$SCHEME" -derivedDataPath DerivedData
87-
88-
- name: Build kit schemes
88+
- name: Build kit for each platform
89+
working-directory: ${{ matrix.kit.local_path }}
8990
run: |
90-
echo '${{ toJson(matrix.kit.schemes) }}' | jq -c '.[]' | while IFS= read -r ENTRY; do
91-
SCHEME=$(echo "$ENTRY" | jq -r '.scheme')
92-
DEST=$(echo "$ENTRY" | jq -r '.destination')
93-
94-
if [ -d "${{ matrix.kit.local_path }}/$SCHEME.xcodeproj" ]; then
95-
PROJECT="${{ matrix.kit.local_path }}/$SCHEME.xcodeproj"
96-
else
97-
PROJECT="$(ls -d ${{ matrix.kit.local_path }}/*.xcodeproj | head -1)"
98-
fi
99-
100-
xcodebuild build -project "$PROJECT" -scheme "$SCHEME" \
101-
-destination "generic/platform=$DEST" \
91+
PLATFORMS_JSON='${{ toJson(matrix.kit.platforms) }}'
92+
echo "$PLATFORMS_JSON" | jq -r '.[]' | while IFS= read -r PLATFORM; do
93+
echo "==> Building ${{ matrix.kit.scheme }} for $PLATFORM..."
94+
xcodebuild build \
95+
-skipPackagePluginValidation \
96+
-scheme "${{ matrix.kit.scheme }}" \
97+
-destination "generic/platform=$PLATFORM" \
10298
-derivedDataPath DerivedData \
10399
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
104100
105-
xcodebuild build -project "$PROJECT" -scheme "$SCHEME" \
106-
-destination "generic/platform=$DEST Simulator" \
101+
xcodebuild build \
102+
-skipPackagePluginValidation \
103+
-scheme "${{ matrix.kit.scheme }}" \
104+
-destination "generic/platform=$PLATFORM Simulator" \
107105
-derivedDataPath DerivedData \
108106
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
109107
done
@@ -125,18 +123,11 @@ jobs:
125123
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
126124
127125
- name: Run SPM tests
126+
working-directory: ${{ matrix.kit.local_path }}
128127
run: |
129-
cd ${{ matrix.kit.local_path }}
130-
PACKAGE=$(grep -E '^\s*name:' Package.swift | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
131-
LIB_COUNT=$(grep -c '\.library' Package.swift || true)
132-
if [ "$LIB_COUNT" -gt 1 ]; then
133-
SCHEME="${PACKAGE}-Package"
134-
else
135-
SCHEME="$PACKAGE"
136-
fi
137-
for xc in *.xcodeproj; do [ -d "$xc" ] && mv "$xc" "${xc}.bak"; done
138-
trap 'for xc in *.xcodeproj.bak; do [ -d "$xc" ] && mv "$xc" "${xc%.bak}"; done' EXIT
139-
xcodebuild test -scheme "$SCHEME" \
128+
xcodebuild test \
129+
-skipPackagePluginValidation \
130+
-scheme "${{ matrix.kit.scheme }}" \
140131
-destination "platform=iOS Simulator,name=iPhone 16" \
141132
-derivedDataPath DerivedData-Test \
142133
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

.github/workflows/release-publish.yml

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -140,64 +140,22 @@ jobs:
140140
fetch-depth: 0
141141

142142
- name: Select Xcode
143-
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
143+
run: |
144+
XCODE="${{ matrix.kit.xcode_version || env.XCODE_VERSION }}"
145+
sudo xcode-select -s "/Applications/Xcode_${XCODE}.app"
144146
145147
- name: Build kit xcframeworks
146148
shell: bash
147149
run: |
148-
SCHEMES_JSON='${{ toJson(matrix.kit.schemes) }}'
149-
LOCAL_PATH="${{ matrix.kit.local_path }}"
150-
BUILD_SETTINGS="CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES"
151-
152-
mkdir -p archives xcframeworks
153-
154-
MODULES=$(echo "$SCHEMES_JSON" | jq -r '.[].module' | sort -u)
155-
156-
for MODULE in $MODULES; do
157-
XCFRAMEWORK_ARGS=""
158-
159-
while IFS= read -r ENTRY; do
160-
SCHEME=$(echo "$ENTRY" | jq -r '.scheme')
161-
DEST=$(echo "$ENTRY" | jq -r '.destination')
162-
163-
if [ -d "$LOCAL_PATH/$SCHEME.xcodeproj" ]; then
164-
PROJECT="$LOCAL_PATH/$SCHEME.xcodeproj"
165-
else
166-
PROJECT="$(ls -d "$LOCAL_PATH"/*.xcodeproj | head -1)"
167-
fi
168-
169-
ARCHIVE_DEVICE="archives/${MODULE}-${DEST}"
170-
ARCHIVE_SIM="archives/${MODULE}-${DEST}_Simulator"
171-
172-
case "$DEST" in
173-
iOS)
174-
PLATFORM_DEVICE="generic/platform=iOS"
175-
PLATFORM_SIM="generic/platform=iOS Simulator"
176-
;;
177-
tvOS)
178-
PLATFORM_DEVICE="generic/platform=tvOS"
179-
PLATFORM_SIM="generic/platform=tvOS Simulator"
180-
;;
181-
*)
182-
echo "::error::Unknown destination: $DEST"
183-
exit 1
184-
;;
185-
esac
186-
187-
xcodebuild archive -project "$PROJECT" -scheme "$SCHEME" \
188-
-destination "$PLATFORM_DEVICE" -archivePath "$ARCHIVE_DEVICE" $BUILD_SETTINGS
189-
xcodebuild archive -project "$PROJECT" -scheme "$SCHEME" \
190-
-destination "$PLATFORM_SIM" -archivePath "$ARCHIVE_SIM" $BUILD_SETTINGS
191-
192-
XCFRAMEWORK_ARGS+=" -archive ${ARCHIVE_DEVICE}.xcarchive -framework ${MODULE}.framework"
193-
XCFRAMEWORK_ARGS+=" -archive ${ARCHIVE_SIM}.xcarchive -framework ${MODULE}.framework"
194-
done < <(echo "$SCHEMES_JSON" | jq -c ".[] | select(.module == \"$MODULE\")")
195-
196-
xcodebuild -create-xcframework $XCFRAMEWORK_ARGS -output "xcframeworks/${MODULE}.xcframework"
197-
(cd xcframeworks && zip -r "${MODULE}.xcframework.zip" "${MODULE}.xcframework" && rm -rf "${MODULE}.xcframework")
198-
done
199-
200-
rm -rf archives
150+
PLATFORMS_JSON='${{ toJson(matrix.kit.platforms) }}'
151+
PLATFORMS=$(echo "$PLATFORMS_JSON" | jq -r '.[]' | paste -sd ',' -)
152+
153+
./Scripts/build_kit_xcframework.sh \
154+
--path "${{ matrix.kit.local_path }}" \
155+
--scheme "${{ matrix.kit.scheme }}" \
156+
--module "${{ matrix.kit.module }}" \
157+
--platforms "$PLATFORMS" \
158+
--output xcframeworks
201159
202160
- name: Upload xcframework artifacts
203161
uses: actions/upload-artifact@v4
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# How Kit XCFrameworks Are Generated in Release Workflows
2+
3+
## Summary
4+
5+
Kit XCFrameworks are generated by the `Release – Publish` GitHub Actions workflow in `.github/workflows/release-publish.yml`.
6+
7+
They are not built by the core SDK scripts in `Scripts/make_artifacts.sh` or `Scripts/xcframework.sh`. Those scripts are used only for the core `mParticle_Apple_SDK.xcframework.zip` artifact.
8+
9+
For kits, the workflow calls `Scripts/build_kit_xcframework.sh` which builds each kit directly from its `Package.swift` (SPM) — no Xcode project files are needed. The script archives device and simulator variants for each platform, post-processes headers and module maps into the framework bundles, combines them with `xcodebuild -create-xcframework`, and zips the result.
10+
11+
## Release Flow
12+
13+
```mermaid
14+
flowchart LR
15+
A[Kits/matrix.json] --> B[release-publish.yml: load-matrix]
16+
B --> C[build-kits job]
17+
C --> D[build_kit_xcframework.sh per kit]
18+
D --> E[xcodebuild archive via SPM per platform]
19+
E --> F[post-process: copy headers + module map]
20+
F --> G[xcodebuild -create-xcframework]
21+
G --> H[zip module.xcframework.zip]
22+
H --> I[upload artifact]
23+
I --> J[mirror-and-release-kits]
24+
J --> K[GitHub release in mirrored kit repo]
25+
```
26+
27+
## Key Details
28+
29+
### Build Script: `Scripts/build_kit_xcframework.sh`
30+
31+
The reusable script takes five arguments:
32+
33+
```bash
34+
./Scripts/build_kit_xcframework.sh \
35+
--path Kits/braze/braze-12 \
36+
--scheme mParticle-Braze \
37+
--module mParticle_Braze \
38+
--platforms iOS,tvOS \
39+
--output xcframeworks/
40+
```
41+
42+
For each platform (iOS, tvOS), it:
43+
44+
1. Runs `xcodebuild archive -skipPackagePluginValidation` from the kit directory (where `Package.swift` lives)
45+
2. Archives both device and simulator variants
46+
3. Post-processes each archive to copy public headers and generate a `module.modulemap` into the framework bundle
47+
4. Combines all archives into a single xcframework via `xcodebuild -create-xcframework`
48+
5. Zips the result
49+
50+
Important build settings:
51+
52+
- `CODE_SIGN_IDENTITY=`, `CODE_SIGNING_REQUIRED=NO`, `CODE_SIGNING_ALLOWED=NO`
53+
- `SKIP_INSTALL=NO`, `BUILD_LIBRARY_FOR_DISTRIBUTION=YES`
54+
- `INSTALL_PATH=$(LOCAL_LIBRARY_DIR)/Frameworks`
55+
56+
The script also exports `BUILD_XCFRAMEWORK=1`, which signals each kit's `Package.swift` to use `.dynamic` library type (required for framework output from SPM).
57+
58+
### Kit Metadata: `Kits/matrix.json`
59+
60+
Each kit entry defines:
61+
62+
```json
63+
{
64+
"name": "braze-12",
65+
"local_path": "Kits/braze/braze-12",
66+
"scheme": "mParticle-Braze",
67+
"module": "mParticle_Braze",
68+
"platforms": ["iOS", "tvOS"],
69+
"podspec": "Kits/braze/braze-12/mParticle-Braze-12.podspec",
70+
"dest_repo": "mparticle-apple-integration-braze-12",
71+
"xcode_version": "16.4"
72+
}
73+
```
74+
75+
- `scheme`: The SPM product name (auto-generates an xcodebuild scheme)
76+
- `module`: The framework module name (hyphens → underscores)
77+
- `platforms`: Which platforms to build for — the same scheme is used for all platforms
78+
- `xcode_version`: Optional per-kit Xcode version override
79+
80+
### Package.swift Configuration
81+
82+
Each kit's `Package.swift` uses two environment variables:
83+
84+
- `USE_LOCAL_VERSION=1` — Resolves the core SDK dependency from the monorepo checkout (set by the workflow)
85+
- `BUILD_XCFRAMEWORK=1` — Switches the library product to `.dynamic` type (set by the build script)
86+
87+
```swift
88+
let useLocalVersion = ProcessInfo.processInfo.environment["USE_LOCAL_VERSION"] != nil
89+
let buildXCFramework = ProcessInfo.processInfo.environment["BUILD_XCFRAMEWORK"] != nil
90+
91+
// Local dependency uses explicit name for identity resolution
92+
let mParticleAppleSDK: Package.Dependency = {
93+
if useLocalVersion {
94+
return .package(name: "mparticle-apple-sdk", path: "../../../")
95+
}
96+
// ...remote dependency for non-local builds
97+
}()
98+
99+
// Dynamic type required for xcframework generation
100+
.library(
101+
name: "mParticle-Braze",
102+
type: buildXCFramework ? .dynamic : nil,
103+
targets: ["mParticle-Braze"]
104+
)
105+
```
106+
107+
## Multi-Platform Kits
108+
109+
Kits that support both iOS and tvOS (e.g., Braze, ComScore, FirebaseGA4) use the **same SPM scheme** for both platforms. The build script simply passes different `-destination` flags:
110+
111+
- `generic/platform=iOS` / `generic/platform=iOS Simulator`
112+
- `generic/platform=tvOS` / `generic/platform=tvOS Simulator`
113+
114+
All four archives are combined into a single xcframework containing slices for both platforms.
115+
116+
## Core SDK vs Kit Builds
117+
118+
### Core SDK
119+
120+
The core SDK release job uses `Scripts/make_artifacts.sh` and `Scripts/xcframework.sh` to create `mParticle_Apple_SDK.xcframework.zip`.
121+
122+
### Kits
123+
124+
Kit xcframeworks are built separately inside `Scripts/build_kit_xcframework.sh`, called from the `build-kits` job in `.github/workflows/release-publish.yml`.
125+
126+
## Publishing
127+
128+
After the kit xcframework zips are created:
129+
130+
1. The workflow uploads them as artifacts
131+
2. The `mirror-and-release-kits` job downloads them
132+
3. The workflow mirrors the kit subtree to its destination repo under `mparticle-integrations/`
133+
4. It creates a GitHub release in the mirrored kit repo
134+
5. It attaches the generated `*.xcframework.zip` files to that release
135+
136+
These releases are what SPM consumers use for kit binary distribution.

Kits/adjust/adjust-5/Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import PackageDescription
77
let version = "9.0.0"
88

99
let useLocalVersion = ProcessInfo.processInfo.environment["USE_LOCAL_VERSION"] != nil
10+
let buildXCFramework = ProcessInfo.processInfo.environment["BUILD_XCFRAMEWORK"] != nil
1011

1112
let mParticleAppleSDK: Package.Dependency = {
1213
if useLocalVersion {
13-
return .package(path: "../../../")
14+
return .package(name: "mparticle-apple-sdk", path: "../../../")
1415
}
1516

1617
let url = "https://github.com/mParticle/mparticle-apple-sdk"
@@ -26,6 +27,7 @@ let package = Package(
2627
products: [
2728
.library(
2829
name: "mParticle-Adjust",
30+
type: buildXCFramework ? .dynamic : nil,
2931
targets: ["mParticle-Adjust"]
3032
)
3133
],

0 commit comments

Comments
 (0)