Досвід мобільного CICD: один стандарт fastlane на багато мобільних додатків

Досвід мобільного CICD: один стандарт fastlane на багато мобільних додатків
Я б хотіла поговорити про безперервну інтеграцію та доставку для мобільних додатків за допомогою fastlane. Як ми впроваджуємо CI/CD на всі мобільні програми, як ми до цього йшли і що вийшло в результаті.

У мережі вже достатньо матеріалу по інструменту, якого так не вистачало нам на старті, тому я навмисно не докладно описуватиму інструмент, а лише пошлюся на те, що було у нас тоді:

Стаття складається із двох частин:

  • Передісторія появи мобільного CI/CD у компанії
  • Технічне рішення розкочування CI/CD на N-додатків

Перша частина - більша ностальгія за колишніми часами, а друга ж - досвід, який можна застосувати у себе.

Так історично склалося

Рік 2015

Ми тільки почали займатися розробкою мобільних додатків, тоді ми ще нічого не знали про безперервну інтеграцію, про DevOps та інші модні штуки. Кожне оновлення програми викочував сам розробник зі своєї машини. І якщо для Android це досить просто – зібрав, підписав .apk і закинув у Google Developer Console, то для iOS тодішній інструмент дистрибуції через Xcode залишав нам розкішні вечори — спроби завантажити архів нерідко закінчувалися помилками і доводилося спробувати ще раз. Виходило, що прокачаний розробник кілька разів на місяць не пише код, а займається релізом програми.

Рік 2016

Ми підросли, за плечима вже були думки про те, як звільнити розробників від цілого дня для релізу, а так само з'явився другий додаток, що тільки сильніше за нас підштовхувало до автоматизації. Того ж року ми вперше поставили Jenkins і написали купку страшних скриптів, дуже схожих на ті, що показує fastlane у своїй документації.

$ xcodebuild clean archive -archivePath build/MyApp 
    -scheme MyApp

$ xcodebuild -exportArchive 
                        -exportFormat ipa 
                        -archivePath "build/MyApp.xcarchive" 
                        -exportPath "build/MyApp.ipa" 
                        -exportProvisioningProfile "ProvisioningProfileName"

$ cd /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/

$ ./altool —upload-app 
-f {abs path to your project}/build/{release scheme}.ipa  
-u "[email protected]" 
-p "PASS_APPLE_ID"

На жаль, про те, як ці скрипти працюють і для чого потрібна ця нескінченна пачка ключів досі знали тільки наші розробники, а коли щось у черговий раз ламалося, «шикарні вечори» для розборів ліг їм і діставалися.

Рік 2017

Цього року ми дізналися, що є така штука, як fastlane. Було не так багато інформації, як зараз, як завести, як використовувати. Та й сам інструмент був тоді ще сируватий: постійні помилки, тільки розчаровували нас і в чарівну автоматизацію, яку вони обіцяли, важко вірилося.

Однак основні утиліти, що входять до ядра fastlane, gym и pilot, У нас вийшло завести.

Наші скрипти трохи облагородилися.

$ fastlane gym  —-workspace "Example.xcworkspace" 
                --scheme "AppName" 
                —-buildlog_path "/tmp" 
                -—clean

Облагородилися хоча б тому, що не всі параметри, необхідні для xcodebuild, потрібно вказувати gym самостійно зрозуміє, де і що лежить. А для більш тонкого налаштування можна вказати ті ж ключі, що і в xcodebuildтільки неймінг ключів зрозуміліше.

Цього разу, завдяки gym та вбудованому форматеру xcpretty, логи збірки стали набагато перебірливішими. Це стало економити час на лагодження зламаних збірок, а іноді в цьому могла самостійно розібратися реліз-команда.

На жаль, вимірів по швидкості збирання xcodebuild и gym ми не зробили, але віритимемо документації — до 30% прискорення.

Єдиний процес на всі програми

Рік 2018 та теперішній час

До 2018 року сам процес складання та викочування додатків повністю переїхав на Jenkins, розробники перестали релізувати зі своїх машин, право на реліз мала лише реліз-команда.

Нам уже захотілося докрутити запуск тестів та статичний аналіз, а наші скрипти зростали та росли. Росли і змінювалися разом із нашими додатками. На той момент додатків було близько 10. Враховуючи, що платформи у нас дві — це близько 20 скриптів, що «живуть».

