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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 32 additions & 20 deletions .github/actions/setup-build-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ inputs:
keychain-password:
description: "Keychain password"
required: true
include-pulse:
description: "Whether to include Pulse in cache (false for production builds)"
required: false
default: "true"
skip-spm-cache:
description: "Skip SPM cache for release builds to avoid simulator artifacts"
required: false
Expand All @@ -33,6 +29,32 @@ runs:
security list-keychains -d user -s "$KEYCHAIN_NAME" "$(security default-keychain | tr -d '"')"
security default-keychain -s "$KEYCHAIN_NAME"

- name: 🧰 Select Xcode
shell: bash
run: |
set -euo pipefail
XCODE_APP="/Applications/Xcode_26.4.1.app"
if [ -d "$XCODE_APP" ]; then
sudo xcode-select -s "$XCODE_APP/Contents/Developer"
else
echo "::notice::$XCODE_APP is not installed. Falling back to the runner default Xcode."
fi

- name: 🧰 Verify Xcode SDK
shell: bash
run: |
set -euo pipefail
xcodebuild -version
SDK_VERSION="$(xcrun --sdk iphoneos --show-sdk-version)"
echo "iPhoneOS SDK: $SDK_VERSION"
case "$SDK_VERSION" in
26.*|[3-9][0-9].*) ;;
*)
echo "::error::iPhoneOS SDK $SDK_VERSION is too old for App Store Connect upload. Use a runner with Xcode 26 or later."
exit 1
;;
esac

- name: 💫 Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand All @@ -44,13 +66,13 @@ runs:
uses: actions/cache@v4
with:
path: ~/.tuist-bin
key: tuist-binary-${{ runner.os }}-4.118.0
key: tuist-binary-${{ runner.os }}-4.194.0

- name: 🛠 Install Tuist (manual binary)
if: steps.tuist-binary-cache.outputs.cache-hit != 'true'
shell: bash
run: |
TUIST_VERSION=4.118.0
TUIST_VERSION=4.194.0
mkdir -p ~/.tuist-bin
curl -L "https://github.com/tuist/tuist/releases/download/${TUIST_VERSION}/tuist.zip" -o tuist.zip
unzip -o tuist.zip -d ~/.tuist-bin
Expand All @@ -66,9 +88,9 @@ runs:
uses: actions/cache@v4
with:
path: Tuist/.build
key: tuist-spm-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }}
key: tuist-spm-xcode26-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }}
restore-keys: |
tuist-spm-${{ runner.os }}-
tuist-spm-xcode26-${{ runner.os }}-

- name: 🛠 Install tuist dependencies
if: inputs.skip-spm-cache == 'true' || steps.spm-cache.outputs.cache-hit != 'true'
Expand All @@ -81,17 +103,7 @@ runs:
if [ "${{ inputs.skip-spm-cache }}" == "true" ]; then
echo "⏭️ SPM cache SKIPPED (release build)"
elif [ "${{ steps.spm-cache.outputs.cache-hit }}" == "true" ]; then
echo "✅ SPM cache HIT - skipping tuist cache warm up"
echo "✅ SPM cache HIT - using restored Tuist/.build"
else
echo "❌ SPM cache MISS - will build dependencies"
echo "❌ SPM cache MISS - dependencies will be compiled from source"
fi

- name: 🏗 Warm up external dependencies (with Pulse)
if: inputs.skip-spm-cache != 'true' && steps.spm-cache.outputs.cache-hit != 'true' && inputs.include-pulse == 'true'
shell: bash
run: tuist cache ComposableArchitecture Kingfisher Pulse KakaoSDKAuth KakaoSDKCommon GoogleSignIn GoogleSignInSwift

- name: 🏗 Warm up external dependencies (without Pulse)
if: inputs.skip-spm-cache != 'true' && steps.spm-cache.outputs.cache-hit != 'true' && inputs.include-pulse != 'true'
shell: bash
run: tuist cache ComposableArchitecture Kingfisher KakaoSDKAuth KakaoSDKCommon GoogleSignIn GoogleSignInSwift
2 changes: 1 addition & 1 deletion .github/workflows/cd_develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ concurrency:

