Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
94 changes: 94 additions & 0 deletions .github/scripts/prepare-pod-build.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
require 'fileutils'
require 'pathname'
require 'xcodeproj'

sdk_path = Pathname.new(ARGV.fetch(0)).expand_path
build_path = Pathname.new(ARGV.fetch(1)).expand_path
FileUtils.mkdir_p(build_path)
project = Xcodeproj::Project.new(build_path.join('PodBuild.xcodeproj'))
app = project.new_target(:application, 'PodBuild', :ios, '15.0')
notification_extension = project.new_target(:app_extension, 'NotificationService', :ios, '15.0')

[app, notification_extension].each do |target|
target.build_configurations.each do |config|
config.build_settings.merge!(
'SWIFT_VERSION' => '5.0',
'GENERATE_INFOPLIST_FILE' => 'YES',
'CURRENT_PROJECT_VERSION' => '1',
'MARKETING_VERSION' => '1.0',
'CODE_SIGNING_ALLOWED' => 'NO',
'PRODUCT_BUNDLE_IDENTIFIER' => "com.simla.PodBuild#{target == app ? '' : '.NotificationService'}"
)
end
end

app.build_configurations.each do |config|
config.build_settings['INFOPLIST_KEY_UILaunchScreen_Generation'] = 'YES'
end
notification_extension.build_configurations.each do |config|
config.build_settings['INFOPLIST_FILE'] = 'NotificationInfo.plist'
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES'
end
Xcodeproj::Plist.write_to_path(
{ 'NSExtension' => {
'NSExtensionPointIdentifier' => 'com.apple.usernotifications.service',
'NSExtensionPrincipalClass' => '$(PRODUCT_MODULE_NAME).NotificationService'
} }, build_path.join('NotificationInfo.plist')
)

app.add_dependency(notification_extension)
embed = app.new_copy_files_build_phase('Embed App Extensions')
embed.dst_subfolder_spec = '13'
embed.add_file_reference(notification_extension.product_reference)

File.write(build_path.join('App.swift'), <<~SWIFT)
import UIKit
import Core
import OnlineConsultant
import Tracker
import Recommendations
import PushService
import PushNotificationExtension

@main final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}
SWIFT
File.write(build_path.join('NotificationService.swift'), <<~SWIFT)
import UserNotifications
import PushNotificationExtension

final class NotificationService: UNNotificationServiceExtension {
private let handler = PushNotificationHandler(siteToken: "ci-build")
}
SWIFT
app.add_file_references([project.main_group.new_file('App.swift')])
notification_extension.add_file_references([project.main_group.new_file('NotificationService.swift')])
project.save
scheme = Xcodeproj::XCScheme.new
scheme.add_build_target(app)
scheme.set_launch_target(app)
scheme.save_as(project.path, 'PodBuild', true)

File.write(build_path.join('Podfile'), <<~PODS)
source 'https://cdn.cocoapods.org/'
platform :ios, '15.0'
use_frameworks! :linkage => :static

sdk_path = #{sdk_path.to_s.dump}
target 'PodBuild' do
%w[
SimlaCore SimlaOnlineConsultant SimlaTracker SimlaRecommendations
SimlaPushService SimlaPushNotificationExtension SimlaSDKConfiguration SimlaSQLiteStorage
].each do |name|
pod name, :path => sdk_path
end
end

target 'NotificationService' do
pod 'SimlaSDKConfiguration', :path => sdk_path
pod 'SimlaSQLiteStorage', :path => sdk_path
pod 'SimlaPushNotificationExtension', :path => sdk_path
end
PODS
puts "CocoaPods build project: #{build_path}"
17 changes: 17 additions & 0 deletions .github/scripts/select-simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import json
import os
import subprocess

sdk = subprocess.check_output(
['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-version'], text=True
).strip()
runtime = 'com.apple.CoreSimulator.SimRuntime.iOS-' + sdk.replace('.', '-')
devices = json.loads(subprocess.check_output(
['xcrun', 'simctl', 'list', 'devices', 'available', '--json'], text=True
))['devices'].get(runtime, [])
device = next((item for item in devices if item['name'].startswith('iPhone')), None)
if device is None:
raise SystemExit('No available iPhone simulator for iOS ' + sdk)
print('Selected ' + device['name'] + ' (iOS ' + sdk + ')')
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write('device_id=' + device['udid'] + '\n')
216 changes: 216 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
DEVELOPER_DIR: /Applications/Xcode_26.5.app/Contents/Developer

jobs:
builds:
name: ${{ matrix.name }}
runs-on: macos-26
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- kind: spm
name: SPM build
- kind: pod
name: Pod build
env:
COCOAPODS_DISABLE_STATS: 'true'
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Show Xcode version
run: xcodebuild -version

- name: Build all SDK modules for iOS
if: ${{ matrix.kind == 'spm' }}
shell: bash
run: |
set -euo pipefail
xcodebuild \
-scheme ConsultantSdk-Package \
-destination 'generic/platform=iOS' \
-derivedDataPath "$RUNNER_TEMP/SimlaDerivedData" \
-clonedSourcePackagesDirPath "$RUNNER_TEMP/SimlaSourcePackages" \
build CODE_SIGNING_ALLOWED=NO \
2>&1 | tee "$RUNNER_TEMP/spm-build.log"

