Системы CI для разработки под iOS: трансформация Intel в ARM

Опубликовано: 2024-02-14

В постоянно развивающемся мире технологий компании должны адаптироваться к ветру перемен, чтобы оставаться актуальными и конкурентоспособными. Одним из таких преобразований, которое произвело настоящий фурор в мире технологий, является переход от архитектуры Intel x86_64 к архитектуре iOS ARM, примером которого является новаторский чип Apple M1. В этом контексте системы CI для iOS стали решающим фактором для компаний, реализующих этот переход, гарантируя, что процессы разработки и тестирования программного обеспечения остаются эффективными и соответствуют новейшим технологическим стандартам.

Apple анонсировала свои чипы M1 почти три года назад, и с тех пор стало ясно, что компания примет архитектуру ARM и в конечном итоге прекратит поддержку программного обеспечения на базе Intel. Чтобы обеспечить совместимость между архитектурами, Apple представила новую версию Rosetta, своей собственной среды двоичной трансляции, которая доказала свою надежность в прошлом во время существенного преобразования архитектуры с PowerPC на Intel в 2006 году. Преобразование все еще продолжается, и мы видели Xcode теряет поддержку Rosetta в версии 14.3.

В Miquido мы осознали необходимость перехода с Intel на ARM несколько лет назад. Мы начали подготовку в середине 2021 года. Как компания-разработчик программного обеспечения, имеющая одновременно множество клиентов, приложений и проектов, мы столкнулись с несколькими проблемами, которые нам пришлось преодолеть. Эта статья может стать вашим практическим руководством, если ваша компания столкнулась с похожей ситуацией. Описанные ситуации и решения описаны с точки зрения разработки iOS, но вы можете найти идеи, подходящие и для других технологий. Важным компонентом нашей стратегии перехода было обеспечение полной оптимизации нашей системы CI для iOS для новой архитектуры, что подчеркивает важность Системы CI для iOS помогают поддерживать эффективные рабочие процессы и высококачественные результаты в условиях столь значительных изменений.

Проблема: миграция архитектуры Intel на iOS ARM

Apple выпустила свой чип M1 в 2020 году

Миграционный процесс разделился на две основные ветви.

1. Замена существующих компьютеров разработчиков на базе процессоров Intel на новые Macbook M1.

Этот процесс должен был быть относительно простым. Мы установили политику постепенной замены всех компьютеров Intel Macbook от разработчиков в течение двух лет. В настоящее время 95% наших сотрудников используют Macbook на базе ARM.

Однако в ходе этого процесса мы столкнулись с некоторыми неожиданными проблемами. В середине 2021 года нехватка компьютеров Mac M1 замедлила процесс замены. К концу 2021 года нам удалось заменить лишь несколько Macbook из почти 200 ожидающих. По нашим оценкам, потребуется около двух лет, чтобы полностью заменить все компьютеры Intel Mac компании на Macbook M1, включая инженеров, не использующих iOS.

К счастью, Apple выпустила свои новые чипы M1 Pro и M2. В результате мы сместили акцент с замены процессоров Intel на компьютеры Mac M1 на замену их чипами M1 Pro и M2.

Программное обеспечение, не готовое к переходу, вызвало разочарование разработчиков

Первым инженерам, получившим новые Macbook M1, пришлось нелегко, поскольку большая часть программного обеспечения не была готова к переходу на новую архитектуру Apple iOS ARM. Больше всего пострадали сторонние инструменты, такие как Rubygems и Cocoapods, которые представляют собой инструменты управления зависимостями, основанные на многих других Rubygems. Некоторые из этих инструментов тогда не были скомпилированы для архитектуры iOS ARM, поэтому большую часть программного обеспечения приходилось запускать с использованием Rosetta, что вызывало проблемы с производительностью и разочарование.

Однако создатели программного обеспечения работали над решением большинства этих проблем по мере их возникновения. Момент прорыва наступил с выпуском Xcode 14.3, который больше не имел поддержки Rosetta. Это был четкий сигнал всем разработчикам программного обеспечения о том, что Apple настаивает на переходе архитектуры Intel на ARM-архитектуру iOS. Это вынудило большинство сторонних разработчиков программного обеспечения, которые ранее полагались на Rosetta, перенести свое программное обеспечение на ARM. В настоящее время 99% стороннего программного обеспечения, ежедневно используемого в Miquido, работает без Rosetta.

2. Замена системы CI Miquido для iOS

Замена системы непрерывной интеграции iOS в Miquido оказалась более сложной задачей, чем простая замена машин. Сначала взгляните на нашу инфраструктуру того времени:

