Розганяємо збірку Swift проекту в Xcode

image
Стаття про те як полагодити инкрементальную компіляцію в Xcode для Swift проектів і прискорити build phases для Cocoapods і Carthage, нічого не поламавши.
Невеликий спойлер: на трьох різних проектах вийшло скоротити час инкрементальной складання в 9 разів!
Туторіал несе суто практичний характер з мінімумом води. Обов'язково до прочитання для діючих iOS розробників.

Проблема
У нас в компанії ведеться кілька паралельних проектів на Swift і, практично, скрізь була складність з послідуючим складанням. На кожен повторний 'Run' йшло від 30 до 50 секунд. Хвилину пишеться код, пів хвилини чекаєш поки збереться проект. Встигаєш сходити за чаєм, покурити, порахувати податки і іноді подрімати.
Однозначно треба було щось міняти. В моїх попередніх статтях, де описуються актуальні проблеми компілятора і способи їх вирішення, ми змогли досягти певних успіхів. Тим не менш, це не усунуло труднощі з инкрементальной компіляцією, яка з'їдала більшу частину часу, постійно вибиваючи своєю повільністю з робочого процесу. Упевнений, кожен з цим стикається регулярно, прийнявши як частина робочого процесу.
Але ми не стали з цим миритися, вирішивши розслідувати всі обставини справи і в підсумку отримавши непоганий результат. Про що і буду радий розповісти.
P. S. Картинка в шапці не означає, що ми 'вертіли' Xcode. Це його нібито так прискорюємо.
Инкрементальная компіляція
Якщо хто не знайомий з терміном — це спосіб складання тільки змінених місць коду без тотальної рекомпиляции всього проекту. А ще це те, що нормально не працює в Xcode.
У минулому пості товариш Gxost підказав ідею, яка на деякий час вирішила проблему. Ми вже почали відкривати шампанське всією командою і вішати портрет імператора спасителя на стіну, але, на жаль, у цей момент проблема відновилася.
Судячи з усього, ми не одні такі. Про рецидивах пишуть і на StackOverflow під відповіддю, а так само у вихідній теми на форумах Apple.
Це ні в якому разі не палиця в город. Навіть навпаки — подяка, все це підштовхнуло продовжити копати. Якщо хоч і тимчасово, але проблема була вирішена, то значить істина десь поруч.
Що таке взагалі цей header map для якого ми ставимо прапор рекомендації Apple?
Трохи веб-археологии щодо документації яблука:
Header maps (also known as «header maps») are files Xcode uses to compile the locations of the headers used in a target.
Своїми словами, header maps — це індексний файл Xcode з розташуванням хидеров проекту. Файл про всіх хидерах, які ми використовуємо.
Так як я обіцяв не занурюватися занадто глибоко в теорію, то ось відмінна стаття на цю тему. Легко читається, вкрай рекомендую.
Головне, що щось у цих header maps(або те, що їх використовує) провокує недобросовісну роботу инкрементальной компіляції. Якщо ми знайшли таку технічну пухлина, то давайте поступимо без півзаходів і просто їх відключимо.
Заходимо в build settings прибираємо зайві деталі виставляємо існуючий прапор USE_HEADER_MAPS NO:
image
Тепер нам треба компенсувати втрачений функціонал і вручну додати розташування всіх хидеров проекту.
Нікуди не йдемо з налаштувань і руками прописуємо шлях до папок з заголовками в полі 'user header search paths':
image
В Swift проекті їх повинно бути мало, тільки ваші кастомні Obj-C речі.
Знову робимо повний clean і пробуємо завести проект. Якщо влетіли в наступну помилку,
image
то значить ви просто забули згадати шлях до одного або декількох заголовних файлів. Після їх виправлення все має зібратися без ускладнень, так як крім цього ми ніяких змін не вносили.
В якості бонусу зазначимо, що инкрементальная компіляція працює найкращим чином при вимкненому whole-module-optimization(WMO) про який ми говорили в минулий раз. Це не означає, що для полномодульной оптимизациии рішення не працює, просто без неї все проходить на кілька секунд швидше. Нехай при цьому складання з чистого аркуша і тягнуться цілу вічність.
Тут вже кожен сам для себе вирішує, що йому зручніше, швидше і краще підходить. Якщо ви вирішили відмовитися від WMO, то досить прибрати прапор SWIFT_WHOLE_MODULE_OPTIMIZATION з налаштувань проекту, будь-яких труднощів виникнути не повинно.
Результат: инкрементальную компіляцію ми успішно полагодили. Відтепер має витрачатися не більше кількох секунд на сам факт складання Swift, не рахуючи codesign, лінкування і різних build scripts(про них ми поговоримо нижче). Усередині компанії ми перевірили цей метод на трьох різних проектах, кількох версія OSX і взагалі безлічі конфігураціях в цілому, що зокрема відноситься і до Xcode. І ось вже тривалий час рецидивів не спостерігаємо.
Розгін Cocoapods і Carthage
Напевно, багато хто помічав, що крім самої компіляції ще багато часу йде на різні 'shell scripts':
image
При використанні cocoapods та/або carthage їх навіть кілька. На їх виконання йде від 3 до 10 секунд кожен раз в залежності від швидкості вашого диска, процесора і положення зірок на небі. Cocoapods прописує себе туди автоматично, а для Carthage доводиться це робити, вручную.
Трохи вивчивши контент, ми з'ясували, що ці скрипти займаються нічим іншим як копіюванням своїх ресурсів в складальну директорію проекту. При цьому вони не піклуються про те були скопійовані цих файли чи ні. Хоча, набагато логічніше було б перевіряти наявність потрібних ресурсів перед їх повторним копіюванням.
Що ми і зробимо.

