7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZOZOAdvent Calendar 2024

Day 19

GitHub ActionsでC++ライブラリをXCFrameworkにビルドしSwift Packageとして配布する

Last updated at Posted at 2024-12-18

はじめに

この記事ではCMakeで管理されているC++のライブラリをXCFrameworkにビルドし、Swift Pacakgeとして配布する一連の流れをGitHub Actionsで自動化するサンプルを紹介します。流れはざっくり以下です。

  1. C++ライブラリのリポジトリにタグを打つ
  2. 上記をトリガーにSwift Pacakge側のリポジトリのワークフローをトリガーする
    1. C++ライブラリをXCFrameworkにビルドする
    2. そのXCFrameworkをGitHub Release Assetsにアップロードする
    3. 上記のXCFrameworkを参照するようにPackage.swiftを更新しPRを作成する

最初からC++ライブラリにPackage.swiftをおけばいいのでは?

今回のようなシンプルな例だとそれでもいいのですが、実際のプロジェクトに後からPackage.swiftを置こうとすると、C++側の依存の解決やビルドが通らなかったり、色々めんどくさいことあったのでこのような構成になっています。

今回利用するサンプルコード

C++ライブラリ

構造は以下のようになっています。足し算をするadd関数が存在するだけの簡単なプロジェクトです。タグが打たれるとSwift Package側にイベントをdispatchします。

.
├── .github
│   └── workflows
│       └── dispatch.yml
├── CMakeLists.txt
├── build_xcframework.sh
├── include
│   ├── module.modulemap
│   └── sample.h
└── source
    └── sample.cpp

Swift Package

ビルドしたXCFrameworkはGitHub Release AssetsにアップロードされbinaryTargetで取得しています。また、Privateリポジトリにも対応できるようにURLはgithub.comではなくapi.github.comとしています。1

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let zipUrl = "https://api.github.com/repos/NakaokaRei/SwiftPackageXCFrameworkSample/releases/assets/xxxxxxxx.zip"
let checksum = "xxxxxxxxxxxx"

let package = Package(
    name: "SwiftPackageXCFrameworkSample",
    products: [
        .library(
            name: "SwiftPackageXCFrameworkSample",
            targets: ["CxxLibSample"]
        ),
    ],
    targets: [
        .binaryTarget(name: "CxxLibSample", url: zipUrl, checksum: checksum),
        .testTarget(
            name: "SwiftPackageXCFrameworkSampleTests",
            dependencies: ["CxxLibSample"],
            swiftSettings: [.interoperabilityMode(.Cxx)]
        ),
    ],
    cLanguageStandard: .c11,
    cxxLanguageStandard: .cxx20
)

C++ライブラリのリポジトリからSwift Package側にイベントをdispatchする

GitHub ActionsのAPIを使い別のリポジトリにイベントをdispatchします。

以下のようにタグがプッシュされた時に上記のAPIを叩きます。

(C++ライブラリ側) dispatch.yml
name: Trigger Release Workflow

on:
  push:
    tags:
      - '*.*.*'

jobs:
  trigger_workflow:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger Release Workflow
        env:
          PAT: ${{ secrets.PAT_GITHUB }}
          EVENT_TYPE: release-cxxlibsample
          TAG_NAME: ${{ github.ref }}
        run: |
          run: |
          curl -X POST \
            -H "Accept: application/vnd.github+json" \
            -H "Authorization: Bearer $PAT" \
            https://api.github.com/repos/NakaokaRei/SwiftPackageXCFrameworkSample/dispatches \
            -d '{"event_type": "'"$EVENT_TYPE"'", "client_payload": {"tag": "'"$TAG_NAME"'"}}'

C++のライブラリをXCFrameworkとしてビルドする

CMakeで静的ライブラリのターゲットが定義されているので、cmake --buildで静的バイナリをビルドしその後xcodebuild -create-xcframeworkでXCFrameworkを生成します。xcframeworkを生成する全体のスクリプトは以下から確認できます。

CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(CxxLibSample VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED YES)

add_library(CxxLibSample STATIC
    source/sample.cpp
    include/sample.h
)