CI-архитектура в Miquido. CI-система для трансформации iOS.

У нас был облачный экземпляр Gitlab и 9 подключенных к нему компьютеров Mac Mini на базе процессоров Intel. Эти машины выполняли задачи, а Gitlab отвечал за оркестровку. Всякий раз, когда задание CI попадало в очередь, Gitlab назначал его первому доступному исполнителю, который соответствовал требованиям проекта, указанным в файле gitlab-ci.yml. Gitlab создавал сценарий задания, содержащий все команды сборки, переменные, пути и т. д. Затем этот сценарий был перенесен на бегун и выполнен на этом компьютере.

Хотя эта установка может показаться надежной, мы столкнулись с проблемами виртуализации из-за плохой поддержки процессоров Intel. В результате мы решили не использовать виртуализацию, такую ​​как Docker, и выполнять задания на самих физических машинах. Мы попытались создать эффективное и надежное решение на основе Docker, но ограничения виртуализации, такие как отсутствие ускорения графического процессора, привели к тому, что выполнение заданий занимало вдвое больше времени, чем на физических машинах. Это привело к увеличению накладных расходов и быстрому заполнению очередей.

Из-за соглашения об уровне обслуживания macOS мы могли настроить только две виртуальные машины одновременно. Поэтому мы решили расширить пул физических исполнителей и настроить их для выполнения заданий Gitlab непосредственно в их операционной системе. Однако этот подход имел и несколько недостатков.

Проблемы в процессе сборки и управлении бегунами

  1. Никакой изоляции сборок за пределами изолированной программной среды каталога сборки.

Средство запуска выполняет каждую сборку на физическом компьютере, а это означает, что сборки не изолированы от изолированной программной среды каталога сборки. В этом есть свои преимущества и недостатки. С одной стороны, мы можем использовать системные кэши для ускорения сборки, поскольку большинство проектов используют один и тот же набор сторонних зависимостей.

С другой стороны, кэш становится необслуживаемым, поскольку остатки одного проекта могут повлиять на все остальные проекты. Это особенно важно для общесистемных кешей, поскольку для разработки Flutter и React Native используются одни и те же средства выполнения. В частности, React Native требует множества зависимостей, кэшированных через NPM.

  1. Возможная путаница с системными инструментами.

Хотя ни одно из заданий не было выполнено с привилегиями sudo, у них все же была возможность получить доступ к некоторым системным или пользовательским инструментам, таким как Ruby. Это создавало потенциальную угрозу взлома некоторых из этих инструментов, особенно потому, что macOS использует Ruby для некоторых своих устаревших программ, включая некоторые устаревшие функции Xcode. Системная версия Ruby — это не то, с чем вам хотелось бы связываться.

Однако введение rbenv создает еще один уровень сложности. Важно отметить, что Rubygems устанавливаются для каждой версии Ruby, и для некоторых из этих драгоценных камней требуются определенные версии Ruby. Почти все сторонние инструменты, которые мы использовали, были зависимы от Ruby, причем главными действующими лицами были Cocoapods и Fastlane.

  1. Управление подписывающими удостоверениями.

Управление несколькими удостоверениями подписи из различных учетных записей клиентских разработчиков может стать проблемой, когда речь идет о системных цепочках для ключей на бегунах. Идентификатор подписи — это очень конфиденциальная часть данных, поскольку он позволяет нам кодировать приложение, что делает его уязвимым для потенциальных угроз.

Для обеспечения безопасности идентификационные данные должны быть изолированы в разных проектах и ​​защищены. Однако этот процесс может стать кошмаром, учитывая дополнительную сложность, которую macOS вносит в реализацию связки ключей.

  1. Проблемы в многопроектной среде.

Не все проекты создавались с использованием одних и тех же инструментов, особенно Xcode. Некоторые проекты, особенно находящиеся на этапе поддержки, поддерживались с использованием последней версии Xcode, с помощью которой проект был разработан. Это означает, что если по этим проектам требовалась какая-либо работа, CI должен был быть способен ее выполнить. В результате исполнителям приходилось одновременно поддерживать несколько версий Xcode, что эффективно сужало количество исполнителей, доступных для конкретной работы.

5. Требуются дополнительные усилия.

Любые изменения, внесенные на бегунах, например, установка программного обеспечения, должны выполняться на всех бегунах одновременно. Хотя для этого у нас был инструмент автоматизации, поддержка сценариев автоматизации требовала дополнительных усилий.

Индивидуальные инфраструктурные решения для разнообразных потребностей клиентов