jobs:
deploy_develop:
runs-on: macos-15
runs-on: macos-26
steps:
- name: 🔄 Checkout source code
uses: actions/checkout@v4
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/cd_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ concurrency:

jobs:
deploy_main:
runs-on: macos-15
runs-on: macos-26
steps:
- name: 🔄 Checkout source code
uses: actions/checkout@v4
Expand All @@ -22,7 +22,6 @@ jobs:
with:
keychain-name: ${{ secrets.KEYCHAIN_NAME }}
keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}
include-pulse: "false"
skip-spm-cache: "true"

- name: 🚀 Deploy to App Store Connect (Twix)
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ concurrency:

jobs:
build:
runs-on: macos-15
runs-on: macos-26
steps:
- name: 🔄 Checkout source code
uses: actions/checkout@v4
Expand All @@ -24,9 +24,9 @@ jobs:
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData
key: deriveddata-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }}
key: deriveddata-xcode26-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }}
restore-keys: |
deriveddata-${{ runner.os }}-
deriveddata-xcode26-${{ runner.os }}-

- name: 🧪 Run PR Checks (build + test)
env:
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ init:
mise exec node@24 -- node -v
mise use --global node@24 go@1
mise install tuist
mise use tuist@4.115.1
mise use tuist@4.194.0

clean:
tuist clean
Expand All @@ -15,7 +15,7 @@ clean:

generate:
tuist install
tuist generate
tuist generate --cache-profile none

module:
swift Scripts/GenerateModule.swift
Expand Down
22 changes: 11 additions & 11 deletions Tuist/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 3 additions & 8 deletions Tuist/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,19 @@ import PackageDescription
"Pulse": .framework,
"KakaoSDK": .staticLibrary,
"GoogleSignIn": .staticLibrary,
"GoogleSignInSwift": .staticLibrary,
"FirebaseCore": .staticLibrary,
"FirebaseAnalytics": .staticLibrary,
"FirebaseRemoteConfig" : .staticLibrary,
"FirebaseMessaging": .staticLibrary,
"FirebaseCrashlytics": .staticLibrary
"GoogleSignInSwift": .staticLibrary
]
)
#endif

