Mengapa Fastlane di Mac Dedikasi?
Fastlane adalah alat otomatisasi standar untuk pengembangan iOS dan Android. Ia menangani segalanya mulai dari code signing hingga deployment TestFlight. Menjalankan Fastlane di Mac Mini M4 dedikasi memberi Anda:
Sertifikat code signing bertahan di antara eksekusi. Tidak perlu impor/ekspor pada setiap build.
DerivedData persisten, jadi fastlane build memakan waktu 2-4 menit alih-alih 15+.
Jalankan fastlane snapshot dengan simulator asli untuk semua ukuran perangkat.
Server Mac dedikasi yang stabil berarti lebih sedikit deployment yang tidak stabil dan masalah code signing.
Instal Fastlane
SSH ke server build Mac mini M4 khusus Anda dan instal Fastlane. Untuk CI, kami merekomendasikan Bundler: Gemfile mengunci versi Fastlane sehingga setiap run memakai versi yang sama, sedangkan Homebrew cocok untuk penggunaan lokal. Hindari Ruby sistem macOS: rilis Fastlane saat ini memerlukan Ruby 3.1 atau yang lebih baru.
Opsi A: Bundler dan Gemfile (Direkomendasikan untuk CI)
# Ruby 3.1 or later (current Fastlane releases need it; the macOS system Ruby is older)
# Homebrew not installed yet? Install it first, see Option B.
brew install ruby
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zprofile
export PATH="/opt/homebrew/opt/ruby/bin:$PATH"
# In your project, a Gemfile that pins Fastlane
cd /path/to/your/ios-project
cat > Gemfile <<'EOF'
source "https://rubygems.org"
gem "fastlane", "~> 2.240"
gem "cocoapods" # if using CocoaPods
EOF
bundle install
# Commit Gemfile and Gemfile.lock, then always run Fastlane through Bundler
bundle exec fastlane --version
Opsi B: Instal melalui Homebrew (Penggunaan Lokal)
# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Install Fastlane
brew install fastlane
# Verify installation
fastlane --version
Inisialisasi Fastlane di Proyek Anda
# Navigate to your project directory
cd /path/to/your/ios-project
# Initialize Fastlane (with the Homebrew install, run: fastlane init)
bundle exec fastlane init
# Choose option 4: "Manual setup"
# This creates the fastlane/ directory with Appfile and Fastfile
Konfigurasikan Match untuk Code Signing
Fastlane Match menyimpan sertifikat code signing dan provisioning profile Anda di repositori Git privat atau penyimpanan cloud. Ini memastikan semua mesin (dan anggota tim) menggunakan identitas penandatanganan yang sama.
Inisialisasi Match
# Initialize match (choose "git" for storage)
fastlane match init
# This creates fastlane/Matchfile
Konfigurasikan Matchfile
# fastlane/Matchfile
git_url("https://github.com/your-org/ios-certificates.git")
storage_mode("git")
type("appstore") # default type, can be overridden per lane
app_identifier(["com.yourcompany.myapp"])
username("your-apple-id@example.com")
# For CI environments, use App Store Connect API key instead of username/password
# api_key_path("fastlane/AuthKey.json")
Hasilkan Sertifikat
# Generate development certificates and profiles
fastlane match development
# Generate App Store distribution certificates and profiles
fastlane match appstore
# For ad-hoc distribution
fastlane match adhoc
# On CI, use readonly mode to avoid accidentally creating new certs
fastlane match appstore --readonly
Buat Fastfile (Lane Build, Test, Deploy)
Berikut Fastfile lengkap dengan lane untuk mem-build, menguji, dan men-deploy aplikasi iOS Anda:
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
# ---- SHARED ----
before_all do
setup_ci if ENV['CI'] # Configures keychain for CI environments
end
# ---- BUILD ----
desc "Build the app for testing"
lane :build do
match(type: "development", readonly: true)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
configuration: "Debug",
destination: "generic/platform=iOS Simulator",
# Outside the checkout, so it survives the next git clean
derived_data_path: File.expand_path("~/DerivedData/MyApp"),
skip_archive: true,
skip_codesigning: true
)
end
# ---- TEST ----
desc "Run all unit and UI tests"
lane :test do
run_tests(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
devices: ["iPhone 17"], # any device from: xcrun simctl list devices available
derived_data_path: File.expand_path("~/DerivedData/MyApp"),
result_bundle: true,
output_directory: "fastlane/test_results",
parallel_testing: true,
concurrent_workers: 4
)
end
# ---- BETA ----
desc "Build and push a new beta to TestFlight"
lane :beta do
# Ensure we are on a clean git state
ensure_git_status_clean
# Fetch App Store certificates
match(type: "appstore", readonly: true)
# Increment build number (the API key avoids an Apple ID login and 2FA)
increment_build_number(
build_number: latest_testflight_build_number(
api_key_path: "fastlane/AuthKey.json"
) + 1
)
# Build the app
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
derived_data_path: File.expand_path("~/DerivedData/MyApp"),
output_directory: "fastlane/builds"
)
# Upload to TestFlight
upload_to_testflight(
skip_waiting_for_build_processing: true,
api_key_path: "fastlane/AuthKey.json"
)
# Commit the version bump
commit_version_bump(
message: "chore: bump build number [skip ci]",
force: true
)
# Tag the release
add_git_tag(
tag: "beta/#{lane_context[SharedValues::BUILD_NUMBER]}"
)
push_to_git_remote
end
# ---- RELEASE ----
desc "Build and submit to App Store Review"
lane :release do
match(type: "appstore", readonly: true)
# Increment version number (patch)
increment_version_number(bump_type: "patch")
increment_build_number(
build_number: latest_testflight_build_number(
api_key_path: "fastlane/AuthKey.json"
) + 1
)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
derived_data_path: File.expand_path("~/DerivedData/MyApp")
)
upload_to_app_store(
submit_for_review: true,
automatic_release: false,
api_key_path: "fastlane/AuthKey.json",
precheck_include_in_app_purchases: false,
force: true # skip the HTML preview prompt, which fails in non-interactive CI
)
commit_version_bump(message: "chore: release #{lane_context[SharedValues::VERSION_NUMBER]}")
add_git_tag
push_to_git_remote
end
# ---- ERROR HANDLING ----
error do |lane, exception|
# Send notification on failure (Slack, email, etc.)
# slack(
# message: "Lane #{lane} failed: #{exception.message}",
# success: false
# )
end
end
Siapkan Tangkapan Layar Otomatis
Fastlane Snapshot menangkap tangkapan layar App Store di berbagai perangkat dan bahasa secara otomatis. Di Mac dedikasi, ini berjalan andal tanpa bersaing untuk sumber daya.
# Initialize snapshot
fastlane snapshot init
# This creates:
# - fastlane/Snapfile
# - fastlane/SnapshotHelper.swift (add to UI test target)
Konfigurasikan Snapfile
# fastlane/Snapfile
# Simulator names from: xcrun simctl list devices available
devices([
"iPhone 17 Pro Max",
"iPhone 17",
"iPad Pro 13-inch (M5)"
])
languages([
"en-US",
"fr-FR",
"de-DE",
"ja"
])
scheme("MyAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)
# Speed up by running in parallel
concurrent_simulators(true)
Tambahkan Snapshot ke UI Test Anda
// In your XCUITest file:
import XCTest
class ScreenshotTests: XCTestCase {
override func setUp() {
continueAfterFailure = false
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
}
func testHomeScreen() {
snapshot("01_HomeScreen")
}
func testDetailScreen() {
let app = XCUIApplication()
app.cells.firstMatch.tap()
snapshot("02_DetailScreen")
}
func testSettings() {
let app = XCUIApplication()
app.tabBars.buttons["Settings"].tap()
snapshot("03_Settings")
}
}
Tambahkan Lane Tangkapan Layar
# Add to your Fastfile:
desc "Capture App Store screenshots"
lane :screenshots do
capture_screenshots
frame_screenshots(white: true) # Add device frames
upload_to_app_store(
skip_binary_upload: true,
skip_metadata: true,
api_key_path: "fastlane/AuthKey.json",
force: true # skip the HTML preview prompt when run in CI
)
end
Deploy ke TestFlight
Untuk lingkungan CI, gunakan kunci API App Store Connect alih-alih kredensial Apple ID Anda. Ini menghindari prompt 2FA di server headless.
Buat Kunci API
- Buka App Store Connect > Users and Access > Integrations > App Store Connect API
- Klik tombol + untuk menghasilkan kunci API baru
- Pilih peran App Manager
- Unduh file
.p8 - Catat Key ID dan Issuer ID
Buat JSON Kunci API
# fastlane/AuthKey.json
{
"key_id": "YOUR_KEY_ID",
"issuer_id": "YOUR_ISSUER_ID",
"key": "-----BEGIN PRIVATE KEY-----\nYOUR_P8_KEY_CONTENT\n-----END PRIVATE KEY-----",
"in_house": false
}
# IMPORTANT: Add this to .gitignore!
echo "fastlane/AuthKey.json" >> .gitignore
Jalankan Deployment
# Deploy to TestFlight
fastlane beta
# Or run the full release pipeline
fastlane release
Integrasikan dengan CI/CD
Fastlane berintegrasi mulus dengan platform CI/CD apa pun. Berikut contoh GitHub Actions menggunakan self-hosted runner Mac Mini M4 Anda:
# .github/workflows/deploy.yml
name: Deploy to TestFlight
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: [self-hosted, macOS, ARM64, M4]
# push_to_git_remote needs write access: the token is read-only by default
permissions:
contents: write
env:
# Xcode for this job, without sudo (path from `xcodes installed`)
DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
# base64 of "github_username:personal_access_token", for example:
# echo -n your_github_username:your_personal_access_token | base64
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use the Homebrew Ruby (3.1 or later)
run: echo "/opt/homebrew/opt/ruby/bin" >> "$GITHUB_PATH"
- name: Set up App Store Connect API Key
run: |
mkdir -p fastlane
echo '${{ secrets.APP_STORE_CONNECT_API_KEY }}' > fastlane/AuthKey.json
- name: Install dependencies
run: |
bundle install
pod install # if using CocoaPods
- name: Deploy to TestFlight
run: bundle exec fastlane beta
- name: Clean up API key
if: always()
run: rm -f fastlane/AuthKey.json
Praktik Terbaik
Tambahkan Gemfile dengan versi Fastlane yang disematkan. Jalankan bundle exec fastlane untuk memastikan versi yang konsisten di semua lingkungan.
Kunci API menghindari prompt 2FA dan lebih aman untuk lingkungan CI.
match --readonly di CI
Mencegah pembuatan sertifikat baru secara tidak sengaja. Hanya hasilkan sertifikat secara manual saat diperlukan.
setup_ci di lingkungan CI
Ini membuat keychain sementara untuk menghindari pengotoran keychain sistem dan mencegah dialog izin keychain.
Di Mac dedikasi, tentukan derived_data_path untuk menggunakan kembali artifact build di antara eksekusi.