Miquido — это компания-разработчик программного обеспечения, которая работает с несколькими клиентами с разными потребностями. Мы адаптируем наши услуги к конкретным требованиям каждого клиента. Мы часто размещаем базу кода и необходимую инфраструктуру для малого бизнеса или стартапов, поскольку им может не хватать ресурсов или знаний для ее обслуживания.

Корпоративные клиенты обычно имеют собственную инфраструктуру для размещения своих проектов. Однако некоторые из них не имеют для этого возможности или обязаны согласно отраслевым нормам использовать свою инфраструктуру. Они также предпочитают не использовать сторонние SaaS-сервисы, такие как Xcode Cloud или Codemagic. Вместо этого им нужно решение, которое соответствует их существующей архитектуре.

Чтобы удовлетворить потребности этих клиентов, мы часто размещаем проекты в нашей инфраструктуре или настраиваем ту же конфигурацию iOS с непрерывной интеграцией в их инфраструктуре. Однако мы проявляем особую осторожность при работе с конфиденциальной информацией и файлами, например при подписании удостоверений личности.

Использование Fastlane для эффективного управления сборкой

Здесь Fastlane может оказаться удобным инструментом. Он состоит из различных модулей, называемых действиями, которые помогают оптимизировать процесс и разделить его между разными клиентами. Одно из этих действий, называемое сопоставлением, помогает поддерживать удостоверения подписи разработки и производства, а также профили подготовки. Он также работает на уровне ОС, разделяя эти идентификаторы в отдельные цепочки для ключей на время сборки, и выполняет очистку после сборки, что очень полезно, поскольку мы запускаем все наши сборки на физических машинах.

Fastlane: инструмент автоматизации разработки
Кредиты изображений: Fastlane

Изначально мы обратились к Fastlane по определенной причине, но обнаружили, что у него есть дополнительные функции, которые могут быть нам полезны.

  1. Загрузка сборки в Testflight

Раньше API AppStoreConnect не был общедоступным для разработчиков. Это означало, что единственный способ загрузить сборку в Testflight — через Xcode или Fastlane. Fastlane — это инструмент, который, по сути, очищает API ASC и превращает его в действие под названием «пилот» . Однако этот метод часто ломался со следующим обновлением Xcode. Если разработчик хотел загрузить свою сборку в Testflight с помощью командной строки, Fastlane был лучшим доступным вариантом.

  1. Простое переключение между версиями Xcode

Имея более одного экземпляра Xcode на одной машине, необходимо было выбрать, какой Xcode использовать для сборки. К сожалению, Apple сделала неудобным переключение между версиями Xcode — для этого вам нужно использовать «xcode-select», что дополнительно требует привилегий sudo. Fastlane также охватывает это.

  1. Дополнительные утилиты для разработчиков

Fastlane предоставляет множество других полезных утилит, включая управление версиями и возможность отправлять результаты сборки в веб-перехватчики.

Недостатки Fastlane

Адаптация Fastlane к нашим проектам была разумной и надежной, поэтому мы пошли в этом направлении. Мы успешно использовали его в течение нескольких лет. Однако за эти годы мы выявили несколько проблем:

  1. Fastlane требует знаний Ruby.

Fastlane — это инструмент, написанный на Ruby, и для его эффективного использования требуется хорошее знание Ruby. Если в конфигурации Fastlane или в самом инструменте есть ошибки, их отладка с помощью irb или pry может оказаться довольно сложной задачей.

  1. Зависимость от многочисленных драгоценных камней.

Сама Fastlane использует примерно 70 драгоценных камней. Чтобы снизить риски разрушения системы Ruby, в проектах использовались локальные гем-пакеты. Получение всех этих драгоценных камней отнимало много времени.

  1. Проблемы с системой Ruby и Rubygems.

В результате все проблемы с системой Ruby и Rubygems, упомянутые ранее, также применимы и здесь.

  1. Избыточность для проектов Flutter.

Проекты Flutter также были вынуждены использовать Fastlane Match только для того, чтобы сохранить совместимость с проектами iOS и защитить цепочки для ключей бегуна. В этом была абсурдная необходимость, поскольку Flutter имеет собственную встроенную систему сборки, а упомянутые ранее накладные расходы были введены только для управления подписыванием удостоверений и профилями подготовки.

Большинство этих проблем были исправлены в процессе, но нам нужно было более надежное и надежное решение.

Идея: адаптация новых, более надежных инструментов непрерывной интеграции для iOS.

