Analyzing a SPM Framework (iOS)

Due to technical and standardization requirements, Emerge Tools only supports analyzing frameworks packaged as xcframeworks. If you want to analyze your Swift Package Manager (SPM) framework, the easiest way is to build it using the script provided below:

#!/bin/bash

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd -P)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"

PROJECT_BUILD_DIR="${PROJECT_BUILD_DIR:-"${PROJECT_ROOT}/build"}"
XCODEBUILD_BUILD_DIR="$PROJECT_BUILD_DIR/xcodebuild"
XCODEBUILD_DERIVED_DATA_PATH="$XCODEBUILD_BUILD_DIR/DerivedData"

PACKAGE_NAME=$1
if [ -z "$PACKAGE_NAME" ]; then
    echo "No package name provided. Using the first scheme found in the Package.swift."
    PACKAGE_NAME=$(xcodebuild -list | awk 'schemes && NF>0 { print $1; exit } /Schemes:$/ { schemes = 1 }')
    echo "Using: $PACKAGE_NAME"
fi

backup_package_swift() {
    cp Package.swift Package.swift.bak
}

restore_package_swift() {
    mv Package.swift.bak Package.swift
}

modify_package_swift() {
    sed -i '' 's/type: .static,//g' Package.swift
    sed -i '' 's/type: .dynamic,//g' Package.swift
    sed -i '' -e ':a' -e 'N' -e '$!ba' -e 's/\(library[^,]*name: [^,]*,\)/\1 type: .dynamic,/g' Package.swift
}

build_framework() {
    local sdk="$1"
    local destination="$2"
    local scheme="$3"

    local XCODEBUILD_ARCHIVE_PATH="./$scheme-$sdk.xcarchive"

    rm -rf "$XCODEBUILD_ARCHIVE_PATH"

    xcodebuild archive \
        -scheme $scheme \
        -archivePath $XCODEBUILD_ARCHIVE_PATH \
        -derivedDataPath "$XCODEBUILD_DERIVED_DATA_PATH" \
        -sdk "$sdk" \
        -destination "$destination" \
        BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
        INSTALL_PATH='Library/Frameworks' \
        OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface

    FRAMEWORK_MODULES_PATH="$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework/Modules"
    mkdir -p "$FRAMEWORK_MODULES_PATH"
    cp -r \
    "$XCODEBUILD_DERIVED_DATA_PATH/Build/Intermediates.noindex/ArchiveIntermediates/$scheme/BuildProductsPath/Release-$sdk/$scheme.swiftmodule" \
    "$FRAMEWORK_MODULES_PATH/$scheme.swiftmodule"
    # Delete private swiftinterface
    rm -f "$FRAMEWORK_MODULES_PATH/$scheme.swiftmodule/*.private.swiftinterface"
}

echo "Modifying Package.swift"
backup_package_swift
modify_package_swift

build_framework "iphonesimulator" "generic/platform=iOS Simulator" "$PACKAGE_NAME"
build_framework "iphoneos" "generic/platform=iOS" "$PACKAGE_NAME"

echo "Builds completed successfully."

rm -rf "$PACKAGE_NAME.xcframework"
xcodebuild -create-xcframework -framework $PACKAGE_NAME-iphonesimulator.xcarchive/Products/Library/Frameworks/$PACKAGE_NAME.framework -framework $PACKAGE_NAME-iphoneos.xcarchive/Products/Library/Frameworks/$PACKAGE_NAME.framework -output $PACKAGE_NAME.xcframework

cp -r $PACKAGE_NAME-iphonesimulator.xcarchive/dSYMs $PACKAGE_NAME.xcframework/ios-arm64_x86_64-simulator
cp -r $PACKAGE_NAME-iphoneos.xcarchive/dSYMs $PACKAGE_NAME.xcframework/ios-arm64

zip -r "$PACKAGE_NAME.xcframework.zip" "$PACKAGE_NAME.xcframework"

echo "Restoring Package.swift"
restore_package_swift

This script will automatically detect the first product listed inside your Package.swift file. If your package contains multiple products, you can specify the desired product’s name as an argument:

sh buildXCFrameworkArchive.sh MY_PRODUCT_NAME

After running the script, you will have an xcframework located at the root of your project, named MY_PRODUCT_NAME.xcframework.


Example GitHub Actions Workflow

Github does provide 200 minutes of macOS runners for free, you can use them to build and upload your framework when generating a new version, like this:

  1. Create a new file called buildXCFramework.sh at the root of your project and add the previously shared script.
  2. Create a new file at .github/workflows/release.yml and add the following content:
name: Build
on:
  push:
    branches: [main]
jobs:
  build:
    name: Build XCFramework
    runs-on: macos-latest
    env:
      PRODUCT_NAME: MY_PRODUCT_NAME
    steps:
      - uses: actions/checkout@v4
      - name: Setup Xcode
        uses: maxim-lobanov/setup-xcode@v1
        with:
          xcode-version: latest-stable
      - name: Build XCFramework
        run: sh build.sh $PRODUCT_NAME
      - name: Upload artifact to Emerge
        uses: EmergeTools/[email protected]
        with:
          build_type: release
          artifact_path: ./build/${{ env.PRODUCT_NAME }}.xcframework.zip
          emerge_api_key: ${{ secrets.EMERGE_API_KEY }}
  1. Replace MY_PRODUCT_NAME in the workflow with the actual name of your package.
  2. Obtain your Emerge Tools API key. Follow the instructions in Uploading Basics to get your API key.
  3. Set the EMERGE_API_KEY as a secret in your GitHub repository settings.

Now, whenever a new commit is pushed to the main branch, the workflow will automatically build and upload the new framework version to Emerge Tools.