Skip to content
Closed
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,13 @@ mobile/bin/
mobile/lib/
mobile/build/
scripts/node_modules/

# fastlane
mobile/fastlane/.gems/
mobile/fastlane/vendor/bundle/
mobile/fastlane/.bundle/
mobile/fastlane/report.xml
mobile/fastlane/Preview.html
mobile/fastlane/screenshots/
mobile/fastlane/test_output/
mobile/fastlane/result
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.android
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down
7 changes: 6 additions & 1 deletion ci/Jenkinsfile.combined
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env groovy

library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Object to store public URLs for description. */
urls = [:]
Expand Down Expand Up @@ -113,6 +113,11 @@ pipeline {
'MacOS/aarch64', jenkins.Build('status-app/systems/macos/aarch64/package')
)
} } }
stage('iOS/aarch64') { steps { script {
ios_aarch64 = getArtifacts(
'iOS/aarch64', jenkins.Build('status-app/systems/ios/arm64/package')
)
} } }
}
}
stage('Publish') {
Expand Down
21 changes: 14 additions & 7 deletions ci/Jenkinsfile.ios
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down Expand Up @@ -72,11 +72,8 @@ pipeline {
APP_TYPE = "${utils.getAppType()}"
/* iOS build configuration */
IPHONE_SDK = "iphoneos"
ARCH = "x86_64"
/* iOS app paths */
ARCH = "arm64"
STATUS_IOS_APP_ARTIFACT = "pkg/${utils.pkgFilename(ext: 'ipa', arch: getArch(), version: env.VERSION, type: env.APP_TYPE)}"
STATUS_IOS_APP = "${WORKSPACE}/mobile/bin/ios/qt6/Status.app"
STATUS_IOS_IPA = "${WORKSPACE}/mobile/bin/ios/qt6/Status.ipa"
TESTFLIGHT_POLL_TIMEOUT = "${params.TESTFLIGHT_POLL_TIMEOUT}"
TESTFLIGHT_POLL_INTERVAL = "${params.TESTFLIGHT_POLL_INTERVAL}"
}
Expand Down Expand Up @@ -112,14 +109,15 @@ pipeline {
stage('Package iOS App') {
steps {
sh 'mkdir -p pkg'
sh "cp ${env.STATUS_IOS_IPA} ${env.STATUS_IOS_APP_ARTIFACT}"
sh "cp ${WORKSPACE}/mobile/bin/ios/qt6/Status${utils.isReleaseBuild() ? '' : 'PR'}.ipa ${env.STATUS_IOS_APP_ARTIFACT}"
sh "ls -lh ${env.STATUS_IOS_APP_ARTIFACT}"
}
}

stage('Parallel Upload') {
parallel {
stage('Upload to TestFlight') {
when { expression { utils.isReleaseBuild() } }
steps {
script {
def changelog = sh(script: './scripts/generate-changelog.sh', returnStdout: true).trim()
Expand All @@ -133,11 +131,20 @@ pipeline {
}
}
}
stage('Upload to Diawi') {
when { expression { !utils.isReleaseBuild() } }
steps {
script {
def comment = "status-desktop PR build ${env.VERSION} ${git.commit(8)}"
env.DIAWI_URL = app.uploadToDiawi(env.STATUS_IOS_APP_ARTIFACT, comment)
jenkins.setBuildDesc(IPA: env.DIAWI_URL)
}
}
}
stage('Upload to S3') {
steps {
script {
env.PKG_URL = s5cmd.upload(env.STATUS_IOS_APP_ARTIFACT)
jenkins.setBuildDesc(IPA: env.PKG_URL)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.linux
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.linux-nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env groovy

library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.macos
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.qt-build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

pipeline {
agent {
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.tests-e2e
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

pipeline {
agent {
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.tests-e2e.windows
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

pipeline {

Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.tests-nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down
2 changes: 1 addition & 1 deletion ci/Jenkinsfile.tests-ui
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down
3 changes: 2 additions & 1 deletion ci/Jenkinsfile.windows
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.31'
library 'status-jenkins-lib@v1.9.32'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand Down Expand Up @@ -96,6 +96,7 @@ pipeline {
USE_MOCKED_KEYCARD_LIB = "${params.USE_MOCKED_KEYCARD_LIB}"
/* FIXME: figure out why NIMFLAGS are not respected */
XDG_CACHE_HOME = "${env.WORKSPACE_TMP}/.cache"
NIM_SDS_SOURCE_DIR = "${env.WORKSPACE_TMP}/nim-sds".replace('\\', '/')
}

stages {
Expand Down
7 changes: 7 additions & 0 deletions ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ It also expects the presence of the following credentials:
* `macos-keychain-file` - Keychain file with the MacOS signing certificate.

You can read about how to create such a keychain [here](https://github.com/status-im/infra-docs/blob/master/articles/macos_signing_keychain.md).

## iOS

iOS builds use **fastlane** with **match** for code signing management.

For detailed documentation on iOS signing, fastlane configuration, and certificate management, see [`mobile/fastlane/README.md`](../mobile/fastlane/README.md).

7 changes: 7 additions & 0 deletions mobile/fastlane/Appfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# App identifiers for Status App iOS builds
app_identifier("app.status.mobile")
team_id("8B5X2M6H2Y")

for_lane :pr do
app_identifier("app.status.mobile.pr")
end
178 changes: 178 additions & 0 deletions mobile/fastlane/Fastfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# This file defines the signing and packaging lanes for iOS
# Building is done separately via make targets

default_platform(:ios)

# Build configuration
APP_NAME_RELEASE = "Status.app"
APP_NAME_PR = "StatusPR.app"
DISPLAY_NAME_RELEASE = "Status"
DISPLAY_NAME_PR = "Status PR"
PROJECT_DIR = File.expand_path("../", __dir__)
BUILD_DIR = File.join(PROJECT_DIR, "bin", "ios", "qt6")

platform :ios do
before_all do
UI.message("Project directory: #{PROJECT_DIR}")
UI.message("Build directory: #{BUILD_DIR}")
end

after_all do
# Clean up CI keychain after signing
if is_ci
delete_keychain(name: keychain_name) rescue nil
end
end

error do
# Clean up CI keychain on failure too
if is_ci
delete_keychain(name: keychain_name) rescue nil
end
end

# ============================================
# PR Builds - Sign and package for ad-hoc distribution
# ============================================
desc "Sign and package iOS app for PRs"
lane :pr do
setup_ci_keychain

run_match(type: "adhoc")

resign_and_package(
app_name: APP_NAME_PR,
display_name: DISPLAY_NAME_PR,
profile_type: "adhoc"
)
end

# ============================================
# Release Builds - Sign and package for App Store
# ============================================
desc "Sign and package iOS app for release"
lane :release do
setup_ci_keychain

run_match(type: "appstore")

resign_and_package(
app_name: APP_NAME_RELEASE,
display_name: DISPLAY_NAME_RELEASE,
profile_type: "appstore"
)
end

# ============================================
# Helper Methods
# ============================================

private_lane :setup_ci_keychain do
if is_ci
create_keychain(
name: keychain_name,
password: keychain_password,
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
end
end

private_lane :run_match do |options|
match_params = {
type: options[:type],
readonly: false,
# Auto-regenerate profiles when new devices are registered (for dev and adhoc)
force_for_new_devices: options[:type] == "adhoc"
}

# Only specify keychain params in CI where we create a custom keychain
if is_ci
match_params[:keychain_name] = keychain_name
match_params[:keychain_password] = keychain_password
end

match(match_params)
end

private_lane :resign_and_package do |options|
app_identifier = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
app_name = options[:app_name] || "Status.app"
display_name = options[:display_name] || "Status"
profile_type = options[:profile_type]

app_path = File.join(BUILD_DIR, app_name)
ipa_name = app_name.sub(".app", ".ipa")
ipa_path = File.join(BUILD_DIR, ipa_name)

unless File.exist?(app_path)
UI.user_error!("#{app_name} not found at #{app_path}")
end

# Get signing identity and provisioning profile from match
signing_identity = ENV["sigh_#{app_identifier}_#{profile_type}_certificate-name"]
provisioning_profile = ENV["sigh_#{app_identifier}_#{profile_type}_profile-path"]

UI.message("Signing identity: #{signing_identity}")
UI.message("Provisioning profile: #{provisioning_profile}")

unless provisioning_profile && File.exist?(provisioning_profile)
UI.user_error!("Provisioning profile not found!")
end

unless signing_identity
UI.user_error!("Signing identity not found!")
end

UI.message("Creating unsigned IPA...")
FileUtils.rm_f(ipa_path)

Dir.mktmpdir do |tmpdir|
payload_dir = File.join(tmpdir, "Payload")
FileUtils.mkdir_p(payload_dir)
FileUtils.cp_r(app_path, payload_dir)

Dir.chdir(tmpdir) do
sh("zip -r '#{ipa_path}' Payload")
end
end

# Clean up any stale temp directory from previous resign attempts
# https://github.com/fastlane/fastlane/blob/512a047abd596a5c2c0e0156e9f52e111552a727/sigh/lib/assets/resign.sh#L177C1-L177C9
FileUtils.rm_rf("_floatsignTemp")

# Call resign.sh directly with /bin/bash instead of using the resign action
# Ruby backticks use /bin/sh which doesn't support bash process substitution < <(...)
# that resign.sh uses for processing nested apps/extensions
resign_sh = File.join(Sigh::ROOT, 'lib', 'assets', 'resign.sh')

UI.message("Resigning IPA with resign.sh directly via bash...")
command = [
resign_sh.shellescape,
ipa_path.shellescape,
signing_identity.shellescape,
"-p", "#{app_identifier}=#{provisioning_profile}".shellescape,
"-d", display_name.shellescape,
"-b", app_identifier.shellescape,
"-v",
ipa_path.shellescape # output path (same as input, overwrites)
].join(' ')

UI.message("Command: #{command}")

# Use sh() which properly executes via the shell, wrapped in bash -c
sh("/bin/bash", "-c", command)

UI.success("Signed and packaged: #{ipa_path}")
end

def keychain_name
"status_ci_#{ENV['BUILD_NUMBER'] || 'local'}.keychain"
end

def keychain_password
ENV["MATCH_PASSWORD"]
end
end
7 changes: 7 additions & 0 deletions mobile/fastlane/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
source "https://rubygems.org"

# Core dependencies
gem "fastlane", "~> 2.225"

plugins_path = File.join(File.dirname(__FILE__), 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
Loading