Хорошей новостью является то, что Apple получила полный контроль над своей архитектурой чипов и разработала новую среду виртуализации для macOS. Эта платформа позволяет пользователям создавать, настраивать и запускать виртуальные машины Linux или macOS, которые быстро запускаются и характеризуются производительностью, сравнимой с нативной (я действительно имею в виду нативную).

Это выглядело многообещающе и могло стать краеугольным камнем для наших новых инструментов непрерывной интеграции для iOS. Однако это была лишь часть комплексного решения. Имея инструмент управления виртуальными машинами, нам также нужно было что-то, что могло бы использовать эту среду в координации с нашими исполнителями Gitlab.

При этом большинство наших проблем, связанных с низкой производительностью виртуализации, станут устаревшими. Это также позволило бы нам автоматически решить большинство проблем, которые мы намеревались решить с помощью Fastlane.

Разработка индивидуального решения для управления идентификацией подписи по требованию.

Нам нужно было решить еще одну проблему — управление подписью удостоверений. Мы не хотели использовать для этого Fastlane, поскольку это казалось чрезмерным для наших нужд. Вместо этого мы искали решение, которое больше соответствовало бы нашим требованиям. Наши требования были просты: процесс управления идентификацией должен был выполняться по требованию, исключительно на время сборки, без каких-либо предустановленных идентификаторов в связке ключей и быть совместимым с любой машиной, на которой он будет работать.

Проблема распространения и отсутствие стабильного API AppstoreConnect устарели, когда Apple выпустила свой «альтернативный инструмент», который позволял обмениваться данными между пользователями и ASC.

Итак, у нас возникла идея, и нам нужно было найти способ соединить эти три аспекта вместе:

  1. Поиск способа использования платформы виртуализации Apple.
  2. Заставляем его работать с бегунами Gitlab.
  3. Поиск решения для управления подписью удостоверений в нескольких проектах и ​​исполнителях.

Решение: краткий обзор нашего подхода (инструменты включены)

Мы начали искать решения для решения всех проблем, упомянутых ранее.

  1. Использование платформы виртуализации Apple.

Для первого препятствия мы нашли решение довольно быстро: наткнулись на инструмент для пирога от Cirrus Labs. С первого момента мы знали, что это будет наш выбор.

Наиболее значительными преимуществами использования инструмента для тарта, предлагаемого Cirrus Lab, являются:

  • Возможность создания виртуальных машин из необработанных изображений .ipsw.
  • Возможность создания виртуальных машин с использованием предварительно упакованных шаблонов (с установленными некоторыми служебными инструментами, такими как Brew или Xcode), доступных на странице Cirrus Labs GitHub.
  • Инструмент Tart использует упаковщик для поддержки динамического создания изображений.
  • Инструмент Tart поддерживает образы как Linux, так и MacOS.
  • Инструмент использует выдающуюся особенность файловой системы APFS, которая позволяет дублировать файлы без фактического резервирования для них дискового пространства. Таким образом, вам не нужно выделять дисковое пространство, размер которого в 3 раза превышает размер исходного образа. Вам нужно достаточно места на диске только для исходного образа, а клон занимает только то место, которое является разницей между ним и исходным образом. Это невероятно полезно, тем более что образы macOS, как правило, довольно большие.

Например, для рабочего образа macOS Ventura с установленными Xcode и другими утилитами требуется минимум 60 ГБ дискового пространства. В обычных обстоятельствах образ и два его клона заняли бы до 180 ГБ дискового пространства, что является значительным объемом. И это только начало, поскольку вы можете захотеть иметь более одного исходного образа или установить несколько версий Xcode на одной виртуальной машине, что еще больше увеличит ее размер.

  • Этот инструмент позволяет управлять IP-адресами исходных и клонированных виртуальных машин, обеспечивая доступ к виртуальным машинам по SSH.
  • Возможность перекрестного монтирования каталогов между хост-компьютером и виртуальными машинами.
  • Инструмент удобен для пользователя и имеет очень простой интерфейс командной строки.

Вряд ли в этом инструменте есть что-то, чего не хватает с точки зрения использования его для управления виртуальными машинами. Практически ничего, кроме одного: хоть и многообещающий плагин-упаковщик для сборки образов «на лету», он занимал слишком много времени, поэтому мы решили его не использовать.

Мы попробовали тарт, и он сработал фантастически. Его производительность была на уровне родной, а управление было простым.

Успешно внедрив пирог с впечатляющими результатами, мы сосредоточились на решении других задач.

  1. Ищем способ совместить tart с раннерами Gitlab.

После решения первой проблемы перед нами встал вопрос, как совместить tart с раннерами Gitlab.