Щоразу, коли ми хотіли додати новий крок до скрипту, доводилося копіпастити шматочки у всі shell-скрипти. Можливо, можна було працювати й акуратніше, але часто такі зміни закінчувалися друкарськими помилками, які вже перетворювалися на вечори реліз-команди на ремонт скриптів і з'ясування, хто з розумників додав цю команду і що вона взагалі робить. В цілому, не можна сказати, що скрипти для складання під одну платформу були хоч скільки-небудь схожими. Хоча безумовно робили те саме.

Для того, щоб завести процес для нового додатка, потрібно було витратити день, щоб підібрати «свіжу» версію з цих скриптів, налагодити і сказати, що «так, працює».

Влітку 2018 ми ще раз подивилися у бік досі розвиваючого fastlane.

Завдання №1: узагальнити всі кроки скриптів та переписати їх у Fastfile

Коли ми починали, наші скрипти виглядали онучою з усіх кроків та милиць у одному shell-скрипті у Jenkins. Ми ще не перейшли на pipeline та поділ у стаді.

Подивилися на те що є і виділили 4 кроки, що підходять під опис нашого CI/CD:

  • build - встановлення залежностей, складання архіву,
  • test - запуск unit-тестів розробника, підрахунок покриття,
  • sonar — запуск усіх лінтерів та надсилання звітів у SonarQube,
  • deploy - відправка артефакту в альфу (TestFlight).

І якщо не вдаватися в подробиці, опустити ключі, що використовуються у actions, вийде ось такий Fastfile:

default_platform(:ios)

platform :ios do
  before_all do
    unlock
  end

  desc "Build stage"
  lane :build do
    match
    prepare_build
    gym
  end

  desc "Prepare build stage: carthage and cocoapods"
  lane :prepare_build do
    pathCartfile = ""
    Dir.chdir("..") do
      pathCartfile = File.join(Dir.pwd, "/Cartfile")
    end
    if File.exist?(pathCartfile)
      carthage
    end
    pathPodfile = ""
    Dir.chdir("..") do
      pathPodfile = File.join(Dir.pwd, "/Podfile")
    end
    if File.exist?(pathPodfile)
      cocoapods
    end
  end

  desc "Test stage"
  lane :test do
    scan
    xcov
  end

  desc "Sonar stage (after run test!)"
  lane :run_sonar do
    slather
    lizard
    swiftlint
    sonar
  end

  desc "Deploy to testflight stage"
  lane :deploy do
    pilot
  end

  desc "Unlock keychain"
  private_lane :unlock do
    pass = ENV['KEYCHAIN_PASSWORD']
    unlock_keychain(
      password: pass
    )
  end
end

Насправді перший Fastfile у нас вийшов монструозним, враховуючи деякі милиці, які нам все ще були потрібні, і кількість параметрів, які ми підставляли:

lane :build do
carthage(
  command: "update",
  use_binaries: false,
  platform: "ios",
  cache_builds: true)
cocoapods(
  clean: true,
    podfile: "./Podfile",
    use_bundle_exec: false)

gym(
  workspace: "MyApp.xcworkspace",
  configuration: "Release",
  scheme: "MyApp",
  clean: true,
  output_directory: "/build",
  output_name: "my-app.ipa")
end 

lane :deploy do
 pilot(
  username: "[email protected]",
  app_identifier: "com.example.app",
  dev_portal_team_id: "TEAM_ID_NUMBER_DEV",
  team_id: "ITS_TEAM_ID")
end

На прикладі вище, лише частина параметрів, які нам потрібно вказати: це параметри складання - схема, конфігурація, назви Provision Profile, а також параметри дистрибуції - Apple ID облікового запису розробника, пароль, ідентифікатор програми і так далі. У першому наближенні ми поклали всі ці ключі в спеціальні файли. Gymfile, Matchfile и Appfile.

Тепер у Jenkins можна викликати короткі команди, які не «замилюють» погляд і добре зчитуються оком:

# fastlane ios <lane_name>

$ fastlane ios build
$ fastlane ios test
$ fastlane ios run_sonar
$ fastlane ios deploy

Ура, ми молодці

Що одержали? Зрозумілі команди кожного кроку. Причесані скрипти, акуратно розкладені у файли fastlane. Зрадівши, ми побігли до розробників з проханням додати все, що потрібно у свої репозиторії.

