mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00

* Update various packages. Notable changes: - DeviceKit now detects iPad Pro (M4), iPad Air (M2) and iPhones 16. - KZFileWatchers isn't updated but now has a tag to pin to. * Try using a raw destination for tests. To fix an issue where CI isn't finding iPhone 16 when it's clearly listed by simctl.
419 lines
12 KiB
Ruby
419 lines
12 KiB
Ruby
require 'yaml'
|
|
require 'semantic'
|
|
|
|
enterprise = '../Enterprise/fastlane/Fastfile'
|
|
if File.exist?(enterprise)
|
|
import enterprise
|
|
end
|
|
|
|
before_all do
|
|
xcversion(version: "16.0")
|
|
|
|
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180"
|
|
ENV["FASTLANE_XCODE_LIST_TIMEOUT"] = "180"
|
|
ENV["SENTRY_LOG_LEVEL"] = "DEBUG"
|
|
end
|
|
|
|
lane :alpha do
|
|
app_store_connect_api_key(
|
|
key_id: ENV["APPSTORECONNECT_KEY_ID"],
|
|
issuer_id: ENV["APPSTORECONNECT_KEY_ISSUER_ID"],
|
|
key_content: ENV["APPSTORECONNECT_KEY_CONTENT"]
|
|
)
|
|
|
|
config_xcodegen_alpha()
|
|
|
|
code_signing_identity = "Apple Distribution: Vector Creations Limited (7J4U792NQT)"
|
|
|
|
app_provisioning_profile_name = "ElementX PR Ad Hoc"
|
|
app_bundle_identifier = "io.element.elementx.pr"
|
|
|
|
nse_provisioning_profile_name = "ElementX NSE PR Ad Hoc"
|
|
nse_bundle_identifier = "io.element.elementx.pr.nse"
|
|
|
|
update_code_signing_settings(
|
|
targets: ["ElementX"],
|
|
use_automatic_signing: false,
|
|
bundle_identifier: app_bundle_identifier,
|
|
profile_name: app_provisioning_profile_name,
|
|
code_sign_identity: code_signing_identity
|
|
)
|
|
|
|
get_provisioning_profile(
|
|
app_identifier: app_bundle_identifier,
|
|
provisioning_name: app_provisioning_profile_name,
|
|
ignore_profiles_with_different_name: true,
|
|
adhoc: true
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
targets: ["NSE"],
|
|
use_automatic_signing: false,
|
|
bundle_identifier: nse_bundle_identifier,
|
|
profile_name: nse_provisioning_profile_name,
|
|
code_sign_identity: code_signing_identity
|
|
)
|
|
|
|
get_provisioning_profile(
|
|
app_identifier: nse_bundle_identifier,
|
|
provisioning_name: nse_provisioning_profile_name,
|
|
ignore_profiles_with_different_name: true,
|
|
adhoc: true
|
|
)
|
|
|
|
build_ios_app(
|
|
scheme: "ElementX",
|
|
clean: true,
|
|
export_method: "ad-hoc",
|
|
output_directory: "build",
|
|
export_options: {
|
|
provisioningProfiles: {
|
|
app_bundle_identifier => app_provisioning_profile_name,
|
|
nse_bundle_identifier => nse_provisioning_profile_name
|
|
}
|
|
}
|
|
)
|
|
|
|
upload_to_diawi()
|
|
|
|
upload_to_browserstack()
|
|
end
|
|
|
|
lane :unit_tests do |options|
|
|
run_tests(
|
|
scheme: "UnitTests",
|
|
destination: "platform=iOS Simulator,name=iPhone 16,OS=18.0",
|
|
ensure_devices_found: true,
|
|
result_bundle: true,
|
|
number_of_retries: 3,
|
|
)
|
|
|
|
if !options[:skip_previews]
|
|
run_tests(
|
|
scheme: "PreviewTests",
|
|
destination: "platform=iOS Simulator,name=iPhone SE (3rd generation),OS=18.0",
|
|
ensure_devices_found: true,
|
|
result_bundle: true,
|
|
number_of_retries: 3,
|
|
)
|
|
end
|
|
|
|
# We use xcresultparser in the workflow to collect coverage from both result bundles.
|
|
end
|
|
|
|
lane :ui_tests do |options|
|
|
# Use a fresh simulator state to ensure hardware keyboard isn't attached.
|
|
# Not necessary when running on GitHub.
|
|
# reset_simulator_contents()
|
|
|
|
create_simulator_if_necessary(
|
|
name: "iPhone 16",
|
|
type: "com.apple.CoreSimulator.SimDeviceType.iPhone-16",
|
|
runtime: "com.apple.CoreSimulator.SimRuntime.iOS-18-0"
|
|
)
|
|
|
|
create_simulator_if_necessary(
|
|
name: "iPad (10th generation)",
|
|
type: "com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation",
|
|
runtime: "com.apple.CoreSimulator.SimRuntime.iOS-18-0"
|
|
)
|
|
|
|
if options[:test_name]
|
|
test_to_run = ["UITests/#{options[:test_name]}"]
|
|
else
|
|
test_to_run = nil
|
|
end
|
|
|
|
run_tests(
|
|
scheme: "UITests",
|
|
devices: ["iPhone 16", "iPad (10th generation)"],
|
|
ensure_devices_found: true,
|
|
prelaunch_simulator: true,
|
|
result_bundle: true,
|
|
only_testing: test_to_run,
|
|
number_of_retries: 3,
|
|
)
|
|
end
|
|
|
|
|
|
lane :integration_tests do
|
|
clear_derived_data()
|
|
|
|
create_simulator_if_necessary(
|
|
name: "iPhone 16 Pro",
|
|
type: "com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro",
|
|
runtime: "com.apple.CoreSimulator.SimRuntime.iOS-18-0"
|
|
)
|
|
|
|
run_tests(
|
|
scheme: "IntegrationTests",
|
|
destination: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.0",
|
|
ensure_devices_found: true,
|
|
result_bundle: true,
|
|
reset_simulator: true
|
|
)
|
|
end
|
|
|
|
lane :config_production do
|
|
config_secrets()
|
|
xcodegen(spec: "project.yml")
|
|
end
|
|
|
|
lane :config_nightly do |options|
|
|
build_number = options[:build_number]
|
|
UI.user_error!("Invalid build number.") unless !build_number.to_s.empty?
|
|
|
|
target_file_path = "../project.yml"
|
|
data = YAML.load_file target_file_path
|
|
data["include"].append({ "path" => "fastlane/nightly.yml" })
|
|
|
|
config_secrets()
|
|
|
|
File.open(target_file_path, 'w') { |f| YAML.dump(data, f) }
|
|
|
|
xcodegen(spec: "project.yml")
|
|
|
|
# Automatically done by Xcode Cloud. Cannot override
|
|
# https://developer.apple.com/documentation/xcode/setting-the-next-build-number-for-xcode-cloud-builds
|
|
# bump_build_number()
|
|
|
|
release_version = get_version_number(target: "ElementX")
|
|
|
|
update_app_icon(caption_text: "#{release_version} (#{build_number})", modulate: "100,20,100")
|
|
end
|
|
|
|
$sentry_retry=0
|
|
lane :upload_dsyms_to_sentry do |options|
|
|
auth_token = ENV["SENTRY_AUTH_TOKEN"]
|
|
UI.user_error!("Invalid Sentry Auth token.") unless !auth_token.to_s.empty?
|
|
|
|
dsym_path = options[:dsym_path]
|
|
UI.user_error!("Invalid DSYM path.") unless !dsym_path.to_s.empty?
|
|
|
|
begin
|
|
sentry_upload_dif(
|
|
auth_token: auth_token,
|
|
org_slug: 'element',
|
|
project_slug: 'element-x-ios',
|
|
url: 'https://sentry.tools.element.io/',
|
|
path: dsym_path,
|
|
)
|
|
rescue => exception
|
|
$sentry_retry += 1
|
|
|
|
if $sentry_retry <= 5
|
|
UI.message "Sentry failed, retrying."
|
|
upload_dsyms_to_sentry options
|
|
else
|
|
raise exception
|
|
end
|
|
end
|
|
end
|
|
|
|
lane :release_to_github do
|
|
api_token = ENV["GITHUB_TOKEN"]
|
|
UI.user_error!("Invalid GitHub API token.") unless !api_token.to_s.empty?
|
|
|
|
release_version = get_version_number(target: "ElementX")
|
|
|
|
github_release = set_github_release(
|
|
repository_name: "element-hq/element-x-ios",
|
|
api_token: api_token,
|
|
name: release_version,
|
|
tag_name: release_version,
|
|
is_generate_release_notes: true,
|
|
)
|
|
|
|
release_date = Date.today.strftime("%Y-%m-%d")
|
|
generated_notes = github_release["body"].gsub(/<!-- .*? -->/, '').gsub("### ", "\n").gsub("## ", "### ")
|
|
UI.user_error!("The generated release notes are missing!") unless !generated_notes.to_s.empty?
|
|
|
|
# Prepend the new release notes to the CHANGES.md file
|
|
changes_file = "../CHANGES.md"
|
|
File.open(changes_file, "r+") do |file|
|
|
content = file.read
|
|
file.rewind
|
|
file.write("## Changes in #{release_version} (#{release_date})#{generated_notes}\n\n#{content}")
|
|
end
|
|
|
|
# The changelog will be committed when prepare_next_release is called.
|
|
sh("git add #{changes_file}")
|
|
end
|
|
|
|
lane :prepare_next_release do
|
|
target_file_path = "../project.yml"
|
|
xcode_project_file_path = "../ElementX.xcodeproj"
|
|
|
|
data = YAML.load_file target_file_path
|
|
current_version = data["settings"]["MARKETING_VERSION"]
|
|
|
|
version = Semantic::Version.new(current_version)
|
|
|
|
new_version = version.increment!(:patch)
|
|
|
|
# Bump the patch version. The empty string after -i is so that sed doesn't
|
|
# create a backup file on macOS
|
|
sh("sed -i '' 's/MARKETING_VERSION: #{current_version}/MARKETING_VERSION: #{new_version.to_string}/g' #{target_file_path}")
|
|
|
|
xcodegen(spec: "project.yml")
|
|
|
|
setup_git()
|
|
|
|
sh("git add #{target_file_path} #{xcode_project_file_path}")
|
|
|
|
sh("git commit -m 'Prepare next release'")
|
|
|
|
git_push()
|
|
end
|
|
|
|
lane :tag_nightly do |options|
|
|
build_number = options[:build_number]
|
|
UI.user_error!("Invalid build number.") unless !build_number.to_s.empty?
|
|
|
|
xcodegen_project_file_path = "../project.yml"
|
|
data = YAML.load_file xcodegen_project_file_path
|
|
current_version = data["settings"]["MARKETING_VERSION"]
|
|
|
|
setup_git()
|
|
|
|
tag_name = "nightly/#{current_version}.#{build_number}"
|
|
sh("git tag #{tag_name}")
|
|
|
|
git_push(tag_name: tag_name)
|
|
end
|
|
|
|
private_lane :setup_git do
|
|
sh("git config --global user.name 'Element CI'")
|
|
sh("git config --global user.email 'ci@element.io'")
|
|
end
|
|
|
|
private_lane :git_push do |options|
|
|
# Use the Github API token for repo write access
|
|
api_token = ENV["GITHUB_TOKEN"]
|
|
UI.user_error!("Invalid GitHub API token.") unless !api_token.to_s.empty?
|
|
|
|
# Get repo url path, without `http`, `https` or `git@` prefixes or `.git` suffix
|
|
repo_url = sh("git ls-remote --get-url origin | sed 's#http://##g' | sed 's#https:\/\/##g' | sed 's#git@##g' | sed 's#.git##g'")
|
|
|
|
# This sometimes has a trailing newline
|
|
repo_url = repo_url.strip
|
|
|
|
# Push the tag separately if available
|
|
if options[:tag_name]
|
|
sh("git push https://#{api_token}@#{repo_url} #{options[:tag_name]}")
|
|
end
|
|
|
|
sh("git push https://#{api_token}@#{repo_url}")
|
|
end
|
|
|
|
private_lane :config_xcodegen_alpha do
|
|
target_file_path = "../project.yml"
|
|
data = YAML.load_file target_file_path
|
|
data["include"].append({ "path" => "fastlane/alpha.yml" })
|
|
File.open(target_file_path, 'w') { |f| YAML.dump(data, f) }
|
|
|
|
xcodegen(spec: "project.yml")
|
|
|
|
version = ENV["GITHUB_PR_NUMBER"]
|
|
|
|
update_app_icon(caption_text: "PR #{version}", modulate: "100,100,200")
|
|
|
|
bump_build_number()
|
|
end
|
|
|
|
private_lane :upload_to_diawi do
|
|
api_token = ENV["DIAWI_API_TOKEN"]
|
|
UI.user_error!("Invalid Diawi API token.") unless !api_token.to_s.empty?
|
|
|
|
# Upload to Diawi
|
|
diawi(
|
|
token: api_token,
|
|
wall_of_apps: false,
|
|
file: lane_context[SharedValues::IPA_OUTPUT_PATH]
|
|
)
|
|
|
|
# Get the Diawi link from Diawi action shared value
|
|
diawi_link = lane_context[SharedValues::UPLOADED_FILE_LINK_TO_DIAWI]
|
|
UI.command_output("Diawi link: " + diawi_link.to_s)
|
|
|
|
# Generate the Diawi QR code file link
|
|
diawi_app_id = URI(diawi_link).path.split('/').last
|
|
diawi_qr_code_link = "https://www.diawi.com/qrcode/link/#{diawi_app_id}"
|
|
|
|
# Set "DIAWI_FILE_LINK" to GitHub environment variables for Github actions
|
|
sh("echo DIAWI_FILE_LINK=#{diawi_link} >> $GITHUB_ENV")
|
|
sh("echo DIAWI_QR_CODE_LINK=#{diawi_qr_code_link} >> $GITHUB_ENV")
|
|
end
|
|
|
|
private_lane :upload_to_browserstack do
|
|
browserstack_username = ENV["BROWSERSTACK_USERNAME"]
|
|
UI.user_error!("Invalid BrowserStack username.") unless !browserstack_username.to_s.empty?
|
|
|
|
browserstack_access_key = ENV["BROWSERSTACK_ACCESS_KEY"]
|
|
UI.user_error!("Invalid BrowserStack access key.") unless !browserstack_access_key.to_s.empty?
|
|
|
|
upload_to_browserstack_app_automate(
|
|
browserstack_username: browserstack_username,
|
|
browserstack_access_key: browserstack_access_key,
|
|
file_path: lane_context[SharedValues::IPA_OUTPUT_PATH],
|
|
custom_id: "element-x-ios-pr"
|
|
)
|
|
end
|
|
|
|
private_lane :bump_build_number do
|
|
# Increment build number to current date
|
|
build_number = Time.now.strftime("%Y%m%d%H%M")
|
|
increment_build_number(build_number: build_number)
|
|
end
|
|
|
|
private_lane :update_app_icon do |options|
|
|
caption_text = options[:caption_text]
|
|
UI.user_error!("Invalid caption text.") unless !caption_text.to_s.empty?
|
|
|
|
modulate = options[:modulate]
|
|
UI.user_error!("Invalid icon modulate parameters.") unless !modulate.to_s.empty?
|
|
|
|
Dir.glob("../ElementX/Resources/Assets.xcassets/AppIcon.appiconset/**/*.png") do |file_name|
|
|
# Change the icons color
|
|
sh("convert '#{file_name}' -modulate #{modulate} '#{file_name}'")
|
|
|
|
image_width = sh("identify -format %w '#{file_name}'")
|
|
image_height = sh("identify -format %h '#{file_name}'").to_i
|
|
caption_height = image_height / 5
|
|
|
|
# Add a label on top
|
|
sh("convert -background '#0008' -fill white -gravity center -size '#{image_width}'x'#{caption_height}' caption:'#{caption_text}' '#{file_name}' +swap -gravity south -composite '#{file_name}'")
|
|
end
|
|
end
|
|
|
|
private_lane :create_simulator_if_necessary do |options|
|
|
simulator_name = options[:name]
|
|
UI.user_error!("Invalid simulator name") unless !simulator_name.to_s.empty?
|
|
|
|
simulator_type = options[:type]
|
|
UI.user_error!("Invalid simulator type") unless !simulator_type.to_s.empty?
|
|
|
|
simulator_runtime = options[:runtime]
|
|
UI.user_error!("Invalid simulator runtime") unless !simulator_runtime.to_s.empty?
|
|
|
|
|
|
# Use a `(` here to avoid matching `iPhone 14 Pro` on `iPhone 14 Pro Max` for example
|
|
begin sh("xcrun simctl list devices | grep '#{simulator_name} ('")
|
|
UI.success "Simulator already exists"
|
|
rescue
|
|
sh("xcrun simctl create '#{simulator_name}' #{simulator_type} #{simulator_runtime}")
|
|
end
|
|
end
|
|
|
|
private_lane :config_secrets do
|
|
maplibre_api_key = ENV["MAPLIBRE_API_KEY"]
|
|
UI.user_error!("Invalid Map Libre API key.") unless !maplibre_api_key.to_s.empty?
|
|
|
|
set_xcconfig_value(
|
|
path: './ElementX/SupportingFiles/secrets.xcconfig',
|
|
name: 'MAPLIBRE_API_KEY',
|
|
value: maplibre_api_key,
|
|
mask_value: true
|
|
)
|
|
end
|