Почнемо з Carthage.

Натхненням для цього рішення став недооцінений відповідь на StackOverflow, де взяли і покрили лайками одноразовий милицю. До речі, якщо ви раптом вирішите скористатися схваленим рішенням, то зловите дикі і гарячі помилки в Runtime варто вам зробити повний clean.
Ми так робити не будемо, тому підемо на правильному шляху видозмінимо наш Copy Carthage Frameworks build phase:
FILE="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Carthage-Installation-Flag"
if [ -f "$FILE" ]; then
exit 0
fi
touch "$FILE"
/usr/local/bin/carthage copy-frameworks

Цей скрипт додатково копіює разом з ресурсами ще один порожній файлик, який служить маяком, що операція пройшла успішно. Він не займає місця, не дає побічних ефектів і дозволений дітям з 3-х років. Наступного разу, коли скрипт буде запущено, він спочатку перевірить наявність цього файла, якщо файл є, то операція автоматично завершиться без зайвих рухів.
Для впевненості, скріншот результату, який у нас має вийти:
image
Важливо! Якщо ви вирішите додати або видалити залежність Carthage, то перед складанням проекту обов'язково потрібно провести повний clean. Інакше скрипт так і буде вважати, що він вже все давно встановив. А ви будете в поті чола намагатися знайти пояснення тому, що відбувається.
до Речі, знайти функцію clean можна в меню по шляху Product -> Clean.
image
Якщо ще затиснути option(alt), то можна зробити особливий clean, який до того ж виганяє демонів з проекту видаляє різні локальні налаштування, кеш і часто дає збій іншу нісенітницю.
Тепер Cocoapods
Для бобів принцип той самий, але так як скрипти генеруються автоматично, доведеться додати трохи магії в Podfile. Щоб це зробити, докинемо наступні рядки в самий кінець файлу:
post_install do |installer|
Dir.glob(installer.sandbox.target_support_files_root + "Pods-*/*.sh").each do |script|
flag_name = File.basename(script, ".sh") + "-Installation-Flag"
folder = "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
file = File.join(folder, flag_name)
content = File.read(script)
content.gsub!(/set -e/, "set -e\nKG_FILE=\"#{file}\"\nif [ -f \"$KG_FILE\" ]; then exit 0; fi\nmkdir -p \"#{folder}\"\ntouch \"$KG_FILE\"")
File.write(script, content)
end
end

Принцип роботи цього скрипта такий же: він додає скрипти копіювання ресурсів перевірку на файл-прапор. При цьому не змінює build phases, на відміну від Carthage, а відразу змінює сам скрипт cocoapods.
до Речі, якщо у вас вже був задіяний блок post_install, то не треба створювати ще один, досить помістити скрипт всередину вже наявного. Останні версії Cocoapods(1.1.1) виводять попередження, якщо ви напортачите, а ось більш ранні просто мовчки проковтнуть помилку, забезпечивши вам веселу налагодження.
Тепер можна зробити 'pod install', щоб зміни вступили в силу. Як і у випадку з Carthage, при редагуванні Podfile, теж потрібен повний clean проекту перед запуском.
image
p.s. Ruby не моя рідна мова, притримає капці.
Висновок
Таким чином, у нас вийшло скоротити час складання проекту з 45-и секунд до 5-в.
Трохи не забув пруфы. Час збірки:
image
Скріншот взято з відео до минулого статті.
Час збірки:
<img src=«cloud.kilograpp.com/f/2d8c4f0829/?dl=1&t=1 alt=»image"/>
На мій погляд, дуже значущий результат. Підхід з скороченням витрат має обов'язково стояти на озброєнні кожної компанії і розробника.
Впевнений, що серед вас знайдуться люди з досвідом оптимізації Xcode, готові доповнити і поділитися думками. Та й взагалі, хотілося б в коментарях бачити питання, щоб в наступних статтях згадував вас, ваш досвід, досвід ваших колег і висвітлював тематику, оскільки для мене це хобі, життя і робота.
Наостанок поділюся утилітою, якою я користувався для вимірювання часу компіляції методів: https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode
Показує індивідуально кожну функцію і сумарний час, витрачений на її складання.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.