mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
* Uses the remote version of `purchases-ios` for the sample app * Removes framework reference to fix build failure * Removes workspace * Resets `Package.resolved` to fix compiler errors * Re-adds missing dependency * Updates `README.md`
1117 lines
36 KiB
Ruby
1117 lines
36 KiB
Ruby
# This file contains the fastlane.tools configuration
|
|
# You can find the documentation at https://docs.fastlane.tools
|
|
#
|
|
# For a list of all available actions, check out
|
|
#
|
|
# https://docs.fastlane.tools/actions
|
|
#
|
|
# For a list of all available plugins, check out
|
|
#
|
|
# https://docs.fastlane.tools/plugins/available-plugins
|
|
#
|
|
|
|
default_platform(:ios)
|
|
|
|
def current_version_number
|
|
File.read("#{File.dirname(__FILE__)}/../.version").strip
|
|
end
|
|
|
|
files_with_version_number = {
|
|
'./RevenueCat.podspec' => ['s.version = "{x}"'],
|
|
'./RevenueCatUI.podspec' => ['s.version = "{x}"'],
|
|
'./Sources/Misc/SystemInfo.swift' => ['return "{x}"'],
|
|
'./.version' => ['{x}'],
|
|
'./scripts/docs/v4_api_migration_guide.html' => ['purchases-ios-docs/{x}/documentation/revenuecat/v4_api_migration_guide'],
|
|
'./scripts/docs/v5_api_migration_guide.html' => ['purchases-ios-docs/{x}/documentation/revenuecat/v5_api_migration_guide']
|
|
}
|
|
PLIST_VERSION_PATTERNS = ["<key>CFBundleShortVersionString</key>\n\t<string>{x}</string>",]
|
|
files_with_version_number_without_prerelease_modifiers = {
|
|
'./Sources/Info.plist' => PLIST_VERSION_PATTERNS,
|
|
'./Tests/BackendIntegrationTestApp/Info.plist' => PLIST_VERSION_PATTERNS,
|
|
'./Tests/BackendIntegrationTests/Info.plist' => PLIST_VERSION_PATTERNS,
|
|
'./Tests/UnitTests/Info.plist' => PLIST_VERSION_PATTERNS,
|
|
'./Tests/UnitTestsHostApp/Info.plist' => PLIST_VERSION_PATTERNS
|
|
}
|
|
files_to_update_on_latest_stable_releases = {
|
|
'./scripts/docs/index.html' => ['purchases-ios-docs/{x}/documentation/revenuecat'],
|
|
}
|
|
|
|
repo_name = 'purchases-ios'
|
|
snapshots_repo_name = 'purchases-ios-snapshots'
|
|
changelog_latest_path = './CHANGELOG.latest.md'
|
|
changelog_path = './CHANGELOG.md'
|
|
|
|
platform :ios do
|
|
before_all do
|
|
setup_circle_ci
|
|
|
|
if ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID']
|
|
app_store_connect_api_key # This will create a shared client that all the tools know how to use and will look for
|
|
end
|
|
end
|
|
|
|
desc "Bump version, update swift header, edit changelog, and create pull request"
|
|
lane :bump do |options|
|
|
bump_version_update_changelog_create_pr(
|
|
current_version: current_version_number,
|
|
changelog_latest_path: changelog_latest_path,
|
|
changelog_path: changelog_path,
|
|
files_to_update: files_with_version_number,
|
|
files_to_update_without_prerelease_modifiers: files_with_version_number_without_prerelease_modifiers,
|
|
files_to_update_on_latest_stable_releases: files_to_update_on_latest_stable_releases,
|
|
repo_name: repo_name,
|
|
github_rate_limit: options[:github_rate_limit],
|
|
editor: options[:editor],
|
|
next_version: options[:next_version],
|
|
automatic_release: options[:automatic_release]
|
|
)
|
|
compile_autogenerated_header_commit_and_push(options)
|
|
end
|
|
|
|
desc "Automatically bumps version, update swift header, edit changelog, and create pull request"
|
|
lane :automatic_bump do |options|
|
|
next_version, type_of_bump = determine_next_version_using_labels(
|
|
repo_name: repo_name,
|
|
github_rate_limit: options[:github_rate_limit]
|
|
)
|
|
options[:next_version] = next_version
|
|
options[:automatic_release] = true
|
|
if type_of_bump == :skip
|
|
UI.message('Skipping automatic bump since the next version doesn\'t include public facing changes')
|
|
next
|
|
end
|
|
if type_of_bump == :major
|
|
UI.message('Skipping automatic bump since the next version is a major release')
|
|
next
|
|
end
|
|
bump(options)
|
|
end
|
|
|
|
desc "Make github release"
|
|
lane :github_release do |options|
|
|
create_github_release(
|
|
version: options[:version],
|
|
repo_name: repo_name,
|
|
github_api_token: ENV["GITHUB_TOKEN"],
|
|
changelog_latest_path: changelog_latest_path,
|
|
upload_assets: ['RevenueCat.xcframework.zip', 'RevenueCatUI.xcframework.zip']
|
|
)
|
|
end
|
|
|
|
desc "Creates PR changing version to next minor adding a -SNAPSHOT suffix"
|
|
lane :prepare_next_version do |options|
|
|
create_next_snapshot_version(
|
|
current_version: current_version_number,
|
|
repo_name: repo_name,
|
|
github_pr_token: ENV["GITHUB_PULL_REQUEST_API_TOKEN"],
|
|
files_to_update: files_with_version_number,
|
|
files_to_update_without_prerelease_modifiers: files_with_version_number_without_prerelease_modifiers
|
|
)
|
|
end
|
|
|
|
|
|
desc "Setup development environment"
|
|
lane :setup_dev do |options|
|
|
begin
|
|
sh("brew install swiftlint")
|
|
rescue => exception
|
|
UI.error("❌ Please install homebrew and then re-run this lane: /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
|
|
raise exception
|
|
end
|
|
|
|
pre_commit_link = "../.git/hooks/pre-commit"
|
|
if File.exist?(pre_commit_link) || File.symlink?(pre_commit_link)
|
|
UI.message("pre-commit script already linked ✅")
|
|
else
|
|
UI.message("Linking pre-commit script 🔗")
|
|
Dir.chdir ".." do
|
|
sh("ln -s -f ../../scripts/pre-commit.sh .git/hooks/pre-commit")
|
|
end
|
|
end
|
|
end
|
|
|
|
desc "Build the Paywalls Test app for Emerge Snapshots"
|
|
lane :build_paywalls_tester_for_emerge do |options|
|
|
scan(
|
|
workspace: "Tests/TestingApps/PaywallsTester/PaywallsTester.xcworkspace",
|
|
scheme: "PaywallsTester - Live Config",
|
|
build_for_testing: true,
|
|
)
|
|
emerge(repo_name: "RevenueCat/purchases-ios")
|
|
end
|
|
|
|
desc "Runs all the iOS tests"
|
|
lane :test_ios do |options|
|
|
generate_snapshots = ENV["CIRCLECI_TESTS_GENERATE_SNAPSHOTS"] == "true"
|
|
|
|
# For platforms that fail to run StoreKit tests.
|
|
# See https://developer.apple.com/forums/thread/724068 and `FB12223404`.
|
|
skip_sk_tests = options[:skip_sk_tests]
|
|
|
|
if generate_snapshots
|
|
if skip_sk_tests
|
|
test_plan = "CI-RevenueCat-Snapshots"
|
|
else
|
|
test_plan = "CI-Snapshots"
|
|
end
|
|
elsif skip_sk_tests
|
|
test_plan = "CI-RevenueCat"
|
|
else
|
|
test_plan = "CI-AllTests"
|
|
end
|
|
|
|
scan_with_flaky_test_retries(
|
|
step_name: "scan - iPhone",
|
|
device: ENV['SCAN_DEVICE'] || "iPhone 12 (15.2)",
|
|
ensure_devices_found: true,
|
|
scheme: "RevenueCat",
|
|
prelaunch_simulator: true,
|
|
output_types: 'junit',
|
|
result_bundle: true,
|
|
testplan: test_plan,
|
|
configuration: 'Debug',
|
|
output_directory: "fastlane/test_output/xctest/ios",
|
|
number_of_retries: generate_snapshots ? 0 : 5, # Number of retries in an xcodebuild run
|
|
fail_build: !generate_snapshots,
|
|
number_of_flaky_retries: generate_snapshots ? 0 : 5 # Number of retries in an xcodebuild run
|
|
)
|
|
end
|
|
|
|
desc "Runs all the tvOS tests"
|
|
lane :test_tvos do |options|
|
|
scan_with_flaky_test_retries(
|
|
step_name: "scan - Apple TV",
|
|
device: ENV['SCAN_DEVICE'] || "Apple TV",
|
|
ensure_devices_found: true,
|
|
scheme: "RevenueCat",
|
|
prelaunch_simulator: true,
|
|
output_types: 'junit',
|
|
number_of_retries: 5, # Number of retries in an xcodebuild run
|
|
result_bundle: true,
|
|
testplan: "CI-AllTests",
|
|
configuration: 'Debug',
|
|
output_directory: "fastlane/test_output/xctest/tvos",
|
|
|
|
number_of_flaky_retries: 5 # Number of xcodebuild runs to retry
|
|
)
|
|
end
|
|
|
|
desc "Runs all the watchOS tests"
|
|
lane :test_watchos do |options|
|
|
generate_snapshots = ENV["CIRCLECI_TESTS_GENERATE_SNAPSHOTS"] == "true"
|
|
if generate_snapshots
|
|
test_plan = "CI-RevenueCat-Snapshots"
|
|
else
|
|
test_plan = "CI-RevenueCat"
|
|
end
|
|
|
|
scan_with_flaky_test_retries(
|
|
step_name: "scan - watchOS",
|
|
device: ENV['SCAN_DEVICE'] || "Apple Watch Series 10 (46mm)",
|
|
ensure_devices_found: true,
|
|
scheme: "RevenueCat",
|
|
prelaunch_simulator: true,
|
|
output_types: 'junit',
|
|
result_bundle: true,
|
|
# Host app isn't available on watchOS
|
|
testplan: test_plan,
|
|
configuration: 'Debug',
|
|
output_directory: "fastlane/test_output/xctest/watchos",
|
|
number_of_retries: generate_snapshots ? 0 : 5, # Number of retries in an xcodebuild run
|
|
fail_build: !generate_snapshots,
|
|
|
|
number_of_flaky_retries: generate_snapshots ? 0 : 5 # Number of retries in an xcodebuild run
|
|
)
|
|
end
|
|
|
|
desc "Runs all RevenueCatUI tests"
|
|
lane :test_revenuecatui do |options|
|
|
generate_snapshots = ENV["CIRCLECI_TESTS_GENERATE_REVENUECAT_UI_SNAPSHOTS"] == "true"
|
|
platform = ENV['PLATFORM'] || 'iOS Simulator'
|
|
destination = ENV['DEVICE'] || "iPhone 14,OS=16.4"
|
|
sdk = ENV['BUILD_SDK'] || 'iphonesimulator'
|
|
|
|
fetch_snapshots
|
|
|
|
begin
|
|
# See https://forums.swift.org/t/i-made-a-little-spm-package-that-uses-uikit-how-can-i-run-tests-from-command-line/50399/2
|
|
xcodebuild(
|
|
workspace: '.',
|
|
scheme: 'RevenueCatUI',
|
|
destination: "platform=" + platform + ",name=" + destination,
|
|
sdk: sdk,
|
|
output_style: :basic,
|
|
result_bundle_path: 'fastlane/test_output/revenuecatui.xcresult',
|
|
report_formats: [:junit],
|
|
report_path: 'fastlane/test_output/revenuecatui/tests.xml',
|
|
test: true,
|
|
xcargs: generate_snapshots ? "-testPlan CI-Snapshots" : "-testPlan CI-RevenueCatUI"
|
|
)
|
|
rescue => e
|
|
# Equivalent to `fail_build: !generate_snapshots`
|
|
raise e if !generate_snapshots
|
|
end
|
|
end
|
|
|
|
desc "Release checks"
|
|
lane :release_checks do |options|
|
|
version_number = current_version_number
|
|
check_no_git_tag_exists(version_number)
|
|
check_pods
|
|
export_xcframeworks
|
|
check_no_github_release_exists(version_number)
|
|
end
|
|
|
|
desc "build tvOS, watchOS, macOS"
|
|
lane :build_tv_watch_mac do |options|
|
|
load_spm_dependencies
|
|
build_release(platform: 'watchOS')
|
|
build_release(platform: 'tvOS')
|
|
build_release(platform: 'macOS')
|
|
end
|
|
|
|
desc "macOS build"
|
|
lane :build_mac do |options|
|
|
load_spm_dependencies
|
|
build_release(platform: 'macOS')
|
|
end
|
|
|
|
desc "visionOS build"
|
|
lane :build_visionos do |options|
|
|
build_release(
|
|
platform: 'visionOS',
|
|
scheme: 'RevenueCat'
|
|
)
|
|
build_release(
|
|
platform: 'visionOS',
|
|
scheme: 'RevenueCatUI'
|
|
)
|
|
end
|
|
|
|
desc "archive"
|
|
lane :archive do
|
|
match(type: "appstore", readonly: true)
|
|
gym(export_method: "app-store")
|
|
end
|
|
|
|
desc "archive all platforms"
|
|
lane :archive_all_platforms do
|
|
platforms = {
|
|
'ios' => 'generic/platform=ios',
|
|
'tvos' => 'generic/platform=tvos',
|
|
'catalyst' => 'generic/platform=macOS,variant=Mac Catalyst'
|
|
}
|
|
|
|
platforms.each do |platform, destination|
|
|
match(type: "appstore", platform: platform, readonly: true)
|
|
gym(export_method: "app-store", destination: destination)
|
|
end
|
|
end
|
|
|
|
desc "run API Tests"
|
|
lane :run_api_tests do
|
|
# note: it's called "run" tests but these tests only need to build to run
|
|
xcodebuild(
|
|
workspace: 'RevenueCat.xcworkspace',
|
|
scheme: 'All API Tests',
|
|
destination: 'generic/platform=iOS Simulator',
|
|
configuration: 'release',
|
|
)
|
|
end
|
|
|
|
desc "replace API KEY for installation and integration tests"
|
|
lane :replace_api_key_integration_tests do
|
|
replace_text_in_files(
|
|
previous_text: "REVENUECAT_API_KEY",
|
|
new_text: ENV["REVENUECAT_API_KEY"],
|
|
paths_of_files_to_update: [
|
|
'./Tests/BackendIntegrationTests/Constants.swift',
|
|
'./Tests/InstallationTests/CommonFiles/RCInstallationRunner.m'
|
|
]
|
|
)
|
|
replace_text_in_files(
|
|
previous_text: "REVENUECAT_LOAD_SHEDDER_API_KEY",
|
|
new_text: ENV["REVENUECAT_LOAD_SHEDDER_API_KEY"],
|
|
paths_of_files_to_update: [
|
|
'./Tests/BackendIntegrationTests/Constants.swift',
|
|
'./Tests/v3LoadShedderIntegration/v3LoadShedderIntegrationTests/V3LoadShedderIntegrationTests.swift'
|
|
]
|
|
)
|
|
replace_text_in_files(
|
|
previous_text: "REVENUECAT_CUSTOM_ENTITLEMENT_COMPUTATION_API_KEY",
|
|
new_text: ENV["REVENUECAT_CUSTOM_ENTITLEMENT_COMPUTATION_API_KEY"],
|
|
paths_of_files_to_update: [
|
|
'./Tests/BackendIntegrationTests/Constants.swift',
|
|
'./Tests/InstallationTests/SPMCustomEntitlementComputationInstallation/InstallationTests.swift'
|
|
]
|
|
)
|
|
|
|
replace_text_in_files(
|
|
previous_text: "REVENUECAT_PROXY_URL",
|
|
new_text: ENV["REVENUECAT_PROXY_URL"].to_s,
|
|
paths_of_files_to_update: [
|
|
'./Tests/BackendIntegrationTests/Constants.swift',
|
|
'./Tests/InstallationTests/CommonFiles/RCInstallationRunner.m',
|
|
'./Tests/InstallationTests/SPMCustomEntitlementComputationInstallation/InstallationTests.swift'
|
|
],
|
|
allow_empty: true
|
|
)
|
|
end
|
|
|
|
desc "Create XCFramework, and create GitHub release"
|
|
lane :release do |options|
|
|
version_number = current_version_number
|
|
export_xcframeworks
|
|
github_release(version: version_number)
|
|
end
|
|
|
|
desc "Release to CocoaPods"
|
|
lane :push_revenuecat_pod do |options|
|
|
pod_push(path: "RevenueCat.podspec", synchronous: true)
|
|
end
|
|
|
|
desc "Release to CocoaPods"
|
|
lane :push_revenuecatui_pod do |options|
|
|
pod_push(path: "RevenueCatUI.podspec", synchronous: true)
|
|
end
|
|
|
|
desc "Tag current branch with current version number"
|
|
lane :tag_current_branch do |options|
|
|
version_number = current_version_number
|
|
check_no_git_tag_exists(version_number)
|
|
|
|
add_git_tag(tag: version_number)
|
|
push_git_tags(tag: version_number)
|
|
end
|
|
|
|
desc "Trigger bump"
|
|
lane :trigger_bump do
|
|
trigger_action_in_circle_ci(action: 'bump', repo_name: repo_name)
|
|
end
|
|
|
|
desc "Checks whether the RevenueCat-Swift.h is up to date"
|
|
private_lane :check_autogenerated_header_up_to_date do |options|
|
|
ensure_git_status_clean
|
|
compile_autogenerated_header
|
|
begin
|
|
ensure_git_status_clean
|
|
rescue => e
|
|
UI.error("RevenueCat-Swift.h autogenerated file changed. Make sure it's up-to-date before releasing.")
|
|
end
|
|
end
|
|
|
|
private_lane :compile_autogenerated_header_commit_and_push do |options|
|
|
ensure_git_status_clean
|
|
compile_autogenerated_header
|
|
begin
|
|
ensure_git_status_clean
|
|
rescue => e
|
|
commit_current_changes(commit_message: "Update RevenueCat-Swift.h for version #{current_version_number}")
|
|
push_to_git_remote
|
|
end
|
|
end
|
|
|
|
desc "Trigger CircleCI job to generate snapshots for RevenueCat"
|
|
lane :generate_snapshots_RC do
|
|
generate_snapshots(
|
|
pipeline: "generate_snapshots"
|
|
)
|
|
end
|
|
|
|
desc "Trigger CircleCI job to generate snapshots for RevenueCatUI"
|
|
lane :generate_snapshots_RCUI do
|
|
generate_snapshots(
|
|
pipeline: "generate_revenuecatui_snapshots"
|
|
)
|
|
end
|
|
|
|
private_lane :build_release do |options|
|
|
platform = options[:platform]
|
|
scheme = options[:scheme] || 'RevenueCat'
|
|
|
|
xcodebuild(
|
|
workspace: 'RevenueCat.xcworkspace',
|
|
scheme: scheme,
|
|
destination: "generic/platform=#{platform}",
|
|
configuration: 'release',
|
|
xcargs: "CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=YES"
|
|
)
|
|
end
|
|
|
|
private_lane :generate_snapshots do |options|
|
|
require 'rest-client'
|
|
|
|
# Prompt branch
|
|
default_branch = git_branch
|
|
branch = UI.input("Branch (defaults to #{default_branch}): ")
|
|
branch = default_branch if branch == ""
|
|
|
|
# Get CircleCI token
|
|
circle_token = ENV["CIRCLE_TOKEN"]
|
|
UI.user_error! "Please set the CIRCLE_TOKEN environment variable" unless circle_token
|
|
|
|
# Make request
|
|
headers = {"Circle-Token": circle_token, "Content-Type": "application/json", "Accept": "application/json"}
|
|
data = {parameters: {options[:pipeline] => true}, branch: branch}
|
|
url = "https://circleci.com/api/v2/project/github/RevenueCat/#{repo_name}/pipeline"
|
|
|
|
resp = RestClient.post url, data.to_json, headers
|
|
|
|
# Print workflow url
|
|
number = JSON.parse(resp.body)["number"]
|
|
workflow_url = "https://app.circleci.com/pipelines/github/RevenueCat/#{repo_name}/#{number}"
|
|
|
|
UI.important "Workflow: #{workflow_url}"
|
|
end
|
|
|
|
desc "Creates a new PR after new snapshot files were generated"
|
|
lane :create_snapshot_pr do |options|
|
|
push_snapshot_pr(
|
|
version: options[:version],
|
|
repo: "revenuecat/#{repo_name}",
|
|
base_branch: ENV["CIRCLE_BRANCH"],
|
|
files_to_add: "../*/__Snapshots__/*"
|
|
)
|
|
end
|
|
|
|
desc "Creates a new PR on purchases-ios-snapshots after new snapshot files were generated"
|
|
lane :create_snapshots_repo_pr do |options|
|
|
Dir.chdir("../Tests/#{snapshots_repo_name}") { |dir|
|
|
push_snapshot_pr(
|
|
version: options[:version],
|
|
repo: "revenuecat/#{snapshots_repo_name}",
|
|
base_branch: "main",
|
|
files_to_add: "."
|
|
)
|
|
}
|
|
end
|
|
|
|
private_lane :push_snapshot_pr do |options|
|
|
version = options[:version]
|
|
base_branch = options[:base_branch]
|
|
build_number = ENV["CIRCLE_BUILD_NUM"]
|
|
|
|
branch_name = "generated_snapshots/#{base_branch}-#{build_number}-#{version}"
|
|
|
|
sh("git", "checkout", "-b", branch_name)
|
|
sh("git", "add", options[:files_to_add])
|
|
|
|
file_count = sh("git diff --cached --numstat | wc -l").strip.to_i
|
|
|
|
if file_count == 0
|
|
UI.important("No files to be committed")
|
|
else
|
|
sh("git", "commit", "-m", "[skip ci] Generating new test snapshots")
|
|
sh("git", "push", "origin", branch_name)
|
|
|
|
circle_user = ENV["CIRCLE_USERNAME"]
|
|
branch_link = "https://github.com/RevenueCat/purchases-ios/tree/#{base_branch}"
|
|
body = "Requested by @#{circle_user} for [#{base_branch}](#{branch_link})"
|
|
|
|
create_pull_request(
|
|
repo: options[:repo],
|
|
title: "Generating new test snapshots for `#{base_branch}` - #{version}",
|
|
body: body,
|
|
base: base_branch,
|
|
api_token: ENV["GITHUB_TOKEN"],
|
|
head: branch_name,
|
|
labels: ["test"]
|
|
)
|
|
end
|
|
end
|
|
|
|
desc "Creates RevenueCat-Swift.h for a new release"
|
|
lane :compile_autogenerated_header do |options|
|
|
# See https://github.com/RevenueCat/purchases-ios/blob/main/Documentation.docc/V4_API_Migration_guide.md#objc--spm
|
|
|
|
require 'fileutils'
|
|
|
|
arch = "x86_64"
|
|
|
|
Dir.mktmpdir {|dir|
|
|
xcodebuild(
|
|
project: 'RevenueCat.xcodeproj',
|
|
scheme: 'RevenueCat',
|
|
derivedDataPath: dir,
|
|
configuration: 'release',
|
|
arch: arch,
|
|
build_settings: {
|
|
'ONLY_ACTIVE_ARCH': 'YES'
|
|
}
|
|
)
|
|
|
|
FileUtils.cp(
|
|
"#{dir}/Build/Intermediates.noindex/RevenueCat.build/Release/RevenueCat.build/Objects-normal/#{arch}/RevenueCat-Swift.h",
|
|
File.absolute_path("../Tests/InstallationTests/CommonFiles/RevenueCat-Swift.h")
|
|
)
|
|
}
|
|
end
|
|
|
|
lane :export_xcframeworks do |options|
|
|
create_and_sign_xcframework(
|
|
scheme: 'RevenueCat',
|
|
product_name: 'RevenueCat',
|
|
platforms: [
|
|
'iOS',
|
|
'macOS',
|
|
'maccatalyst',
|
|
'tvOS',
|
|
'watchOS',
|
|
'visionOS'
|
|
])
|
|
create_and_sign_xcframework(
|
|
scheme: 'RevenueCatUIDev',
|
|
product_name: 'RevenueCatUI',
|
|
platforms: [
|
|
'iOS',
|
|
'maccatalyst',
|
|
'watchOS',
|
|
])
|
|
end
|
|
|
|
private_lane :create_and_sign_xcframework do |options|
|
|
scheme = options[:scheme]
|
|
product_name = options[:product_name]
|
|
platforms = options[:platforms]
|
|
|
|
output_directory = "build/xcframeworks/#{product_name}"
|
|
|
|
create_xcframework(
|
|
destinations: platforms,
|
|
scheme: scheme,
|
|
product_name: product_name,
|
|
xcframework_output_directory: output_directory,
|
|
# Bitcode is no longer required since Xcode 14.
|
|
enable_bitcode: false,
|
|
include_debug_symbols: true,
|
|
# Set SKIP_INSTALL and BUILD_LIBRARY_FOR_DISTRIBUTION.
|
|
# See https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle#Set-up-the-project
|
|
override_xcargs: true
|
|
)
|
|
|
|
# sh runs from the Fastfile's location, but other commands run from the project root.
|
|
output_directory_for_sh = "../#{output_directory}"
|
|
|
|
# Sign XCFramework for Xcode 15
|
|
sign_xcframework(
|
|
file: "#{output_directory_for_sh}/#{product_name}.xcframework"
|
|
)
|
|
|
|
xcframeworks_zip_path_for_sh = "../#{product_name}.xcframework.zip"
|
|
sh("ditto", "-c", "-k", "--sequesterRsrc", "--keepParent",
|
|
output_directory_for_sh,
|
|
xcframeworks_zip_path_for_sh)
|
|
|
|
end
|
|
|
|
desc "Run BackendIntegrationTests"
|
|
lane :backend_integration_tests do |options|
|
|
fail_build = options[:fail_build]
|
|
|
|
fetch_snapshots
|
|
replace_api_key_integration_tests
|
|
|
|
scan_with_flaky_test_retries(
|
|
scheme: "BackendIntegrationTests",
|
|
ensure_devices_found: true,
|
|
derived_data_path: "scan_derived_data",
|
|
output_types: 'junit',
|
|
output_style: 'raw',
|
|
result_bundle: true,
|
|
testplan: options[:test_plan],
|
|
configuration: 'Debug',
|
|
output_directory: "fastlane/test_output/xctest/ios",
|
|
|
|
number_of_flaky_retries: 5 # Number of retries in an xcodebuild run
|
|
)
|
|
end
|
|
|
|
desc "Run LoadShedder tests"
|
|
lane :v3_loadshedder_integration_tests do |options|
|
|
replace_api_key_integration_tests
|
|
scan(
|
|
project: "./Tests/v3LoadShedderIntegration/v3LoadShedderIntegration.xcodeproj",
|
|
scheme: "v3LoadShedderIntegration",
|
|
ensure_devices_found: true,
|
|
derived_data_path: "scan_derived_data",
|
|
output_types: 'junit',
|
|
output_style: 'raw',
|
|
result_bundle: true,
|
|
configuration: 'Debug',
|
|
output_directory: "fastlane/test_output/xctest/ios"
|
|
)
|
|
end
|
|
|
|
|
|
desc "Update swift package commit"
|
|
lane :update_swift_package_commit do
|
|
project_file_locations = [
|
|
'./Tests/InstallationTests/SPMInstallation/SPMInstallation.xcodeproj/project.pbxproj',
|
|
'./Tests/InstallationTests/SPMCustomEntitlementComputationInstallation/SPMInstallation.xcodeproj/project.pbxproj',
|
|
'./Tests/InstallationTests/ReceiptParserInstallation/ReceiptParserInstallation.xcodeproj/project.pbxproj',
|
|
'./Examples/MagicWeather/MagicWeather.xcodeproj/project.pbxproj',
|
|
'./Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.pbxproj'
|
|
]
|
|
|
|
old_kind_line = "kind = branch;"
|
|
new_kind_line = "kind = revision;"
|
|
|
|
commit_hash = last_git_commit[:commit_hash]
|
|
old_branch_line = "branch = main;"
|
|
new_revision_line = "revision = #{commit_hash};"
|
|
|
|
replace_text_in_files(
|
|
previous_text: old_kind_line,
|
|
new_text: new_kind_line,
|
|
paths_of_files_to_update: project_file_locations
|
|
)
|
|
|
|
replace_text_in_files(
|
|
previous_text: old_branch_line,
|
|
new_text: new_revision_line,
|
|
paths_of_files_to_update: project_file_locations
|
|
)
|
|
end
|
|
|
|
private_lane :sign_xcframework do |options|
|
|
match(
|
|
readonly: true,
|
|
git_url: ENV['CERTS_REPO_URL'],
|
|
type: 'appstore',
|
|
skip_provisioning_profiles: true,
|
|
# This isn't important, we just need to fetch the certificate
|
|
app_identifier: "com.revenuecat.CarthageIntegration"
|
|
)
|
|
|
|
sh(
|
|
"codesign",
|
|
"--timestamp",
|
|
"-v",
|
|
"--sign", "Apple Distribution: RevenueCat, Inc. (8SXR2327BM)",
|
|
options[:file]
|
|
)
|
|
end
|
|
|
|
private_lane :build_symbols_for_docs do
|
|
ios_version = ENV['DOCS_IOS_VERSION'] || "16.1"
|
|
|
|
sh("swift",
|
|
"build",
|
|
"--target", "RevenueCat",
|
|
# Build for iOS instead of the default macOS. This ensures that iOS-only symbols are included in the docs.
|
|
"-Xswiftc", "-sdk", "-Xswiftc", sh("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path").strip!,
|
|
"-Xswiftc", "-target", "-Xswiftc", "arm64-apple-ios#{ios_version}-simulator",
|
|
"-Xswiftc", "-emit-symbol-graph",
|
|
"-Xswiftc", "-emit-symbol-graph-dir",
|
|
"-Xswiftc", ".build")
|
|
end
|
|
|
|
desc "Preview docs"
|
|
lane :preview_docs do
|
|
ios_version = ENV['DOCS_IOS_VERSION'] || "16.1"
|
|
|
|
ENV["INCLUDE_DOCC_PLUGIN"] = "true"
|
|
Dir.chdir("..") do
|
|
build_symbols_for_docs
|
|
|
|
sh("swift",
|
|
"package",
|
|
"--disable-sandbox",
|
|
"preview-documentation",
|
|
"--target",
|
|
"RevenueCat",
|
|
"--platform",
|
|
"name=iOS,version=#{ios_version}",
|
|
"--transform-for-static-hosting",
|
|
"--enable-inherited-docs",
|
|
"--additional-symbol-graph-dir", ".build")
|
|
end
|
|
end
|
|
|
|
desc "Generate docs without publishing"
|
|
lane :build_docs do
|
|
build_docs_shared
|
|
end
|
|
|
|
desc "Generate & publish docs"
|
|
lane :build_and_publish_docs do
|
|
docs_generation_folder = build_docs_shared
|
|
|
|
version_number = current_version_number
|
|
docs_repo_base_url = ENV["DOCS_REPO_BASE_URL"]
|
|
docs_repo_name = ENV["DOCS_REPO_NAME"]
|
|
|
|
UI.user_error!("Missing environment variable: DOCS_REPO_BASE_URL") unless docs_repo_base_url
|
|
UI.user_error!("Missing environment variable: DOCS_REPO_NAME") unless docs_repo_name
|
|
|
|
docs_repo_url = File.join(docs_repo_base_url, docs_repo_name)
|
|
|
|
docs_files = Dir.glob("scripts/docs/*.html").map { |file| File.realpath(file) }
|
|
# clone docs repo
|
|
Dir.mktmpdir do |docs_repo_clone_dir|
|
|
Dir.chdir(docs_repo_clone_dir) do
|
|
sh("git", "clone", docs_repo_url)
|
|
Dir.chdir(docs_repo_name) do
|
|
# copy docs generated in the previous step into the docs folder
|
|
# and push the changes
|
|
docs_destination_folder = "docs/#{version_number}"
|
|
FileUtils.cp_r docs_generation_folder + "/.", docs_destination_folder
|
|
docs_files.each do |file|
|
|
FileUtils.cp file, "docs/"
|
|
end
|
|
|
|
# using sh instead of fastlane commands because fastlane would run
|
|
# from the repo root
|
|
sh("git", "add", docs_destination_folder)
|
|
sh("git", "add", "docs/*.html")
|
|
sh("git", "commit", "-m", "Update documentation for #{version_number}")
|
|
sh("git", "push")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
desc "Builds the docs"
|
|
private_lane :build_docs_shared do
|
|
ENV["INCLUDE_DOCC_PLUGIN"] = "true"
|
|
|
|
version_number = current_version_number
|
|
docs_repo_name = ENV["DOCS_REPO_NAME"]
|
|
ios_version = ENV['DOCS_IOS_VERSION'] || "16.1"
|
|
|
|
UI.user_error!("Missing environment variable: DOCS_REPO_NAME") unless docs_repo_name
|
|
|
|
hosting_base_path = File.join(docs_repo_name, version_number)
|
|
|
|
docs_generation_folder = Dir.mktmpdir
|
|
|
|
# swift package must be run from the dir that contains the Package.swift
|
|
# output is generated in docs_generation_folder
|
|
Dir.chdir("..") do
|
|
build_symbols_for_docs
|
|
|
|
sh("swift",
|
|
"package",
|
|
"--disable-sandbox",
|
|
"--allow-writing-to-directory",
|
|
docs_generation_folder,
|
|
"generate-documentation",
|
|
"--target",
|
|
"RevenueCat",
|
|
"--disable-indexing", # Produces a DocC archive that is best-suited for hosting online but incompatible with Xcode.
|
|
"--platform",
|
|
"name=iOS,version=#{ios_version}",
|
|
"--output-path",
|
|
docs_generation_folder,
|
|
"--hosting-base-path",
|
|
hosting_base_path,
|
|
"--transform-for-static-hosting",
|
|
"--enable-inherited-docs",
|
|
"--additional-symbol-graph-dir", ".build")
|
|
end
|
|
|
|
docs_generation_folder # returning path from lane
|
|
end
|
|
|
|
desc "Build and deploy PurchaseTesterSwiftUI"
|
|
lane :deploy_purchase_tester do |options|
|
|
dry_run = options[:dry_run] || false
|
|
|
|
project_root = File.expand_path("#{File.dirname(__FILE__)}/..")
|
|
|
|
latest_changelog = File.read("#{project_root}/#{changelog_latest_path}")
|
|
changelog = "Release #{current_version_number}:\n\n#{latest_changelog}"
|
|
|
|
purchase_tester_root = "#{project_root}/Tests/TestingApps/PurchaseTesterSwiftUI"
|
|
xcodeproj_path = "#{purchase_tester_root}/PurchaseTester.xcodeproj"
|
|
|
|
# The following commands use configuration that lives in the PurchaseTesterSwiftUI directory,
|
|
# like the Matchfile. So we move to that project's directory to run them.
|
|
Dir.chdir("#{purchase_tester_root}/fastlane") do
|
|
match(readonly: true, platform: "ios")
|
|
increment_build_number(
|
|
build_number: latest_testflight_build_number(platform: 'ios') + 1,
|
|
xcodeproj: xcodeproj_path,
|
|
)
|
|
end
|
|
|
|
build_ios_app(
|
|
workspace: 'RevenueCat.xcworkspace',
|
|
scheme: "PurchaseTester",
|
|
destination: "generic/platform=ios",
|
|
xcodebuild_formatter: '',
|
|
export_method: 'app-store',
|
|
export_options: {
|
|
manageAppVersionAndBuildNumber: true,
|
|
provisioningProfiles: {
|
|
"com.revenuecat.sampleapp" => "match AppStore com.revenuecat.sampleapp",
|
|
"com.revenuecat.sampleapp.watchkitapp" => "match AppStore com.revenuecat.sampleapp.watchkitapp"
|
|
}
|
|
}
|
|
)
|
|
|
|
if not dry_run
|
|
upload_to_testflight(
|
|
changelog: changelog,
|
|
app_identifier: "com.revenuecat.sampleapp",
|
|
)
|
|
else
|
|
UI.message("Dry run mode enabled. Skipping upload to TestFlight")
|
|
end
|
|
|
|
# The following commands use configuration that lives in the PurchaseTesterSwiftUI directory,
|
|
# like the Matchfile. So we move to that project's directory to run them.
|
|
Dir.chdir("#{purchase_tester_root}/fastlane") do
|
|
match(readonly: true, platform: "macos", additional_cert_types: "mac_installer_distribution")
|
|
increment_build_number(
|
|
build_number: latest_testflight_build_number(platform: 'osx') + 1,
|
|
xcodeproj: xcodeproj_path,
|
|
)
|
|
end
|
|
|
|
build_mac_app(
|
|
workspace: 'RevenueCat.xcworkspace',
|
|
scheme: "PurchaseTester",
|
|
destination: 'generic/platform=macOS',
|
|
xcodebuild_formatter: '',
|
|
export_method: 'app-store',
|
|
export_options: {
|
|
manageAppVersionAndBuildNumber: true,
|
|
provisioningProfiles: {
|
|
"com.revenuecat.sampleapp" => "match AppStore com.revenuecat.sampleapp macos",
|
|
}
|
|
}
|
|
)
|
|
|
|
if not dry_run
|
|
upload_to_testflight(
|
|
changelog: changelog,
|
|
app_platform: 'osx',
|
|
app_identifier: "com.revenuecat.sampleapp",
|
|
)
|
|
else
|
|
UI.message("Dry run mode enabled. Skipping upload to TestFlight")
|
|
end
|
|
|
|
end
|
|
|
|
desc "Clones or updates snapshots repo"
|
|
lane :fetch_snapshots do
|
|
Dir.chdir("../Tests") do
|
|
if !File.directory?(snapshots_repo_name)
|
|
UI.important("Cloning snapshots repo")
|
|
|
|
sh "git", "clone", "git@github.com:RevenueCat/#{snapshots_repo_name}.git"
|
|
end
|
|
|
|
commit = current_snapshots_commit
|
|
UI.message("Updating snapshots repo to #{commit}")
|
|
Dir.chdir(snapshots_repo_name) do
|
|
sh "git", "fetch", "origin"
|
|
sh "git", "checkout", commit
|
|
end
|
|
end
|
|
end
|
|
|
|
desc "Updates purchases-ios-snapshots-commit to point to latest commit on main"
|
|
lane :update_snapshots_repo do
|
|
new_hash = ""
|
|
|
|
Dir.chdir("../Tests/#{snapshots_repo_name}") do
|
|
sh "git", "checkout", "main"
|
|
sh "git", "pull", "--rebase", "origin"
|
|
new_hash = sh("git", "rev-parse", "HEAD")
|
|
end
|
|
|
|
UI.important("Updating snapshot commit to #{new_hash}")
|
|
update_snapshots_commit(new_hash)
|
|
end
|
|
|
|
lane :deploy_to_spm do
|
|
source_repo = ENV["CIRCLE_REPOSITORY_URL"] || "https://github.com/revenuecat/#{repo_name}"
|
|
destination_repo = source_repo.sub(/\.git\z/, "-spm.git")
|
|
|
|
git_clone_and_push(
|
|
source_repo: source_repo,
|
|
destination_repo: destination_repo
|
|
)
|
|
end
|
|
|
|
desc "Create or delete sandbox testers"
|
|
lane :sandbox_testers do
|
|
Spaceship::ConnectAPI.login(use_portal: false)
|
|
|
|
UI.deprecated("Email pattern will be used when creating and deleting multiple testers")
|
|
UI.deprecated("fastlane will increment a number before '@'")
|
|
UI.deprecated("Example: testparty.v4@revenuecat.com will make: ")
|
|
UI.deprecated(" testparty.v4.1@revenuecat.com")
|
|
UI.deprecated(" testparty.v4.2@revenuecat.com")
|
|
UI.deprecated(" testparty.v4.3@revenuecat.com")
|
|
email_pattern = UI.input("Sanbox Tester Email pattern:")
|
|
|
|
options = ["Create", "Delete"]
|
|
option = UI.select("What do you want to do?", options)
|
|
|
|
if option == options[0]
|
|
sandbox_testers_create(email_pattern: email_pattern)
|
|
elsif option == options[1]
|
|
sandbox_testers_delete(email_pattern: email_pattern)
|
|
end
|
|
end
|
|
|
|
desc "Enable customer center development by cherry-picking a specific commit"
|
|
lane :enable_customer_center do |options|
|
|
commit_hash = options[:commit_hash] || "517ad0bf5"
|
|
|
|
UI.message("Applying changes from commit to enable customer center: #{commit_hash}")
|
|
|
|
begin
|
|
sh("git", "cherry-pick", "--no-commit", commit_hash)
|
|
UI.success("Successfully applied changes for customer center development from commit #{commit_hash}")
|
|
UI.important("Changes are now in your working directory. Review and stage/commit as needed.")
|
|
rescue => ex
|
|
UI.error("Failed to enable customer center. Error while applying changes from commit #{commit_hash}")
|
|
UI.error(ex.message)
|
|
|
|
if UI.confirm("Do you want to abort the customer center enablement?")
|
|
sh("git", "cherry-pick", "--abort")
|
|
UI.message("Customer center enablement aborted")
|
|
end
|
|
end
|
|
end
|
|
|
|
private_lane :sandbox_testers_delete do |options|
|
|
email_pattern = options[:email_pattern]
|
|
|
|
email_prefix = email_pattern.split('@').first
|
|
|
|
testers = Spaceship::ConnectAPI::SandboxTester.all.select do |tester|
|
|
tester.email.start_with?(email_prefix)
|
|
end
|
|
|
|
UI.important("Found testers:")
|
|
testers.each do |tester|
|
|
UI.message(tester.email)
|
|
end
|
|
|
|
if UI.confirm("Do you want to delete these sandbox testers?")
|
|
testers.each do |tester|
|
|
UI.message("Deleting #{tester.email}...")
|
|
tester.delete!
|
|
end
|
|
end
|
|
end
|
|
|
|
private_lane :sandbox_testers_create do |options|
|
|
email_pattern = options[:email_pattern]
|
|
|
|
number = UI.input("Number of testers:").to_i
|
|
UI.user_error!("Number needs to be more than 0") if number <= 0
|
|
UI.user_error!("Number needs to be less than 51") if number > 50
|
|
|
|
password = UI.input("Sandbox Tester Password (ex: 'SuperSecret01'):")
|
|
|
|
question = UI.input("Sandbox Tester Secret Question (ex: 'Secret'):")
|
|
answer = UI.input("Sandbox Tester Secret Answer (ex: 'Answer'):")
|
|
|
|
emails = (1..number).map do |i|
|
|
email_pattern.gsub('@', ".#{i.to_s}@")
|
|
end
|
|
|
|
emails.each do |email|
|
|
data = {
|
|
first_name: "Tester",
|
|
last_name: "FastlaneCreated",
|
|
email: email,
|
|
password: password,
|
|
confirm_password: password,
|
|
secret_question: question,
|
|
secret_answer: answer,
|
|
birth_date: "1990-01-01",
|
|
app_store_territory: "USA"
|
|
}
|
|
UI.message("Creating #{email}...")
|
|
Spaceship::ConnectAPI::SandboxTester.create(**data)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
platform :mac do
|
|
desc "Runs tests on macOS"
|
|
lane :test_macos do |options|
|
|
generate_snapshots = ENV["CIRCLECI_TESTS_GENERATE_SNAPSHOTS"] == "true"
|
|
if generate_snapshots
|
|
test_plan = "CI-RevenueCat-Snapshots"
|
|
else
|
|
test_plan = "CI-RevenueCat"
|
|
end
|
|
|
|
begin
|
|
xcodebuild(
|
|
workspace: 'RevenueCat.xcworkspace',
|
|
scheme: 'RevenueCat',
|
|
destination: "platform=macOS,arch=arm64",
|
|
output_style: :basic,
|
|
result_bundle_path: 'fastlane/test_output/xctest/macos/revenuecat.xcresult',
|
|
report_formats: [:junit],
|
|
report_path: 'fastlane/test_output/xctest/macos/revenuecat/tests.xml',
|
|
test: true,
|
|
xcargs: "-testPlan #{test_plan} CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO"
|
|
)
|
|
rescue => e
|
|
# Equivalent to `fail_build: !generate_snapshots`
|
|
raise e if !generate_snapshots
|
|
end
|
|
end
|
|
end
|
|
|
|
def check_no_git_tag_exists(version_number)
|
|
if git_tag_exists(tag: version_number, remote: true, remote_name: 'origin')
|
|
raise "git tag with version #{version_number} already exists!"
|
|
else
|
|
UI.message("✅ git tag with version #{version_number} doesn't exist")
|
|
end
|
|
end
|
|
|
|
def check_no_github_release_exists(version_number)
|
|
found_release_number = get_github_release(url: "revenuecat/purchases-ios", version: version_number)
|
|
raise "Release with version #{version_number} already exists!" unless found_release_number.nil?
|
|
end
|
|
|
|
lane :check_pods do
|
|
pod_lib_lint(
|
|
verbose: true,
|
|
podspec: 'RevenueCat.podspec',
|
|
platforms: 'ios,osx,tvos,watchos,visionos',
|
|
fail_fast: true
|
|
)
|
|
pod_lib_lint(
|
|
verbose: true,
|
|
podspec: 'RevenueCatUI.podspec',
|
|
include_podspecs: 'RevenueCat.podspec',
|
|
platforms: 'ios,osx,tvos,watchos,visionos',
|
|
fail_fast: true
|
|
)
|
|
end
|
|
|
|
lane :load_spm_dependencies do
|
|
Dir.chdir("#{File.dirname(__FILE__)}/../") do
|
|
sh("xcodebuild", "-list")
|
|
end
|
|
end
|
|
|
|
# Snapshots
|
|
|
|
def snapshots_commit_file
|
|
"#{File.dirname(__FILE__)}/../Tests/purchases-ios-snapshots-commit"
|
|
end
|
|
|
|
def current_snapshots_commit
|
|
File.read(snapshots_commit_file).strip
|
|
end
|
|
def update_snapshots_commit(hash)
|
|
File.write(snapshots_commit_file, hash)
|
|
end
|
|
|