Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 22 additions & 31 deletions .github/workflows/build-kits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,32 +77,30 @@ jobs:
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app

- name: Resolve SPM dependencies
working-directory: ${{ matrix.kit.local_path }}
run: |
SCHEME=$(echo '${{ toJson(matrix.kit.schemes) }}' | jq -r '.[0].scheme')
PROJECT="$(ls -d ${{ matrix.kit.local_path }}/*.xcodeproj | head -1)"
xcodebuild -resolvePackageDependencies \
-skipPackagePluginValidation \
-scheme "${{ matrix.kit.scheme }}" \
-derivedDataPath DerivedData

xcodebuild -resolvePackageDependencies -project "$PROJECT" \
-scheme "$SCHEME" -derivedDataPath DerivedData

- name: Build kit schemes
- name: Build kit for each platform
working-directory: ${{ matrix.kit.local_path }}
run: |
echo '${{ toJson(matrix.kit.schemes) }}' | jq -c '.[]' | while IFS= read -r ENTRY; do
SCHEME=$(echo "$ENTRY" | jq -r '.scheme')
DEST=$(echo "$ENTRY" | jq -r '.destination')

if [ -d "${{ matrix.kit.local_path }}/$SCHEME.xcodeproj" ]; then
PROJECT="${{ matrix.kit.local_path }}/$SCHEME.xcodeproj"
else
PROJECT="$(ls -d ${{ matrix.kit.local_path }}/*.xcodeproj | head -1)"
fi

xcodebuild build -project "$PROJECT" -scheme "$SCHEME" \
-destination "generic/platform=$DEST" \
PLATFORMS_JSON='${{ toJson(matrix.kit.platforms) }}'
echo "$PLATFORMS_JSON" | jq -r '.[]' | while IFS= read -r PLATFORM; do
echo "==> Building ${{ matrix.kit.scheme }} for $PLATFORM..."
xcodebuild build \
-skipPackagePluginValidation \
-scheme "${{ matrix.kit.scheme }}" \
-destination "generic/platform=$PLATFORM" \
-derivedDataPath DerivedData \
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

xcodebuild build -project "$PROJECT" -scheme "$SCHEME" \
-destination "generic/platform=$DEST Simulator" \
xcodebuild build \
-skipPackagePluginValidation \
-scheme "${{ matrix.kit.scheme }}" \
-destination "generic/platform=$PLATFORM Simulator" \
-derivedDataPath DerivedData \
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
done
Expand All @@ -124,18 +122,11 @@ jobs:
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

- name: Run SPM tests
working-directory: ${{ matrix.kit.local_path }}
run: |
cd ${{ matrix.kit.local_path }}
PACKAGE=$(grep -E '^\s*name:' Package.swift | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
LIB_COUNT=$(grep -c '\.library' Package.swift || true)
if [ "$LIB_COUNT" -gt 1 ]; then
SCHEME="${PACKAGE}-Package"
else
SCHEME="$PACKAGE"
fi
for xc in *.xcodeproj; do [ -d "$xc" ] && mv "$xc" "${xc}.bak"; done
trap 'for xc in *.xcodeproj.bak; do [ -d "$xc" ] && mv "$xc" "${xc%.bak}"; done' EXIT
xcodebuild test -scheme "$SCHEME" \
xcodebuild test \
-skipPackagePluginValidation \
-scheme "${{ matrix.kit.scheme }}" \
-destination "platform=iOS Simulator,name=iPhone 16" \
-derivedDataPath DerivedData-Test \
CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
66 changes: 12 additions & 54 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,64 +146,22 @@ jobs:
fetch-depth: 0

- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
run: |
XCODE="${{ matrix.kit.xcode_version || env.XCODE_VERSION }}"
sudo xcode-select -s "/Applications/Xcode_${XCODE}.app"

- name: Build kit xcframeworks
shell: bash
run: |
SCHEMES_JSON='${{ toJson(matrix.kit.schemes) }}'
LOCAL_PATH="${{ matrix.kit.local_path }}"
BUILD_SETTINGS="CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES"

mkdir -p archives xcframeworks

MODULES=$(echo "$SCHEMES_JSON" | jq -r '.[].module' | sort -u)

for MODULE in $MODULES; do
XCFRAMEWORK_ARGS=""

while IFS= read -r ENTRY; do
SCHEME=$(echo "$ENTRY" | jq -r '.scheme')
DEST=$(echo "$ENTRY" | jq -r '.destination')

if [ -d "$LOCAL_PATH/$SCHEME.xcodeproj" ]; then
PROJECT="$LOCAL_PATH/$SCHEME.xcodeproj"
else
PROJECT="$(ls -d "$LOCAL_PATH"/*.xcodeproj | head -1)"
fi

ARCHIVE_DEVICE="archives/${MODULE}-${DEST}"
ARCHIVE_SIM="archives/${MODULE}-${DEST}_Simulator"

case "$DEST" in
iOS)
PLATFORM_DEVICE="generic/platform=iOS"
PLATFORM_SIM="generic/platform=iOS Simulator"
;;
tvOS)
PLATFORM_DEVICE="generic/platform=tvOS"
PLATFORM_SIM="generic/platform=tvOS Simulator"
;;
*)
echo "::error::Unknown destination: $DEST"
exit 1
;;
esac