Начнем с описания того, что на самом деле делают бегуны Gitlab:

Упрощенная схема делегирования заданий Gitlab. CI-система для iOS

Нам нужно было включить в диаграмму дополнительную головоломку, которая включала в себя распределение задач от хоста-бегуна к виртуальной машине. Задание GitLab — это сценарий оболочки, содержащий важные переменные, записи PATH и команды.

Нашей целью было перенести этот скрипт на виртуальную машину и запустить его.

Однако эта задача оказалась более сложной, чем мы думали изначально.

Бегун

Стандартные исполнители Gitlab, такие как Docker или SSH, просты в настройке и практически не требуют настройки. Однако нам нужен был больший контроль над конфигурацией, что побудило нас изучить специальные исполнители, предоставляемые GitLab.

Пользовательские исполнители — отличный вариант для нестандартных конфигураций, поскольку каждый шаг выполнения (подготовка, выполнение, очистка) описывается в форме сценария оболочки. Единственное, чего не хватало, — это инструмента командной строки, который мог бы выполнять необходимые нам задачи и запускаться в сценариях конфигурации бегуна.

В настоящее время существует несколько инструментов, которые делают именно это — например, исполнитель CirrusLabs Gitlab tart. Этот инструмент — именно то, что мы искали в то время. Однако его еще не существовало, и после проведения исследований мы не нашли ни одного инструмента, который мог бы помочь нам выполнить нашу задачу.

Написание собственного решения

Поскольку идеального решения найти не удалось, мы написали его сами. Мы ведь инженеры! Идея показалась солидной, и у нас были все необходимые инструменты, поэтому мы приступили к разработке.

Мы решили использовать Swift и пару библиотек с открытым исходным кодом, предоставленных Apple: Swift Argument Parser для обработки выполнения командной строки и Swift NIO для обработки SSH-соединения с виртуальными машинами. Мы начали разработку и через пару дней получили первый работающий прототип инструмента, который в итоге превратился в MQVMRunner.

Инфраструктура iOS CI: MQVMRunner

На высоком уровне инструмент работает следующим образом:

  1. (Подготовительный шаг)
    1. Прочтите переменные, указанные в gitlab-ci.yml (имя изображения и дополнительные переменные).
    2. Выберите запрошенную базу виртуальных машин.
    3. Клонируйте запрошенную базу ВМ.
    4. Настройте каталог для перекрестного подключения и скопируйте в него сценарий задания Gitlab, установив для него необходимые разрешения.
    5. Запустите клон и проверьте SSH-соединение.
    6. При необходимости настройте все необходимые зависимости (например, версию Xcode).
  2. (Выполнить шаг)
    1. Запустите задание Gitlab, выполняющее скрипт из кросс-монтированного каталога на подготовленном клоне виртуальной машины через SSH.
  3. (Этап очистки)
    1. Удалить клонированное изображение.

Проблемы в развитии

В ходе разработки мы столкнулись с несколькими проблемами, из-за которых все пошло не так гладко, как нам хотелось.

  1. Управление IP-адресами.

Управление IP-адресами — важнейшая задача, к которой следует относиться с осторожностью. В прототипе обработка SSH была реализована с использованием прямых и жестко закодированных команд оболочки SSH. Однако в случае неинтерактивных оболочек рекомендуется использовать аутентификацию по ключу. Дополнительно желательно добавить хост в файлknown_hosts, чтобы избежать перебоев. Тем не менее, из-за динамического управления IP-адресами виртуальных машин существует вероятность дублирования записи для определенного IP-адреса, что приводит к ошибкам. Поэтому нам необходимо динамически назначать известные_хосты для конкретного задания, чтобы предотвратить такие проблемы.

  1. Чистое решение Swift.

Учитывая это, а также тот факт, что жестко закодированные команды оболочки в коде Swift не очень элегантны, мы подумали, что было бы неплохо использовать специальную библиотеку Swift, и решили использовать Swift NIO. Мы решили некоторые проблемы, но в то же время добавили пару новых, таких как, например, иногда журналы, размещенные на стандартном выводе, передавались *после* закрытия канала SSH из-за завершения выполнения команды — и, как мы основывались на что вывел в дальнейшей работе, выполнение случайно провалилось.

  1. Выбор версии Xcode.

