프로젝트를 관리할 때 fastlane을 도입해서 배포 과정을 자동화해놓는 편인데, 보통 staging과 production 환경을 나누고 version을 조합해서 사용했다.
fastlane staging version:patch
fastlane production version:minor
fastlane production version:2.1.1
하지만 이대로 쓰기에는 꽤나 불편했다. ios / android 각각의 플랫폼마다 독립적으로 호출해주어야 했고, 환경과 버전을 매번 명시해줘야했기 때문이다. 가령 하나의 피쳐 개발을 끝내고 patch 버전을 올려 배포하고자하면 다음 과정을 거쳐야만 했다.
cd ios
fastlane staging version:patch
fastlane production version:maintain
cd ..
cd android
fastlane staging version:patch
fastlane production version:maintain
cd ..
매번 이런 명령어를 작성하기 귀찮았기 때문에 이후에는 package.json에 미리 커맨드를 지정해놓고 사용했다.
fastlane-staging-patch-ios:...
...
fastlane-staging-minor:...
fastlane-staging-major:...
...
fastlane-patch:...
문제는 커맨드의 경우의 수가 너무 많아져서 코드가 장황해졌고, 커맨드가 뭐였는지 나조차도 혼란스러웠던 경험이 많아졌다는 점이었다. android에서만 staging으로 patch 버전을 올려서 배포하는 커맨드가 fastlane-patch-android-staging인지, fastlane-android-staging-patch 인지 헷갈렸고, 호출하려고보니 미처 정의하지 못한 케이스였던 적도 있었다. 뿐만 아니라 "x,y,z" 꼴로 버전을 명시하고 싶어도 이와같은 시스템에서는 불가능했다.
이런 이유로 shell script 파일을 만들어 대응하고자 했다. 최종 목표는 다음과 같이 호출을 간소화 하는 것이었다.
npm run bump patch
npm run bump ios 2.1.1
npm run dist android stating
npm run dist production ios
npm run dist
이 작업을 위해 가장 먼저 한 일은, fastlane의 lane을 재구성하는 것이었다. 기존에는 하나의 lane에서 version bump와 배포를 함께 수행했는데, 스크립트 고도화를 위해 일단 두 작업 사이의 의존성을 제거해주었다.
desc "Bump App Version"
lane :bump do |options|
...
end
desc "build and distribute staging app"
lane :staging do
...
end
desc "build and distribute production app"
lane :production do
...
end
나는 아래와같이 platform과 version을 명시해줌으로써 스크립트를 호출해서 app version을 수정하고 싶었다.
npm run bump ios patch
npm run bump android 1.2.3
npm run bump minor
이 내용을 기반으로 스크립트의 help brief를 작성해줬다.
-h, --help show brief help
optional) select platform to distribute(android, ios, both). default is both
android
ios
required) specify build version or bump type
patch increment patch version (1.0.0 -> 1.0.1)
minor increment minor version (1.0.1 -> 1.1.0)
major increment major version (1.1.0 -> 2.0.0)
x.y.z specify a version number(1.0.0 -> x.y.z)
전체적인 shell script의 플로우는 다음과 같다.
1. selected_platform과 new_version이라는 변수를 파일 상단에 선언해준다.
2. selected_platform의 초기값은 'both'로, 외부에서 값을 입력받지 않으면 기본적으로 ios와 android에서 모두 실행한다.
3. arg로 "ios"나 "android"가 들어온다면 selected_platform에 이 값을 할당한다.
4. arg로 "patch", "minor", "major", 또는 "x.y.z" 꼴로 버전 정보가 들어오면 이 값을 new_version 변수에 할당한다.
5. 만약 new_version 변수에 값이 할당되지 않았다면 스크립트를 종료한다.
6. 만약 모든 변수에 올바른 값이 할당되었다면 적절하게 fastlane bump를 실행한다.
# 변수 초기화
selected_platform="both"
new_version=""
# args의 갯수가 0보다 크다면 조건문을 돌아라
while test $# -gt 0; do
# 현재 처리중인 arg를 "arg"라는 이름의 변수에 할당(가독성을 위한 작업)
arg="$1"
# 만약 arg가 x.y.z 꼴의 버전이라면 new_version 변수에 할당
if [[ "$arg" =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
new_version="$arg"
shift #현재 바라보는 arg 제거.
# shift된 최신 arg를 다시 할당하여 아래 case문에서 최신 arg를 바라보도록 한다.
arg="$1"
fi
case "$arg" in
-h|--help)
echo "-h, --help show brief help"
echo ""
echo "optional) select platform to distribute(android, ios, both). default is both"
echo " android"
echo " ios"
echo ""
echo "required) specify build version or bump type"
echo " patch increment patch version (1.0.0 -> 1.0.1)"
echo " minor increment minor version (1.0.1 -> 1.1.0)"
echo " major increment major version (1.1.0 -> 2.0.0)"
echo " x.y.z specify a version number(1.0.0 -> x.y.z)"
exit 0
;;
android)
selected_platform="android"
shift
;;
ios)
selected_platform="ios"
shift
;;
patch)
new_version="patch"
shift
;;
minor)
new_version="minor"
shift
;;
major)
new_version="major"
shift
;;
*)
break
;;
esac
done
# 초기화 이후 new_version 값이 할당된 적 없다면 error message와 함께 스크립트 종료
if [ "$new_version" = "" ]; then
echo " bump_type can be 'major', 'minor', 'patch', or a specific version number like 'x.y.z'"
exit 1
fi
if [ "$selected_platform" = "both" -o "$selected_platform" = "ios" ]; then
#조건에 맞춰 미리 정의해둔 fastlane bump lane을 실행한다.
cd ios && fastlane bump version:$new_version && cd ..
fi
if [ "$selected_platform" = "both" -o "$selected_platform" = "android" ]; then
#조건에 맞춰 미리 정의해둔 fastlane bump lane을 실행한다.
cd android && fastlane bump version:$new_version && cd ..
fi
나는 sh파일들은 ./sh/ 경로에 모아놓았다. 이를 쉽게 호출하기 위해 package.json에 정의해주었다.
scripts:{
"bump": "bash sh/bump.sh",
}
npm run bump -- --help
나는 하나의 앱을 production과 staging으로 묶어 관리하고 있었고, 이를 다시 platform에 따라 자유롭게 빌드 환경을 선택하고 싶었다. 만약 env와 platform을 생략한다면 모든 케이스에 대해서 배포를 진행되어야 한다.
npm run dist ios staging
npm run dist android prod
npm run dist production
npm run dist android
npm run dist
이 내용을 기반으로 스크립트의 help brief를 작성해줬다.
-h, --help show brief help
optional) select platform to distribute(android, ios, both). default is both
android
ios
optional) specify a environment to distribute(staging, production|prod, both). default is both
production|prod build and distribute with production environment
staging build and distribute with staging environment
# 변수 초기화
selected_platform="both"
selected_env="both"
# arg가 1개 이상일 경우 반복문 실행
while test $# -gt 0; do
arg="$1"
case "$1" in
-h|--help)
echo "-h, --help show brief help"
echo ""
echo "optional) select platform to distribute(android, ios, both). default is both"
echo " android"
echo " ios"
echo ""
echo "optional) specify a environment to distribute(staging, production|prod, both). default is both"
echo " production|prod build and distribute with production environment"
echo " staging build and distribute with staging environment"
exit 0
;;
android)
selected_platform="android"
shift
;;
ios)
selected_platform="ios"
shift
;;
production|prod)
selected_env="production"
shift
;;
staging)
selected_env="staging"
shift
;;
*)
echo "Error: '$arg' is not one of the predefined options."
exit 1
;;
esac
done
run_command_and_monitor_output() {
# 외부에서 입력받은 명령어 실행하고 표준 출력을 임시 파일에 리다이렉트
# 명령어가 실행되는 동안 아래 라인이 돌아야하므로, "&"를 붙여 백그라운드 실행
$1 > ./output.txt &
# 해당 명령어의 PID 저장
PID=$!
# 로그를 모니터링하고 특정 패턴을 찾는 함수
monitor_output() {
tail -n 0 -f ./output.txt | while read line; do
# 명령어가 백그라운드에서 돌기 때문에 직접 로그를 찍어준다.
echo "$line"
# 로그에서 아래 문구로 시작하는 패턴 찾기
if [[ $line == *"Waiting for the build to show up in the build list"* ]]; then
echo "Pattern found: $line"
# 해당 패턴을 찾으면 현재 실행중인 명령어를 종료
kill $PID
break
fi
done
}
# 로그를 모니터링하고 패턴을 찾는 함수 호출
monitor_output
rm ./output.txt # 임시 파일 삭제
}
# 변수에 할당된 값에 따라 적절한 위치에서 적절한 lane을 실행.
if [ "$selected_platform" = "both" -o "$selected_platform" = "ios" ]; then
cd ios
if [ "$selected_env" = "both" -o "$selected_env" = "staging" ]; then
run_command_and_monitor_output "fastlane staging"
fi
if [ "$selected_env" = "both" -o "$selected_env" = "production" ]; then
run_command_and_monitor_output "fastlane production"
fi
cd ..
fi
if [ "$selected_platform" = "both" -o "$selected_platform" = "android" ]; then
cd android
if [ "$selected_env" = "both" -o "$selected_env" = "staging" ]; then
fastlane staging
fi
if [ "$selected_env" = "both" -o "$selected_env" = "production" ]; then
fastlane production
fi
cd ..
fi
scripts:{
"dist": "bash sh/distribute.sh",
}
위와같은 플로우로 codepush까지 스크립트로 간소화 해주었다.
selected_platform="both"
selected_env="both"
target_version=""
while test $# -gt 0; do
arg="$1"
case "$arg" in
-h|--help)
echo "-h, --help show brief help"
echo ""
echo "-t specify target version. range Expression is available (-t '1.2.3 - 1.3.*')"
echo ""
echo "optional) select platform to distribute(android, ios, both). default is both"
echo " android"
echo " ios"
echo ""
echo "optional) specify a environment to distribute(staging, production|prod, both). default is both"
echo " production|prod build and distribute with production environment"
echo " staging build and distribute with staging environment"
exit 0
;;
-t)
shift
target_version="$1"
shift
;;
android)
selected_platform="android"
shift
;;
ios)
selected_platform="ios"
shift
;;
production|prod)
selected_env="production"
shift
;;
staging)
selected_env="staging"
shift
;;
*)
break
;;
esac
done
if [ "$selected_platform" = "both" -o "$selected_platform" = "ios" ]; then
if [ "$selected_env" = "both" -o "$selected_env" = "production" ]; then
appcenter codepush release-react -a <ownerName>/<appName> -d <deploymentName> -t $target_version
fi
if [ "$selected_env" = "both" -o "$selected_env" = "staging" ]; then
appcenter codepush release-react -a <ownerName>/<appName> -d <deploymentName> -t $target_version
fi
fi
if [ "$selected_platform" = "both" -o "$selected_platform" = "android" ]; then
if [ "$selected_env" = "both" -o "$selected_env" = "production" ]; then
appcenter codepush release-react -a <ownerName>/<appName> -d <deploymentName> -t $target_version
cd android && ./gradlew clean && cd ..
fi
if [ "$selected_env" = "both" -o "$selected_env" = "staging" ]; then
appcenter codepush release-react -a <ownerName>/<appName> -d <deploymentName> -t $target_version
cd android && ./gradlew clean && cd ..
fi
fi
scripts:{
"codepush": "bash sh/codepush.sh",
}
npm run codepush android
npm run codepush staging
npm run codepush prod -- -t "3.1.*"
npm run codepush android staging -- -t "1.1.0 - 1.1.1"
npm run codepush
안녕하세요. 스타트업 사이드프로젝트로 크로스플랫폼 초기맴버분을 찾고 있는데요. 혹시 관심 있으신가요?
관심 있으시다면 아래의 링크를 통해 어떤 주제와 내용인지 이야기해볼 수 있을 것 같습니다.
https://holaworld.io/study/667240d7e3d9c40013f95ab1
help brief 넘기면 gpt 가 기깔나게 뽑아줄것 같네요 아이디어 감사합니다
근데 이제 쉘스크립트 유지보수 누가하냐