xcodebuild archive -project "$PROJECT" -scheme "$SCHEME" \
-destination "$PLATFORM_DEVICE" -archivePath "$ARCHIVE_DEVICE" $BUILD_SETTINGS
xcodebuild archive -project "$PROJECT" -scheme "$SCHEME" \
-destination "$PLATFORM_SIM" -archivePath "$ARCHIVE_SIM" $BUILD_SETTINGS

XCFRAMEWORK_ARGS+=" -archive ${ARCHIVE_DEVICE}.xcarchive -framework ${MODULE}.framework"
XCFRAMEWORK_ARGS+=" -archive ${ARCHIVE_SIM}.xcarchive -framework ${MODULE}.framework"
done < <(echo "$SCHEMES_JSON" | jq -c ".[] | select(.module == \"$MODULE\")")

xcodebuild -create-xcframework $XCFRAMEWORK_ARGS -output "xcframeworks/${MODULE}.xcframework"
(cd xcframeworks && zip -r "${MODULE}.xcframework.zip" "${MODULE}.xcframework" && rm -rf "${MODULE}.xcframework")
done

rm -rf archives
PLATFORMS_JSON='${{ toJson(matrix.kit.platforms) }}'
PLATFORMS=$(echo "$PLATFORMS_JSON" | jq -r '.[]' | paste -sd ',' -)

./Scripts/build_kit_xcframework.sh \
--path "${{ matrix.kit.local_path }}" \
--scheme "${{ matrix.kit.scheme }}" \
--module "${{ matrix.kit.module }}" \
--platforms "$PLATFORMS" \
--output xcframeworks

- name: Upload xcframework artifacts
uses: actions/upload-artifact@v4
Expand Down
136 changes: 136 additions & 0 deletions KIT_XCFRAMEWORK_RELEASE_WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# How Kit XCFrameworks Are Generated in Release Workflows

## Summary

Kit XCFrameworks are generated by the `Release – Publish` GitHub Actions workflow in `.github/workflows/release-publish.yml`.

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.

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.

## Release Flow

```mermaid
flowchart LR
A[Kits/matrix.json] --> B[release-publish.yml: load-matrix]
B --> C[build-kits job]
C --> D[build_kit_xcframework.sh per kit]
D --> E[xcodebuild archive via SPM per platform]
E --> F[post-process: copy headers + module map]
F --> G[xcodebuild -create-xcframework]
G --> H[zip module.xcframework.zip]
H --> I[upload artifact]
I --> J[mirror-and-release-kits]
J --> K[GitHub release in mirrored kit repo]
```

## Key Details

### Build Script: `Scripts/build_kit_xcframework.sh`

The reusable script takes five arguments:

```bash
./Scripts/build_kit_xcframework.sh \
--path Kits/braze/braze-12 \
--scheme mParticle-Braze \
--module mParticle_Braze \
--platforms iOS,tvOS \
--output xcframeworks/
```

For each platform (iOS, tvOS), it:

1. Runs `xcodebuild archive -skipPackagePluginValidation` from the kit directory (where `Package.swift` lives)
2. Archives both device and simulator variants
3. Post-processes each archive to copy public headers and generate a `module.modulemap` into the framework bundle
4. Combines all archives into a single xcframework via `xcodebuild -create-xcframework`
5. Zips the result

Important build settings:

- `CODE_SIGN_IDENTITY=`, `CODE_SIGNING_REQUIRED=NO`, `CODE_SIGNING_ALLOWED=NO`
- `SKIP_INSTALL=NO`, `BUILD_LIBRARY_FOR_DISTRIBUTION=YES`
- `INSTALL_PATH=$(LOCAL_LIBRARY_DIR)/Frameworks`

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).

### Kit Metadata: `Kits/matrix.json`

Each kit entry defines:

```json
{
"name": "braze-12",
"local_path": "Kits/braze/braze-12",
"scheme": "mParticle-Braze",
"module": "mParticle_Braze",
"platforms": ["iOS", "tvOS"],
"podspec": "Kits/braze/braze-12/mParticle-Braze-12.podspec",
"dest_repo": "mparticle-apple-integration-braze-12",
"xcode_version": "16.4"
}
```

- `scheme`: The SPM product name (auto-generates an xcodebuild scheme)
- `module`: The framework module name (hyphens → underscores)
- `platforms`: Which platforms to build for — the same scheme is used for all platforms
- `xcode_version`: Optional per-kit Xcode version override

### Package.swift Configuration

Each kit's `Package.swift` uses two environment variables:

- `USE_LOCAL_VERSION=1` — Resolves the core SDK dependency from the monorepo checkout (set by the workflow)
- `BUILD_XCFRAMEWORK=1` — Switches the library product to `.dynamic` type (set by the build script)

```swift
let useLocalVersion = ProcessInfo.processInfo.environment["USE_LOCAL_VERSION"] != nil
let buildXCFramework = ProcessInfo.processInfo.environment["BUILD_XCFRAMEWORK"] != nil

// Local dependency uses explicit name for identity resolution
let mParticleAppleSDK: Package.Dependency = {
if useLocalVersion {
return .package(name: "mparticle-apple-sdk", path: "../../../")
}
// ...remote dependency for non-local builds
}()

// Dynamic type required for xcframework generation
.library(
name: "mParticle-Braze",
type: buildXCFramework ? .dynamic : nil,
targets: ["mParticle-Braze"]
)
```

## Multi-Platform Kits

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:

- `generic/platform=iOS` / `generic/platform=iOS Simulator`
- `generic/platform=tvOS` / `generic/platform=tvOS Simulator`

All four archives are combined into a single xcframework containing slices for both platforms.

## Core SDK vs Kit Builds

### Core SDK

The core SDK release job uses `Scripts/make_artifacts.sh` and `Scripts/xcframework.sh` to create `mParticle_Apple_SDK.xcframework.zip`.

### Kits

Kit xcframeworks are built separately inside `Scripts/build_kit_xcframework.sh`, called from the `build-kits` job in `.github/workflows/release-publish.yml`.

## Publishing

After the kit xcframework zips are created:

1. The workflow uploads them as artifacts
2. The `mirror-and-release-kits` job downloads them
3. The workflow mirrors the kit subtree to its destination repo under `mparticle-integrations/`
4. It creates a GitHub release in the mirrored kit repo
5. It attaches the generated `*.xcframework.zip` files to that release

These releases are what SPM consumers use for kit binary distribution.
4 changes: 3 additions & 1 deletion Kits/adjust/adjust-5/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import PackageDescription
let version = ""

let useLocalVersion = ProcessInfo.processInfo.environment["USE_LOCAL_VERSION"] != nil
let buildXCFramework = ProcessInfo.processInfo.environment["BUILD_XCFRAMEWORK"] != nil

let mParticleAppleSDK: Package.Dependency = {
if useLocalVersion {
return .package(path: "../../../")
return .package(name: "mparticle-apple-sdk", path: "../../../")
}

let url = "https://github.com/mParticle/mparticle-apple-sdk"
Expand All @@ -26,6 +27,7 @@ let package = Package(
products: [
.library(
name: "mParticle-Adjust",
type: buildXCFramework ? .dynamic : nil,
targets: ["mParticle-Adjust"]
)
],
Expand Down
Loading
Loading