Але вчасно усвідомили, що зіткнемося з тими самими складнощами — ми все ще матимемо 20 скриптів збірки, які так чи інакше почнуть жити своїм життям, правити їх буде складніше, бо скрипти переїдуть у репозиторії, а в нас доступу туди немає. І, загалом, вирішити наш біль у такий спосіб не вийде.

Досвід мобільного CICD: один стандарт fastlane на багато мобільних додатків

Завдання №2: отримати єдиний Fastfile для N-додатків

Зараз уже здається, що розв'язати завдання не так вже й складно — задайте змінні і поїхали. Так, власне, так і вирішили завдання. Але в той момент, коли ми це вкручували, у нас не було ні експертизи в самому fastlane, ні в Ruby, на якому написано fastlane, ні корисних прикладів у мережі — кожен, хто писав про fastlane тоді, обмежувався прикладом для одного додатку для одного розробника.

Fastlane вміє у змінні оточення, і це ми вже спробували, задавши пароль від Keychain:

ENV['KEYCHAIN_PASSWORD']

Подивившись на наші скрипти, ми виділили спільні частини:

#for build, test and deploy
APPLICATION_SCHEME_NAME=appScheme
APPLICATION_PROJECT_NAME=app.xcodeproj
APPLICATION_WORKSPACE_NAME=app.xcworkspace
APPLICATION_NAME=appName

OUTPUT_IPA_NAME=appName.ipa

#app info
APP_BUNDLE_IDENTIFIER=com.example.appName
[email protected]
TEAM_ID=ABCD1234
FASTLANE_ITC_TEAM_ID=123456789

Тепер для того, щоб почати використовувати ці ключі у файлах fastlane'а, потрібно було придумати, як їх туди доставляти. Fastlane має для цього рішення: завантаження змінних через dotenv. У документації сказано, якщо вам важливо підвантажувати ключі для різних цілей, наплід у директорії fastlane кілька конфігураційних файлів .env, .env.default, .env.development.

І тоді ми вирішили використати цю бібліотеку трохи інакше. Помістимо в репозиторії розробників не скрипти fastlane та його мета інформацію, а унікальні ключі цієї програми у файлі .env.appName.

самі Fastfile, Appfile, Matchfile и Gymfile, ми сховали в окремий репозиторій. Туди ж сховали додатковий файл із ключами-паролями від інших сервісів. .env.
Наприклад, можна подивитися тут.

Досвід мобільного CICD: один стандарт fastlane на багато мобільних додатків

На CI виклик не сильно змінився, додався ключ конфігурації конкретної програми:

# fastlane ios <lane_name> --env appName

$ fastlane ios build --env appName
$ fastlane ios test --env appName
$ fastlane ios run_sonar --env appName
$ fastlane ios deploy --env appName

Перед тим як запускати команди, ми підвантажуємо наш репозиторій зі скриптами. Виглядає не так гарно:

git clone [email protected]/FastlaneCICD.git fastlane_temp

cp ./fastlane_temp/fastlane/* ./fastlane/
cp ./fastlane_temp/fastlane/.env fastlane/.env

Поки залишили це рішення, хоча Fastlane має рішення для завантаження Fastfile через дію import_from_git, але він працює тільки для Fastfile, для інших файлів - ні. Якщо хочеться "прям зовсім красиво", можна написати свій action.

Аналогічний набір зробили для Android додатків та ReactNative, файли лежать в одному репозиторії, але в різних гілках iOS, android и react_native.

Коли реліз команда хоче додати якийсь новий крок, зміни в скрипті фіксуються через MR в git, більше не треба шукати винуватців поламаних скриптів, та й загалом зламати тепер, це треба постаратися.

Тепер точно все

Раніше ми витрачали час на підтримку всіх скриптів, їх оновлення та ремонт усіх наслідків оновлень. Було дуже прикро, коли причини помилок і простоїв релізів були простими друкарськими помилками, за якими так складно встежити в мішанині shell-скрипту. Тепер такі помилки зведені до мінімуму. Зміни накочуються відразу на всі програми. А новий додаток завести в процес коштує 15 хвилин - налаштувати шаблонний pipeline на CI і додати ключі до репозиторію розробника.

Здається, залишився неосвітленим пункт із Fastfile для Android та підпис додатків, якщо стаття буде цікава, напишу продовження. Буду рада вашим питанням або пропозиціям «як би ви вирішили це завдання» у коментарях або Telegram bashkirova.

Джерело: habr.com

Додати коментар або відгук