let package = Package(
name: "Twix",
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.23.1"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.23.2"),
.package(url: "https://github.com/onevcat/Kingfisher", from: "8.0.0"),
.package(url: "https://github.com/kean/Pulse", from: "5.1.4"),
.package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.27.1"),
.package(url: "https://github.com/google/GoogleSignIn-iOS", from: "9.1.0"),
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "12.9.0")
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "12.13.0")
]
)
53 changes: 38 additions & 15 deletions Tuist/ProjectDescriptionHelpers/Scripts/CrashlyticsScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,50 @@ import ProjectDescription
public extension TargetScript {
/// Firebase Crashlytics dSYM 업로드 스크립트입니다.
///
/// Archive 빌드 시 생성된 dSYM을 Firebase Console에 업로드해 크래시 스택을 심볼화합니다.
/// SPM 기준 경로를 사용하며, `basedOnDependencyAnalysis: false`로 항상 실행됩니다.
/// Tuist는 SPM 패키지를 Tuist/.build/checkouts/에 관리합니다.
/// `run` 대신 `upload-symbols`를 직접 호출해 dSYM 경로와 GoogleService-Info.plist를 명시합니다.
/// 로컬 Archive 빌드 시 생성된 dSYM 전부를 Firebase Console로 업로드해 크래시 스택을
/// 심볼화합니다. 앱뿐 아니라 `$DWARF_DSYM_FOLDER_PATH` 안의 모든 `.dSYM`(Firebase,
/// GoogleSignIn, Kakao 등 framework 포함)을 순회합니다.
///
/// CI 환경(`$CI == "true"`)에선 Fastlane `upload_symbols_to_crashlytics`가 동일 작업을
/// 더 신뢰성 있게 수행하므로 중복 방지를 위해 즉시 종료합니다.
static let crashlyticsUploadSymbols = TargetScript.post(
script: """
UPLOAD_SYMBOLS=$(find "$SRCROOT" -name "upload-symbols" -path "*/Crashlytics/*" 2>/dev/null | head -1)
if [ -z "$UPLOAD_SYMBOLS" ]; then
UPLOAD_SYMBOLS=$(find "$SRCROOT/../.." -maxdepth 6 -name "upload-symbols" -path "*/Crashlytics/*" 2>/dev/null | head -1)
script: #"""
if [ "${CI:-}" = "true" ]; then
echo "note: CI environment detected — dSYM upload handled by Fastlane (skipping build-phase upload)"
exit 0
fi

if [ ! -d "$DWARF_DSYM_FOLDER_PATH" ]; then
echo "note: $DWARF_DSYM_FOLDER_PATH not found (DEBUG_INFORMATION_FORMAT may not be dwarf-with-dsym) — skipping"
exit 0
fi

UPLOAD_SYMBOLS="$SRCROOT/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols"
if [ ! -x "$UPLOAD_SYMBOLS" ]; then
UPLOAD_SYMBOLS=$(find "$SRCROOT/../.." -maxdepth 6 -name "upload-symbols" -path "*/firebase-ios-sdk/Crashlytics/*" 2>/dev/null | head -1)
fi
if [ -z "$UPLOAD_SYMBOLS" ] || [ ! -x "$UPLOAD_SYMBOLS" ]; then
echo "warning: Firebase Crashlytics upload-symbols not found. Run 'tuist install'."
exit 0
fi

if [ -z "$UPLOAD_SYMBOLS" ]; then
echo "warning: Firebase Crashlytics upload-symbols not found. Run tuist install."
GSP="$SRCROOT/Resources/GoogleService-Info.plist"
if [ ! -f "$GSP" ]; then
echo "warning: GoogleService-Info.plist not found at $GSP — skipping"
exit 0
fi

"$UPLOAD_SYMBOLS" \
-gsp "$SRCROOT/Resources/GoogleService-Info.plist" \
-p ios \
"$DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME"
""",
FAIL=0
for DSYM in "$DWARF_DSYM_FOLDER_PATH"/*.dSYM; do
[ -e "$DSYM" ] || continue
echo "Uploading dSYM: $DSYM"
if ! "$UPLOAD_SYMBOLS" -gsp "$GSP" -p ios "$DSYM"; then
echo "warning: upload-symbols failed for $DSYM"
FAIL=1
fi
done
exit $FAIL
"""#,
name: "Firebase Crashlytics",
basedOnDependencyAnalysis: false
)
Expand Down
21 changes: 19 additions & 2 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,23 @@ platform :ios do
lane :generate do
UI.message("🏗️ Generating Tuist project...")

# --cache-profile none: SPM 의존성을 source에서 컴파일.
# Tuist의 binary cache(~/.cache/tuist/Binaries)가 채워져 있을 때
# 다수의 XCFramework 경로가 link line에 누적되어 ARG_MAX 초과로
# `Argument list too long` 빌드 실패가 발생하는 것을 방지.
if ENV["CI"] == "true"
sh("cd .. && tuist generate")
sh("cd .. && tuist generate --no-open --cache-profile none")
else
sh("cd .. && make generate")
end

UI.success("✅ Tuist project generated!")

UI.message("📋 Available schemes:")
sh("xcodebuild -workspace ../#{WORKSPACE_NAME} -list")
end


desc "Build all modules (without main app)"
lane :build_modules do
UI.message("🔨 Building all modules...")
Expand Down Expand Up @@ -190,6 +195,13 @@ platform :ios do
}
)

# build_app가 lane_context[SharedValues::DSYM_OUTPUT_PATH]에 모든 framework dSYM을 모은
# zip 경로를 채워둠 — upload_symbols_to_crashlytics가 그걸 자동 사용.
upload_symbols_to_crashlytics(
gsp_path: "Projects/App/Resources/GoogleService-Info.plist",
binary_path: "Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols"
)

upload_to_testflight(
api_key: api_key,
app_identifier: BUNDLE_ID,
Expand Down Expand Up @@ -228,6 +240,11 @@ platform :ios do
}
)

upload_symbols_to_crashlytics(
gsp_path: "Projects/App/Resources/GoogleService-Info.plist",
binary_path: "Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols"
)

upload_to_testflight(
api_key: api_key,
app_identifier: BUNDLE_ID,
Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[tools]
tuist = "4.118.0"
tuist = "4.194.0"
Loading