- name: Build SPM tests for simulator
if: ${{ matrix.kind == 'spm' }}
shell: bash
run: |
set -euo pipefail
xcodebuild \
-scheme ConsultantSdk-Package \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$RUNNER_TEMP/SimlaDerivedData" \
-clonedSourcePackagesDirPath "$RUNNER_TEMP/SimlaSourcePackages" \
build-for-testing CODE_SIGNING_ALLOWED=NO \
2>&1 | tee "$RUNNER_TEMP/spm-build-for-testing.log"

- name: Package compiled tests
if: ${{ matrix.kind == 'spm' }}
shell: bash
run: |
set -euo pipefail
products="$RUNNER_TEMP/SimlaDerivedData/Build/Products"
shopt -s nullglob
test_runs=("$products/"*.xctestrun)
if [[ ${#test_runs[@]} -ne 1 ]]; then
echo "Expected exactly one xctestrun file, found ${#test_runs[@]}" >&2
exit 1
fi
tar -C "$products" -czf "$RUNNER_TEMP/spm-test-products.tar.gz" \
Debug-iphonesimulator "$(basename "${test_runs[0]}")"

- name: Upload compiled tests
if: ${{ matrix.kind == 'spm' }}
uses: actions/upload-artifact@v7
with:
name: spm-test-products
path: ${{ runner.temp }}/spm-test-products.tar.gz
compression-level: 0
retention-days: 1
if-no-files-found: error

- name: Upload SPM build logs
if: ${{ !cancelled() && matrix.kind == 'spm' }}
uses: actions/upload-artifact@v7
with:
name: spm-build-results
retention-days: 7
if-no-files-found: ignore
path: |
${{ runner.temp }}/spm-build.log
${{ runner.temp }}/spm-build-for-testing.log

- name: Prepare CocoaPods application and extension
if: ${{ matrix.kind == 'pod' }}
run: |
pod --version
ruby .github/scripts/prepare-pod-build.rb \
"$GITHUB_WORKSPACE" "$RUNNER_TEMP/SimlaPodBuild"

- name: Install local SDK pods
if: ${{ matrix.kind == 'pod' }}
shell: bash
run: |
set -euo pipefail
cd "$RUNNER_TEMP/SimlaPodBuild"
pod install 2>&1 | tee "$RUNNER_TEMP/pod-install.log"

- name: Build CocoaPods application and extension
if: ${{ matrix.kind == 'pod' }}
shell: bash
run: |
set -euo pipefail
xcodebuild \
-workspace "$RUNNER_TEMP/SimlaPodBuild/PodBuild.xcworkspace" \
-scheme PodBuild \
-destination 'generic/platform=iOS' \
-derivedDataPath "$RUNNER_TEMP/SimlaPodDerivedData" \
build CODE_SIGNING_ALLOWED=NO \
2>&1 | tee "$RUNNER_TEMP/pod-build.log"

- name: Upload CocoaPods build logs
if: ${{ !cancelled() && matrix.kind == 'pod' }}
uses: actions/upload-artifact@v7
with:
name: pod-build-results
retention-days: 7
if-no-files-found: ignore
path: |
${{ runner.temp }}/pod-install.log
${{ runner.temp }}/pod-build.log

module_tests:
name: Tests / ${{ matrix.module }}
needs: builds
runs-on: macos-26
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- module: Core
target: ConsultantSdkTests
- module: OnlineConsultant
target: OnlineConsultantTests
- module: PushService
target: PushServiceTests
- module: PushNotificationExtension
target: PushNotificationExtensionTests
- module: Recommendations
target: RecommendationsTests
- module: Tracker
target: TrackerTests
- module: SQLiteStorage
target: SQLiteStorageTests
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Download compiled SPM tests
uses: actions/download-artifact@v8
with:
name: spm-test-products
path: ${{ runner.temp }}/SimlaDerivedData/Build/Products

- name: Select iPhone simulator
id: simulator
run: python3 .github/scripts/select-simulator.py

- name: Run module tests
shell: bash
env:
TEST_TARGET: ${{ matrix.target }}
SIMULATOR_ID: ${{ steps.simulator.outputs.device_id }}
run: |
set -euo pipefail
products="$RUNNER_TEMP/SimlaDerivedData/Build/Products"
tar -xzf "$products/spm-test-products.tar.gz" -C "$products"
shopt -s nullglob
test_runs=("$products/"*.xctestrun)
if [[ ${#test_runs[@]} -ne 1 ]]; then
echo "Expected exactly one xctestrun file, found ${#test_runs[@]}" >&2
exit 1
fi
xcodebuild \
-xctestrun "${test_runs[0]}" \
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
-only-testing:"$TEST_TARGET" \
-parallel-testing-enabled NO \
-resultBundlePath "$RUNNER_TEMP/$TEST_TARGET.xcresult" \
test-without-building CODE_SIGNING_ALLOWED=NO \
2>&1 | tee "$RUNNER_TEMP/$TEST_TARGET.log"

- name: Upload module test results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: tests-${{ matrix.module }}
retention-days: 7
if-no-files-found: ignore
path: |
${{ runner.temp }}/${{ matrix.target }}.xcresult
${{ runner.temp }}/${{ matrix.target }}.log
Loading
Loading