Поскольку плагин Packer не подходил для создания динамических образов из-за затрат времени, мы решили использовать одну базу виртуальных машин с несколькими предустановленными версиями Xcode. Нам нужно было найти способ, с помощью которого разработчики могли бы указать нужную им версию Xcode в своем gitlab-ci.yml — и мы придумали пользовательские переменные, доступные для использования в любом проекте. Затем MQVMRunner выполнит команду «xcode-select» на клонированной виртуальной машине, чтобы установить соответствующую версию Xcode.

И многие, многие другие

Оптимизация миграции проектов и непрерывная интеграция рабочего процесса iOS с Mac Studios

Мы установили это на двух новых Mac Studios и начали мигрировать проекты. Мы хотели сделать процесс миграции для наших разработчиков максимально прозрачным. Нам не удалось сделать это полностью гладко, но в итоге мы дошли до того, что в gitlab-ci.yml пришлось сделать всего пару вещей:

  • Теги бегунов: использовать Mac Studios вместо Intel.
  • Имя образа: необязательный параметр, введенный для будущей совместимости, если нам понадобится более одной базовой виртуальной машины. Сейчас по умолчанию всегда используется единственная базовая виртуальная машина, которая у нас есть.
  • Версия Xcode: необязательный параметр; если не указано, будет использоваться новейшая доступная версия.

Инструмент получил очень хорошие первоначальные отзывы, поэтому мы решили сделать его открытым исходным кодом. Мы добавили скрипт установки для настройки Gitlab Custom Runner и всех необходимых действий и переменных. Используя наш инструмент, вы можете настроить свой собственный GitLab Runner за считанные минуты — единственное, что вам нужно, — это стартовая и базовая виртуальная машина, на которой будут выполняться задания.

Окончательная структура непрерывной интеграции для iOS выглядит следующим образом:

Финальная инфраструктура CI: MQVMRunner

3. Решение для эффективного управления идентификацией

Мы изо всех сил пытались найти эффективное решение для управления подписывающими удостоверениями наших клиентов. Это было особенно сложно, поскольку подпись удостоверения личности — это высококонфиденциальные данные, которые нельзя хранить в незащищенном месте дольше, чем это необходимо.

Кроме того, мы хотели загружать эти удостоверения только во время сборки, без каких-либо межпроектных решений. Это означало, что удостоверение не должно быть доступно за пределами изолированной программной среды приложения (или сборки). Последнюю проблему мы уже решили, перейдя на виртуальные машины. Однако нам все еще нужно было найти способ хранить и загружать подписывающее удостоверение в виртуальную машину только на время сборки.

Проблемы с Fastlane Match

В то время мы все еще использовали сопоставление Fastlane, которое хранит зашифрованные идентификаторы и положения в отдельном репозитории, загружает их во время процесса сборки в отдельный экземпляр связки ключей и удаляет этот экземпляр после сборки.

Этот подход кажется удобным, но у него есть некоторые проблемы:

  • Для работы требуется вся настройка Fastlane.

Fastlane — это Rubygem, и здесь применимы все проблемы, перечисленные в первой главе.

  • Проверка репозитория во время сборки.

Мы хранили наши идентификационные данные в отдельном репозитории, который извлекался в процессе сборки, а не в процессе установки. Это означало, что нам пришлось установить отдельный доступ к репозиторию идентификаторов не только для Gitlab, но и для конкретных исполнителей, аналогично тому, как мы обрабатывали частные сторонние зависимости.

  • Трудно управлять вне Матча.

Если вы используете Match для управления удостоверениями или инициализацией, ручное вмешательство практически не требуется. Редактирование, расшифровка и шифрование профилей вручную, чтобы совпадения могли работать с ними позже, утомительно и отнимает много времени. Использование Fastlane для выполнения этого процесса обычно приводит к полному удалению настройки подготовки приложений и созданию новой.

  • Немного сложно отладить.

В случае возникновения каких-либо проблем с подписью кода вам может быть сложно определить соответствие идентификатора и обеспечения, которые только что были установлены, поскольку вам придется сначала их декодировать.

  • Проблемы безопасности.

Сопоставьте доступные учетные записи разработчиков, используя предоставленные учетные данные, чтобы внести изменения от их имени. Несмотря на то, что Fastlane имеет открытый исходный код, некоторые клиенты отказались от него из соображений безопасности.

  • И последнее, но не менее важное: избавление от Match устранит самое большое препятствие на нашем пути к полному избавлению от Fastlane.

Наши первоначальные требования были следующими:

  • Для загрузки необходимо подписать удостоверение из безопасного места, желательно в неоткрытой текстовой форме, и поместить его в цепочку для ключей.
  • Эта личность должна быть доступна Xcode.
  • Предпочтительно, чтобы переменные идентификационного пароля, имени связки ключей и пароля связки ключей были заданы для целей отладки.