target_include_directories(CxxLibSample PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

XCFrameworkをGitHub Release AssetsにアップロードしPackage.swiftを更新する

以下のGitHub Actionsでcreate_release以降のステップになります。

(Swift Package側) release.yml
name: Release Swift Package

on:
  repository_dispatch:
    types: [release-cxxlibsample]

jobs:
  build_and_release:
    runs-on: macos-latest

    steps:
      - name: Checkout SwiftPackageXCFrameworkSample Repository
        uses: actions/checkout@v3

      - name: Set up environment variables
        run: |
          echo "CLONE_REPO_URL=https://github.com/NakaokaRei/CxxLibSample.git" >> $GITHUB_ENV
          echo "CLONE_REPO_DIR=CxxLibSample" >> $GITHUB_ENV
          echo "BUILD_SCRIPT=build_xcframework.sh" >> $GITHUB_ENV
          echo "XCFRAMEWORK_DIR=build/xcframework" >> $GITHUB_ENV
          echo "SWIFT_PACKAGE_DIR=." >> $GITHUB_ENV

      - name: Extract Tag Name
        id: extract_tag
        run: |
          TAG_REF="${{ github.event.client_payload.tag }}"  # "refs/tags/1.0.0"
          TAG_NAME="${TAG_REF#refs/tags/}"  # "1.0.0"
          echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT

      - name: Clone CxxLibSample Repository
        run: |
          git clone $CLONE_REPO_URL $CLONE_REPO_DIR

      - name: Make build_xcframework.sh executable
        run: chmod +x $CLONE_REPO_DIR/$BUILD_SCRIPT

      - name: Run build_xcframework.sh
        run: |
          cd $CLONE_REPO_DIR
          ./build_xcframework.sh

      - name: Verify XCFramework
        run: |
          if [ ! -d "$CLONE_REPO_DIR/$XCFRAMEWORK_DIR/CxxLibSample.xcframework" ]; then
            echo "Error: XCFramework not found at $CLONE_REPO_DIR/$XCFRAMEWORK_DIR/CxxLibSample.xcframework"
            exit 1
          fi

      - name: Archive XCFramework
        run: |
          TAG_NAME="${{ steps.extract_tag.outputs.tag_name }}"
          XCFRAMEWORK_PATH="$CLONE_REPO_DIR/$XCFRAMEWORK_DIR/CxxLibSample.xcframework"
          ZIP_PATH="build/CxxLibSample.xcframework-${TAG_NAME}.zip"
          mkdir -p build
          ditto -c -k --sequesterRsrc --keepParent "$XCFRAMEWORK_PATH" "$ZIP_PATH"

      - name: Upload XCFramework to GitHub Releases
        id: create_release
        uses: softprops/action-gh-release@v2
        with:
          files: build/CxxLibSample.xcframework-${{ steps.extract_tag.outputs.tag_name }}.zip
          tag_name: ${{ steps.extract_tag.outputs.tag_name }}
          name: ${{ steps.extract_tag.outputs.tag_name }}
          draft: true
          token: ${{ secrets.PAT_GITHUB }}

      - name: Get Release Asset URL and Checksum
        id: get_assets
        env:
          GH_TOKEN: ${{ secrets.PAT_GITHUB }} 
        run: |
          TAG_NAME="${{ steps.extract_tag.outputs.tag_name }}"
          ASSET_URL=$(echo '${{ steps.create_release.outputs.assets }}' | jq -r '.[] | select(.name == "CxxLibSample.xcframework-'${TAG_NAME}'.zip").url')
          echo "zipUrl=${ASSET_URL}" >> $GITHUB_ENV

          CHECKSUM=$(shasum -a 256 build/CxxLibSample.xcframework-${TAG_NAME}.zip | awk '{print $1}')
          echo "checksum=${CHECKSUM}" >> $GITHUB_ENV

          echo "zipUrl=${ASSET_URL}" >> $GITHUB_OUTPUT
          echo "checksum=${CHECKSUM}" >> $GITHUB_OUTPUT

      - name: Update Package.swift
        run: |
          sed -i '' 's#let zipUrl = ".*"#let zipUrl = "${{ steps.get_assets.outputs.zipUrl }}.zip"#' Package.swift
          sed -i '' 's#let checksum = ".*"#let checksum = "${{ steps.get_assets.outputs.checksum }}"#' Package.swift

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v6
        with:
          token: ${{ secrets.PAT_GITHUB }}
          branch: update-package-swift-${{ steps.extract_tag.outputs.tag_name }}
          commit-message: "chore: update Package.swift for version ${{ steps.extract_tag.outputs.tag_name }}"
          title: "chore: update Package.swift for version ${{ steps.extract_tag.outputs.tag_name }}"
          body: |
            This PR updates `Package.swift` to use version ${{ steps.extract_tag.outputs.tag_name }} of `CxxLibSample.xcframework`.
          base: master
          labels: 'release'
          add-paths: Package.swift

ワークフローが完了すると以下のようなPRが作成されるのでこれをマージして完了となります。

終わりに

本記事ではC++ライブラリをSwift Packageとして配布するための自動化方法について紹介しました。参考になれば幸いです。

  1. プライベートリポジトリのRelease AssetsにXCFrameworkをホストして、Swift Package Manager・CocoaPodsで配布する

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?