حتما اگر یک بار گذرتون به فرآیند تهیه خروجی و ریلیز دادن پروژههای iOS افتاده باشه، در جریان سختی و وقتگیر بودن کار هستید. ممکنه مدیریت ریلیزها با شما باشه یا صرفا بخواید نسخه رو به دست نیروهای QA برسونید تا فیچری که اضافه کردید رو تست کنن و یا هر هدف دیگه که شما رو مجبور به تهیه خروجی بکنه.
فرض کنید نیروی QA میخواد فیچری که شما توی برنچ B پیادهسازی کردید رو تست کنه و شما قراره براش خروجی تهیه کنید. اجازه بدید تا با هم مروری به مراحل این کار داشته باشیم:
۱- به یه احتمال زیادی مشغول کار روی برنچ دیگهای مثل F باشیم، بنابراین باید برنچ لوکالمون رو به برنچ B تغییر بدیم و تا پایان فرایند دست از کار روی برنچ F بکشیم.
۲- فرآیند Archive رو شروع کنیم.
۳- بعد از اتمام این فرایند نسبتا طولانی، نوبت به Signing میرسه که احتمال زیاد خیلیهامون از روش Auto signing استفاده میکنیم.
۴- خروجی تهیه شده رو روی سرویسهایی مثل Diawi آپلود میکنیم و لینک نهایی رو تحویل نیروی QA میدیم.
تا اینجای کار اگه همه چی خوب پیش رفته باشه، شما بسته به حجم پروژهتون، چندین دقیقه تمرکز و وقتتون از روی برنچ F برداشته شده. علاوه بر فرایند Archive که بیشترین زمان رو میگیره، تغییر برنچها هم ممکنه زمان زیادی بابت build مجدد کدهای جدید و یا resolve شدن تغییرات پکیجها بگیرن.
هنوز تموم نشده! اگر شما توی محصولی کار کنید که فیچرهای در حال توسعه زیادی داره، همه این سختیها چند برابر میشه و عملا شما تمرکز اصلیتون به جای توسعه، به تهیه خروجی معطوف میشه. بنابراین باید دنبال روشی باشیم که تا حد ممکن بار این فرایند رو از روی دوش خودمون برداریم.
به شخصه خودکار کردن فرایندها برام جذابیت خاصی داره. هرمقدار که فرایندهای خودکارتری داشته باشید، وقت آزاد بیشتری برای تمرکز روی کارهای مهمتر دارید و پروداکتیویتون افزایش چشمگیری خواهد داشت.
تهیه خروجی و ریلیز هم از این امر مستثنی نیست. ما این امکان رو داریم تا با استفاده از ابزاری که در اختیار داریم، این فرایند رو تا حد خوبی خودکار کنیم و وابستگی بقیه افراد (از جمله نیروهای QA) رو به خودمون کم کنیم. این یعنی علاوه برا افزایش وقت آزاد شما، وقت آزاد بقیه هم زیاد میشه، چرا که زمان هماهنگی با شما از فرایند حذف میشه.
ما (واحتمالا خیلی از شما) برای مدیریت نسخهها و سورس کدهامون از Gitlab استفاده میکنیم. Gitlab ابزاری به اسم Gitlab CI/CD داره که به ما کمک میکنه فرایندهای تهیه خروجی و ریلیز دادن رو خودکار کنیم.
برای هر برنچی که روی Gitlab پوش میکنیم، یک Pipeline ایجاد میشه که ممکنه چندین Job داشته باشه که کارای مختلف انجام میدن.(اگر اسمها براتون جدیده نگران نباشید، در ادامه در مورد همشون صحبت میکنیم).
حالا بیاید سناریویی که قبل از این بررسی کردیم رو با این سیستم بررسی کنیم.
فرض کنید نیروی QA میخواد فیچری که شما توی برنچ B پیادهسازی کردید رو تست کنه. مراحل کار توی سیستم جدید به این صورت میشه:
۱- نیروی QA از داخل Gitlab، پایپلاین برنچ B رو اجرا میکنه.
۲- بعد از چند دقیقه، لینک دانلود نسخه مورد نظر داخل کانال(یا هرجای دیگه، بسته به نیاز شما) ارسال میشه.
زیبا شد، نه؟ :))
فرایند از حضور ما مستقل شد و زمان آزادمون هم بیشتر.
همونطور که میدونید برای بیلد کردن پروژههای iOS به Xcode نیاز داریم و برای Xcode به سیستمعامل MacOS.
بنابراین پیشنیاز اولمون یه سیستمعامل مک هست. برای اینکه بتونیم به سیستم مورد نظرمون قابلیت اجرا کردن Pipeline ها رو بدیم باید Gitlab Runner رو روش نصب کنیم. پس بریم سراغ مرحله اول
ابتدا با دستور زیر فایل آخرین نسخه رو دانلود کنید:
$ sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
و بعد بهش دسترسی اجرا بدید:
$ sudo chmod +x /usr/local/bin/gitlab-runner
حالا نوبت نصب runner هست:
$ cd ~$ gitlab-runner install$ gitlab-runner start
بعد از این مرحله runner روی سیستممون نصب میشه و کافیه داخل gitlab ثبتش کنیم. به این منظور دستور زیر رو اجرا میکنیم:
$ gitlab-runner register
و دیتایی که از ما میخواد رو مطابق مشخصات سرور gitlab مون وارد میکنیم:
URL: https://git.example.com/ TOKEN: open [here](https://git.example.com/PROJECT_PATH/-/settings/ci_cd) and copy runner token NAME: your desired name(or press enter) TAGS: Xcode13, iOS15 EXECUTOR: shell
توجه داشته باشید که میتونید تگ رو مطابق میل خودتون وارد کنید. تگهایی که توی مثال بالا هست در ادامه استفاده میشن.
برای دسترسی به تنظیمات CI/CD در Gitlab نیاز هست دسترسی Maintainer داشته باشید.
حالا که روی سیستممون runner نصب کردیم، میتونیم بریم سراغ قدم بعدی.
برای این که runner بدونه چه دستوراتی رو اجرا کنه، نیاز هست که توی مسیر اصلی پروژه تون فایلی به اسم .gitlab-ci.yml
داشته باشید.
در زیر یه نمونه مثال از این فایل رو آوردم که در موردش توضیح خواهم داد.
stages: - build - test build_project: stage: build script: - xcodebuild -resolvePackageDependencies - xcodebuild -workspace Divar.xcworkspace -scheme 'Divar' -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' -quiet build DEBUG_INFORMATION_FORMAT="dwarf" tags: - XCode13 - iOS15 run_tests: stage: test script: - xcodebuild -resolvePackageDependencies - xcodebuild test -workspace Divar.xcworkspace -scheme Divar -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' -enableCodeCoverage YES -resultBundlePath Bundle | xcpretty -s -c --utf needs: ['build_project'] after_script: - xcrun xccov view --report Bundle.xcresult/ --only-targets tags: - XCode13 - iOS15
توی قسمت stages ما میگیم که pipelineمون چه مراحلی داره. میتونیم با توجه به نیازمون stageهای مختلف تعریف کنیم و هر stage میتونه تعدادی job داشته باشه.
توی مثال بالا jobای که اسمش build_project
هست زیر مجموعه استیج build
هست.
تا اینجای کار ما داخل pipeline دو تا stage تعریف کردیم. یکی برای build گرفتن و دیگری برای اجرای تستها.
یه امکانی که Gitlab بهمون میده CI Lint هست. یعنی میتونیم فایل YAML بهش بدیم و اگه ایرادی داره بهمون بگه و حتی نتیجهش رو شبیه سازی کنه. برای این کار کافیه مسیر زیر رو طی کنید:
CI/CD > CI Lint
من مثال بالا رو داخل CI Lint وارد کردم و نتیجه به این شکل هست:
اگه از مثال بالا استفاده میکنید، لازمه که اسم پروژه رو از Divar به اسم پروژه خودتون تغییر بدید. همچنین ما برای تمیز کردن خروجی اجرای تستها از کتابخونه xcpretty استفاده کردیم. برای نصبش کافیه دستور زیر رو استفاده کنید:brew install xcpretty
نکته دیگه که در مثال بالا وجود داره این هست که میشه job هارو به هم وابسته کرد. در بالا ما گفتیم برای اجرا شدن تستها نیازه که job بیلد اجرا شده باشه.
و در آخر باید به tag ها اشاره کنیم.
داخل تعریف هر job میتونیم مشخص کنیم روی runnerهایی اجرا بشن که تگ مشخصی دارن. این کار برای جداسازی runner ها خیلی مفیده. فرض کنید شما چند سیستم با نسخههای مختف Xcode یا MacOS دارید که میتونید موقع register کردن رانر(یا بعد از اون از داخل تنظیمات CI/CD در گیت لب) تگ مرتبط براش تعریف کنید. توی این مثال ما گفتیم job روی runnerای اجرا بشه که تگ Xcode13 و iOS15 داره.
اگه تا اینجای کار همه چی خوب پیش رفته باشه، ما تونستیم فرایند بیلد گرفتن و اجرای تستهامون رو خودکار کنیم.
این مرحله، مرحله مهمیه. همونطور که می دونید ما برای نصب خروجیهای ipa روی دستگاههای اپل محدودیت زیادی داریم. میتونیم خروجی ad-hoc بگیریم و روی دستگاههایی که UDIDشون داخل Developer Panel ثبت شده نسخه رو نصب کنیم، یا میتونیم از Simulator های آنلاین استفاده کنیم. به صورت کلی اگه بخوایم جمع بندی کنیم گزینههای زیر رو داریم:
در ادامه سعی میکنیم دو روش آخر رو استفاده کنیم. قبل از اون خوبه سرویسهایی که به ما این امکانهارو میدن بررسی کنیم.
در ادامه تعدادی اسکریپت میبینیم که ممکنه داخلشون از یک سری متغیر ناشناس استفاده شده باشه. این متغیرها به دلیل امنیت داخل Environment variable های Gitlab تعریف شدن و داخل job ها قابل استفاده هستن.
برای تعریف Environment variable جدید کافیه مسیر زیر رو طی کنید:Settings > CI/CD > Variables
برای استفاده از این روش سرویس معروفی که وجود داره Diawi هست. این سرویس بهمون اجازه میده فایل ipa رو آپلود کنیم و در نهایت بهمون یه لینک دانلود میده که بقیه میتونن ازش استفاده کنن. اما محدودیتهای زیادی هم داره. هم تعداد دانلود و هم تعداد روزی که فایل شمارو نگهداری می کنه محدوده. ابتدا در سایت Diawi ثبت نام کنید و از قسمت developers، یک API TOKEN دریافت کنید. پس از این دو متغیر زیر رو در Environment Variable ها اضافه کنید:
PROJECT_DIAWI_EMAIL = Diawi Email PROJECT_DIAWI_TOKEN = Diawi API Token
برای این کار میتونیم ابتدا یه stage جدید به فایل gitlab-ci.yml مون اضافه کنیم به اسم release و بعد job مربوط به تهیه خروجی رو تعریف کنیم.
قبلش خوبه اشاره کنم که سرویس Diawi بهمون API برای آپلود ipa میده و بنابراین می تونیم کل فرایند رو خودکار کنیم.
stages: - build - test - release build_project: ... run_tests: ... diawi_release: stage: release script: - export CLIQ_API_KEY SLACK_API_KEY - export BUILD_TYPE=Release - bash scripts/release/diawi/diawi.bash Divar.xcworkspace needs: ['build_project'] when: manual artifacts: paths: - scripts/release/diawi/Divar.ipa/*.ipa expire_in: 1 week tags: - XCode13 - iOS15 diawi_debug: stage: release script: - export CLIQ_API_KEY SLACK_API_KEY - export BUILD_TYPE=Debug - bash scripts/release/diawi/diawi.bash Divar.xcworkspace needs: ['build_project'] when: manual artifacts: paths: - scripts/release/diawi/Divar.ipa/*.ipa expire_in: 1 week tags: - XCode13 - iOS15
در اینجا ما دو job جدید به اسمهای diawi_release
و diawi_debug
اضافه کردیم که از اسمشون مشخصه خروجیهای DEBUG و RELEASE بهمون تحویل میدن.
در مورد CLIQ_API_KEY بعدا صحبت خواهم کرد. اما به طور خلاصه بگم برای ارسال لینک دانلود نهایی داخل کانالهای پیامرسان Cliq ازش استفاده میشه که توی Environment Variable های Gitlab تعریف شده و ما برای استفاده توی اسکریپت بعدی exportش کردیم.
اسکریپتامون رو توی مسیر زیر تعریف کردیم. بنابراین لازم هست با توجه به ساختار پروژه خودتون مسیر رو تغییر بدید:
scripts/release/diawi/
برای این که اجرای این job ها به صورت دستی باشه، دستور when: manual
رو به مشخصات اضافه کردیم و همچنین برای این که فایل نهایی ipa رو به صورت artifact بعد از اجرای job داشته باشیم، با مشخص کردن مسیر فایل گفتیم بعد از اتمام کار فایل مورد نظر رو روی gitlab آپلود کنه و تا یک هفته هم نگهداری کنه.
اماچیزی که اینجا مهمه اسکریپت diawi.bash هست که تعریفش در زیر اومده:
scripts/release/diawi/diawi.bash
WORKSPACE_DIR=$( cd "$(dirname "$1")" ; pwd -P )/$(basename $1) CURRENT_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd $CURRENT_DIR EXPORT_OPTIONS_DIR=../exportOptionsAdHoc.plist export BUILD_TYPE CLIQ_API_KEY SLACK_API_KEY xcodebuild -resolvePackageDependencies -workspace $WORKSPACE_DIR -scheme Divar xcodebuild -quiet -workspace $WORKSPACE_DIR -arch arm64 -scheme Divar clean archive -configuration $BUILD_TYPE -allowProvisioningUpdates -sdk iphoneos -archivePath Divar.xcarchive xcodebuild -exportArchive -allowProvisioningUpdates -archivePath Divar.xcarchive -exportOptionsPlist $EXPORT_OPTIONS_DIR -exportPath Divar.ipa NAME64=$(echo ${GITLAB_USER_NAME} | base64) bash diawi-release.bash Divar.ipa/*.ipa $PROJECT_DIAWI_TOKEN $CI_COMMIT_BRANCH $NAME64
به ترتیب از بالا شروع کنیم، اول به یه فایل کانفیگ برای خروجی نیاز داریم که توی مسیر زیر قرار داره:
scripts/release/exportOptionsAdHoc.plist
و تعریفش به صورت زیر هست:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>compileBitcode</key> <false/> <key>destination</key> <string>export</string> <key>method</key> <string>ad-hoc</string> <key>signingStyle</key> <string>automatic</string> <key>stripSwiftSymbols</key> <true/> <key>thinning</key> <string><none></string> </dict> </plist>
ممکنه شما به حالت دیگه ای نیاز داشته باشید. اما به صورت کلی تنظیمات بالا رو میتونید استفاده کنید.
اینجا هم متغیر PROJECT_DIAWI_TOKEN توکنی هست که از ثبت نام در سایت Diawi دریافت کردیم که توی اسکریپت بعدی استفاده خواهد شد
در نهایت اسکریپت زیر رو داریم:
scripts/release/diawi/diawi-release.bash
#!/bin/bash function parse_json() { echo $1 | \ sed -e 's/[{}]/''/g' | \ sed -e 's/", "/'\",\"'/g' | \ sed -e 's/" ,"/'\",\"'/g' | \ sed -e 's/" , "/'\",\"'/g' | \ sed -e 's/","/'\"---SEPERATOR---\"'/g' | \ awk -F=':' -v RS='---SEPERATOR---' "\$1~/\"$2\"/ {print}" | \ sed -e "s/\"$2\"://" | \ tr -d "\n\t" | \ sed -e 's/\\"/"/g' | \ sed -e 's/\\\\/\\/g' | \ sed -e 's/^[ \t]*//g' | \ sed -e 's/^"//' -e 's/"$//' } PROJECT_DIAWI_TOKEN="$2" BRANCH_NAME="$3" USERNAME=$(echo $4 | base64 -D) TOKEN="$PROJECT_DIAWI_TOKEN" EMAIL="$PROJECT_DIAWI_EMAIL" # Retrieve JOB ID JOB_ID="$(curl https://upload.diawi.com/ --http1.1 -F token=$TOKEN -F file=@$1 -F callback_emails=$EMAIL | awk -F '"' '/job/{ print $(NF-1) }')" # Request to check status untill status becomes successful REQUEST="$(curl --http1.1 'https://upload.diawi.com/status?token='"$TOKEN"'&job='"$JOB_ID"'')" STATUS="$(echo $REQUEST | awk -F ',' '/message/{ print $1 }' | awk -F : '{ print $2}')" LINK="$(parse_json $REQUEST link)" while [ $(($STATUS)) -ne 2000 ] do sleep 1 REQUEST="$(curl --http1.1 'https://upload.diawi.com/status?token='"$TOKEN"'&job='"$JOB_ID"'')" STATUS="$(echo $REQUEST | awk -F ',' '/message/{ print $1 }' | awk -F : '{ print $2}')" LINK="$(parse_json $REQUEST link)" echo $REQUEST done # POST TEXT POST_TEXT="New Diawi Link for branch: $BRANCH_NAME\nDownload link: $LINK\nStart by: $USERNAME\nBuild Type: $BUILD_TYPE" # BROADCAST MESSAGE export CLIQ_API_KEY SLACK_API_KEY bash ../broadcast.bash -t "$POST_TEXT" -n "Auto Deploy"
برای ارسال پیام نهایی به پیامرسانها یه اسکریپت با نام broadcast.bash
درمسیر زیر تعریف شده است:
scripts/release/broadcast.bash
#!/bin/bash while getopts t:n: flag do case "${flag}" in t) TEXT=${OPTARG};; n) BOT_NAME=${OPTARG};; *) echo "Invalid option: -$flag" ;; esac done echo $TEXT echo $BOT_NAME curl -X POST \ "https://hooks.slack.com/services/$SLACK_API_KEY" \ -d '{ "channel": "#divar-ios-gitlab", "username": "'"${BOT_NAME}"'", "text": "'"${TEXT}"'", "icon_emoji": ":iphone:" }' curl -X POST \ "https://cliq.zoho.eu/api/v2/bots/iosbot/incoming?zapikey=$CLIQ_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "channel": "iosgitlab", "text": "'"${TEXT}"'", "bot": { "name": "'"${BOT_NAME}"'", "image": "https://cdn3.iconfinder.com/data/icons/picons-social/57/56-apple-1024.png" } }'
در اینجا خروجی هم در Slack و هم در Zoho Cliq ارسال میشود. میبایست نام کانالها و اسمها را مطابق با روش خودتون تغییر بدید.
همچنین API KEY های سرویسهای مدنظرتون رو هم باید در Environment Variable های gitlab وارد کنید.
آموزش ارسال پیام در کانلهای Slack
آموزش ارسال پیام در کانالهای Cliq
اگه تا اینجا رو درست رفته باشیم، بعد از پوش کردن این تغییرات روی Gitlab و اجرا شدن job مربوط به build_project
، میتونیم job های diawi_debug
و diawi_release
رو به صورت دستی اجرا کنیم که در انتها خروجیشون داخل Slack یا Zoho Cliq ارسال میشه.
یکی از سرویسهایی که به ما امکان شبیهسازی آنلاین رو میده سایت Appetize.io هست. با ثبت نام داخل این سایت و دریافت API TOKEN میتونیم مراحل بعد رو پیش ببریم. این Token رو داخل Environment Variable ها با اسم زیر وارد کنید:
APPETIZE_TOKEN = YOUR_API_KEY
ابتدا job مورد نظر رو داخل فایل gitlab-ci.yml تعریف میکنیم:
stages: - build - test - release build_project: ... run_tests: ... diawi_release: ... diawi_debug: ... online_simulator: stage: release script: - export CLIQ_API_KEY APPETIZE_TOKEN SLACK_API_KEY - export BUILD_TYPE=Debug - bash scripts/release/online_simulator/online_simulator.bash Divar.xcworkspace needs: ['build_project'] when: manual tags: - XCode13 - iOS15
بعد از این نوبت به تعریف اسکریپت online_simulator.bash میرسه که در مسیر زیر تعریف شده:
scripts/release/online_simulator/online_simulator.bash
WORKSPACE_DIR=$( cd "$(dirname "$1")" ; pwd -P )/$(basename $1) CURRENT_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd $CURRENT_DIR export BUILD_TYPE CLIQ_API_KEY APPETIZE_TOKEN SLACK_API_KEY xcodebuild -workspace $WORKSPACE_DIR -resolvePackageDependencies -scheme Divar BUILD="$(xcodebuild -sdk iphonesimulator -workspace $WORKSPACE_DIR/ -scheme Divar -arch x86_64 -configuration $BUILD_TYPE)" APP_PATH="$(xcodebuild -workspace $WORKSPACE_DIR -scheme Divar -showBuildSettings | grep -m 1 "CODESIGNING_FOLDER_PATH" | grep -oEi "\/.*" | sed "s/Release-iphoneos/Debug-iphonesimulator/g")" cp -R $APP_PATH Divar.app zip -r build.zip Divar.app bash appetize-upload.bash build.zip $CI_COMMIT_BRANCH
همچنان یادتون نره که اسم پروژه رو با توجه به پروژه خودتون تغییر بدید
فایل appetize-upload.bash
در مسیر زیر تعریف شده:
scripts/release/online_simulator/appetize-upload.bash
#!/bin/bash REQUEST="$(curl --http1.1 https://$APPETIZE_TOKEN@api.appetize.io/v1/apps -F "file=@$1" -F "platform=ios" -F "appPermissions.run=public")" LINK="$(echo $REQUEST | python2 -c "import sys, json; print json.load(sys.stdin)['appURL']")" #POST TEXT POST_TEXT="New Appetize.io link for branch: $2\n Link:\n$LINK\nBuild Type: $BUILD_TYPE" #BROADCAST MESSAGE export CLIQ_API_KEY SLACK_API_KEY bash ../broadcast.bash -t "$POST_TEXT" -n "Online Simulator"
بعد از اجرای این job لینکی به شبیهساز آنلاین در کانالهای مربوطه ارسال خواهد شد.
با استفاده از CI/CD میتونیم فرایندهامون رو تا جای خوبی خودکار کنیم و از فرصتهامون برای انجام کارهای ارزشمندتری استفاده کنیم.
به دلیل محدودیتهای سرویس Diawi، ما در دیوار سرویسی رو طراحی و پیادهسازی کردیم که خروجیهارو به صورت اختصاصی برای خودمون تهیه میکنه و اینگونه از محدودیتها رها شدیم :دی
توی بلاگ پستهای بعدی سعی میکنم در مورد این سرویس بیشتر صحبت کنم.
دمتون گرم که تا اینجا همراهی کردید
من، علی موذنزاده، از شهریور سال ۹۶ در تیم آی او اس دیوار فعالیت میکنم و مسئولیت فعلی من، مدیریت ریلیزهای اپلیکیشن iOS دیواره. همونطور که در این مقاله خوندیم، اگر از اتوماسیون استفاده نکنیم، مدیریت ریلیزها فرایندی به شدت زمانبر و طاقتفرسا خواهد بود، بنابراین در بیشتر اوقات چالشهایی که باهاشون روبرو هستم، پیادهسازی روشهایی برای راحتی و همینطور مطمئنبودن فرایند ریلیزهاست.
اگر مطالعهی این مقاله برای شما مفید بوده و دوست دارید در محیطی کار کنید که امکان رشد و یادگیری برای شما فراهمه، به تیم ما در دیوار بپیوندید.