В Match было все, что нам нужно, но реализация Fastlane только для использования Match казалась излишним, особенно для кроссплатформенных решений с собственной системой сборки. Нам хотелось чего-то похожего на Match, но без тяжелого бремени Ruby.

Создание собственного решения

Мы подумали – давайте напишем это сами! Мы сделали это с помощью MQVMRunner, поэтому можем сделать это и здесь. Мы также выбрали для этого Swift, главным образом потому, что мы можем бесплатно получить множество необходимых API, используя платформу Apple Security.

Конечно, все прошло не так гладко, как ожидалось.

  • Наличие системы безопасности.

Самая простая стратегия заключалась в вызове команд bash, как это делает Fastlane. Однако, имея доступную среду безопасности, мы подумали, что ее будет более элегантно использовать для разработки.

  • Недостаток опыта.

У нас не было большого опыта работы с платформой безопасности для macOS, и оказалось, что она значительно отличается от того, к чему мы привыкли в iOS. Это имело неприятные последствия во многих случаях, когда мы не знали об ограничениях macOS или предполагали, что она работает так же, как и на iOS — большинство этих предположений были ошибочными.

  • Ужасная документация.

Документация Apple Security framework, мягко говоря, скромная. Это очень старый API, восходящий к первым версиям OSX, и иногда у нас создавалось впечатление, что с тех пор он не обновлялся. Большая часть кода не документирована, но мы предусмотрели, как он работает, прочитав исходный код. К счастью для нас, это открытый исходный код.

  • Устаревшие без замен.

Большая часть этой структуры устарела; Apple пытается отойти от типичной цепочки для ключей в стиле macOS (несколько связок для ключей, доступных по паролю) и внедрить связку для ключей в стиле iOS (одна связка для ключей, синхронизируемая через iCloud). Таким образом, они объявили его устаревшим в macOS Yosemite еще в 2014 году, но за последние девять лет не придумали ему никакой замены — поэтому единственный доступный нам API на данный момент устарел, потому что нового еще нет.

Мы предположили, что идентификаторы подписи могут храниться в виде строк в кодировке Base64 в переменных Gitlab для каждого проекта. Это безопасно, основано на каждом проекте, и если оно установлено как замаскированная переменная, ее можно читать и отображать в журналах сборки как неоткрытый текст.

Итак, у нас были идентификационные данные. Нам оставалось только вставить его в брелок. Использование API безопасности. После нескольких попыток и тщательного изучения документации по платформе безопасности мы подготовили прототип чего-то, что позже стало MQSwiftSign.

Изучение системы безопасности macOS, но трудный путь

Для разработки нашего инструмента нам пришлось глубоко понять, как работает связка ключей macOS. Это включало исследование того, как связка ключей управляет элементами, их доступом и разрешениями, а также структуру данных связки ключей. Например, мы обнаружили, что связка ключей — единственный файл macOS, для которого операционная система игнорирует набор ACL. Кроме того, мы узнали, что ACL для определенных элементов связки ключей представляет собой простой текстовый список, сохраненный в файле связки ключей. На этом пути мы столкнулись с рядом проблем, но мы также многому научились.

Одной из серьезных проблем, с которыми мы столкнулись, были подсказки. Наш инструмент был в первую очередь разработан для работы в системах CI iOS, а это означало, что он должен был быть неинтерактивным. Мы не могли попросить пользователей подтвердить пароль на CI.

Однако система безопасности macOS хорошо спроектирована, что делает невозможным редактирование или чтение конфиденциальной информации, включая подписывающее удостоверение, без явного разрешения пользователя. Чтобы получить доступ к ресурсу без подтверждения, программа доступа должна быть включена в список управления доступом к ресурсу. Это строгое требование, которое не может нарушить ни одна программа, даже программы Apple, поставляемые в комплекте с системой. Если какой-либо программе необходимо прочитать или отредактировать запись связки ключей, пользователь должен предоставить пароль связки ключей, чтобы разблокировать ее, и, при необходимости, добавить его в список ACL записи.

Преодоление проблем с разрешениями пользователей

Итак, нам нужно было найти способ, с помощью которого Xcode мог бы получить доступ к удостоверению, установленному нашей связкой ключей, не спрашивая у пользователя разрешения с помощью запроса пароля. Для этого мы можем изменить список управления доступом к элементу, но для этого также требуется разрешение пользователя – и, конечно же, оно требуется. В противном случае это подорвет весь смысл наличия ACL. Мы пытались обойти эту защиту – мы пытались добиться того же эффекта, что и с помощью команды `security set-key-partition-list`.

После глубокого изучения документации фреймворка мы не нашли ни одного API, который позволял бы редактировать ACL, не запрашивая у пользователя ввода пароля. Самое близкое, что мы нашли, — это SecKeychainItemSetAccess, который каждый раз вызывает приглашение пользовательского интерфейса. Затем мы еще раз погрузились, но на этот раз в лучшую документацию, которой является сам исходный код. Как Apple это реализовала?

Оказалось, что, как и следовало ожидать, они использовали частный API. Метод под названием SecKeychainItemSetAccessWithPassword делает по сути то же самое, что и SecKeychainItemSetAccess, но вместо запроса пароля у пользователя пароль предоставляется в качестве аргумента функции. Конечно, как частный API, он не указан в документации, но у Apple нет документации по таким API, как будто они не могли подумать о создании приложения для личного или корпоративного использования. Поскольку инструмент предназначался только для внутреннего использования, мы без колебаний использовали частный API. Единственное, что нужно было сделать, — это соединить метод C со Swift.

Преодоление проблем с разрешениями пользователей

Итак, окончательный рабочий процесс прототипа выглядел следующим образом:

  1. Создайте временную разблокированную связку ключей с отключенной автоматической блокировкой.
  2. Получите и декодируйте данные идентификации подписи в кодировке Base64 из переменных среды (передаваемых Gitlab).
  3. Импортируйте удостоверение в созданную связку ключей.
  4. Установите правильные параметры доступа для импортированного удостоверения, чтобы Xcode и другие инструменты могли прочитать его для разработки кода.

Дальнейшие обновления

Прототип работал хорошо, поэтому мы определили несколько дополнительных функций, которые хотели бы добавить в инструмент. Нашей целью было в конечном итоге заменить Fastlane; мы уже реализовали действие match. Однако fastlane по-прежнему предлагал две ценные функции, которых у нас еще не было: установку профиля подготовки и создание файла Export.plist.

Установка профиля подготовки

Установка профиля подготовки довольно проста – она сводится к извлечению UUID профиля и копированию файла в `~/Library/MobileDevice/Provisioning Profiles/` с UUID в качестве имени файла – и этого достаточно, чтобы Xcode правильно его увидел. Добавление к нашему инструменту простого плагина для циклического обхода предоставленного каталога и выполнения этого для каждого файла .mobileprovision, который он находит внутри, не является сложной задачей.

Создание Export.plist

Однако создание файла Export.plist немного сложнее. Чтобы создать правильный файл IPA, Xcode требует, чтобы пользователи предоставили файл plist с конкретной информацией, собранной из различных источников — файла проекта, списка прав, настроек рабочей области и т. д. Причина, по которой Xcode может собирать эти данные только через мастер распространения, но не через CLI мне неизвестно. Однако мы должны были собрать их с помощью Swift API, имея только ссылки на проект/рабочую область и небольшую порцию знаний о том, как создается файл проекта Xcode.

Результат оказался лучше, чем ожидалось, поэтому мы решили добавить его в качестве еще одного плагина к нашему инструменту. Мы также выпустили его как проект с открытым исходным кодом для более широкой аудитории. На данный момент MQSwiftSign — это многоцелевой инструмент, который можно успешно использовать в качестве замены основных действий, необходимых для создания и распространения вашего приложения iOS, и мы используем его в каждом нашем проекте в Miquido.

Заключительные мысли: успех

Переход с архитектуры Intel на архитектуру iOS ARM оказался непростой задачей. Мы столкнулись с многочисленными препятствиями и потратили много времени на разработку инструментов из-за отсутствия документации. Однако в конечном итоге мы создали надежную систему:

  • Управлять двумя бегунами вместо девяти;
  • Запуск программного обеспечения, которое полностью находится под нашим контролем, без тонны накладных расходов в виде рубигемов — мы смогли избавиться от fastlane или любого стороннего программного обеспечения в наших конфигурациях сборки;
  • МНОГО знаний и понимания вещей, на которые мы обычно не обращаем внимания - например, безопасность системы macOS и сама структура безопасности, фактическая структура проекта Xcode и многое, многое другое.

Я с радостью подбадриваю вас: если у вас возникли проблемы с настройкой средства запуска GitLab для сборок iOS, попробуйте наш MQVMRunner. Если вам нужна помощь в создании и распространении вашего приложения с помощью одного инструмента и вы не хотите полагаться на Rubygems, попробуйте MQSwiftSign. У меня работает, может и у вас сработает!