Конфигурация проекта в Xcode выглядит, как пульт управления космическим кораблем. Зачастую люди понимают, как работает система сборки, но путают термины. В этой статье мы пройдемся по структуре проекта, таргетам, настройкам конфигураций и воркспейсам. Поехали!
Не сказать, что тема свежая — Apple освещала её ещё в 2011 году на wwdc. И ещё раз в этом году.
Поэтому, чтобы добавить статье свежести, используем в качестве аналогии какую-нибудь модную технологию. Например, представим, что Xcode — 3d-принтер, способный печатать еду. И мы решили использовать его в качестве кухни для точки общественного питания.
Разберемся по порядку, ниже речь пойдет о:
- Project
- Target
- Build Settings
- Build Configuration
- Workspace
Project
К примеру, мы хотим кормить людей пиццей и поэтому создаем для нашего 3d-принтера специальный модуль, Pizza.project, который содержит информацию о том, как готовить абстрактную пиццу (форма, время приготовления, возможные ингредиенты и т.д.).
Документация:
Project — это репозиторий для файлов и ресурсов, необходимых для сборки программного продукта. Проект содержит все элементы, используемые для сборки ваших продуктов и поддерживает связи между этими элементами. Проект определяет настройки по умолчанию для всех таргетов в проекте.
Функции project'a:
- Содержит ссылки на файлы исходников, библиотеки и фреймворки, изображения, файлы Interface Builder’а (ксибки, сториборды).
- Описывает группы для структурирования файлов в Project Navigator’е (это те, сбивающие с толку псевдопапки, которые не имеют ничего общего со структурой проекта на диске).
- Описывает конфигурации сборки (об этом дальше)
- Содержит описание таргетов (об этом тоже дальше)
Вот так:
Внутри мы можем увидеть следующее:
- Файл .pbxproj. Редкому iOS-разработчику не приходилось мёржить его. Все мы знаем, что это здоровенный файл, который постоянно изменяется, поскольку содержит в себе почти всю информацию о проекте. Ссылки на исходники и ресурсы, описание группировки файлов, список таргетов, зависимостей таргетов, конфигурации сборки проектов, конфигурации сборки таргетов и не только. В общем, описание функций и внутреннего устройства этого файла достойно отдельной статьи.
- Папка xcuserdata. В ней хранится информация о состоянии проекта (какой файл открыт, какой таргет выбран и т.п.), брейкпоинты и схемы для текущего пользователя Xcode. Эту папку стоит добавить в ваш .gitignor-файл, если вы этого еще не сделали.
- Папка xcshareddata. Как можно догадаться из названия, папка отличается от предыдущей тем, что в ней хранятся настройки (схемы и брейкпоинты), которыми мы хотим поделиться со своими товарищами по разработке приложения.
- Чтобы поделиться брейкпоинтом: нужно зайти в брейкпоинт-навигатор, нажать правой клавишей на тот брейкпоинт, который вы хотите расшарить, и нажать Share Breakpoint.
- Чтобы поделиться схемой, нужно зайти в Product —> Scheme —> Manage schemes и поставить галочку в столбце Shared напротив схемы, которой вы хотите поделиться.
- Файл .xcworkspace(внутренний workspace).
Target
Поскольку абстрактную пиццу у нас никто не купит, нам нужно дополнительно загрузить в наш 3d-принтер несколько моделей конкретных пицц, которые, на основе нашего Pizza.project будет описывать 3d-принтеру, как печатать конкретную пиццу (какие из имеющихся ингредиентов использовать, какую температуру держать при готовке, какой должен быть размер). Такой моделью будет таргет.
Документация:
Таргет точно определяет, какой продукт будет собран, и содержит инструкции для сборки проекта из набора файлов воркспейса или проекта.
Как можно догадаться из названия, таргет описывает цель, к которой мы движемся в своей работе. Это может быть приложение под одну из четырех осей, библиотека или фреймворк, экстеншн или виджет, набор тестов. В Swift каждый Xcode-таргет представляется как отдельный модуль, который можно импортировать и обращаться к нему через его публичный API. Самое важное: мы можем иметь сразу несколько таргетов на одной кодовой базе, которые могут использовать одни и те же исходники и ресурсы. В зависимости от типа конечного продукта и операционной системы, на которой он будет работать, таргет может нести разный набор ответственностей.
Функции target'a:
- Определяет набор исходников и ресурсов, используемых для сборки продукта. Мы можем включать в проект и исключать из него конкретный ресурс или файл с исходным кодом (в разделе Compile Sources в Build Phases, или в File Inspector, выбрав нужный файл).
- Хранит настройки сборки проекта. Мы можем поменять для какого-то продукта какую-то настройку, не меняя настроек для сборки остальных продуктов.
- Описывает фазы сборки. Это набор упорядоченных действий (вроде копирования файлов в бандл, линковки фреймворков сборка ресурсов и исходников), которые xcode будет совершать, чтобы получить продукт. Мы можем менять этот порядок, а так же добавлять свои фазы в виде shellscript’ов. Зачастую бывает нужно правильно выбрать место для своего скрипта в порядке выполнения фаз, например, если мы хотим модифицировать какой-то файл из бандла, нам нужно применить соответствующий скрипт после того, как Xcode скопирует файлы в бандл.
Также мы можем добавить к таргету зависимости, помогая Xcode определить порядок сборки продуктов. Возвращаясь к нашему примеру с пиццей, мы можем предложить нашим клиентам комбо из двух пицц, добавив таргет PizzaCombo.
Как видно на скриншоте, для этого нам пришлось создать таргеты для Гавайской пиццы и Маргариты в виде фреймворков. Теперь нам осталось лишь добавить их в виде зависимостей к таргету PizzaCombo, и мы сможем пользоваться этими продуктами внутри другого продукта.
Build Settings
Помимо правильных ингредиентов для приготовления правильной пиццы наш 3d-принтер должен соблюсти ряд правил. Некоторые из них специфичны для проекта пиццы вообще (например, все пиццы должны печататься одной формы), некоторые специфичны для конкретной пиццы (например, Гавайская пицца должна готовиться при иной температуре, нежели Маргарита, иначе ананасы будут сухими).
Документация:
Настройка сборки — это переменная, которая содержит информацию о том, как должен быть выполнен какой-то конкретный аспект процесса сборки.
Когда я впервые заглянул в раздел Build Settings, моей первой ассоциацией было:
Однако к счастью, нам не обязательно в повседневной разработке знать все имеющиеся настройки и их ключи, а постоянно мы используем лишь некоторые.
Каждая настройка состоит из ключа, значения и заголовка для ключа в человекочитаемом формате (чтобы можно было уместить во вкладке Build Settings).
Значение для настройки может быть установлено как на уровне проекта, так и на уровне таргета. Если на каком-то из уровней значение для настройки не было установлено, она наследует значение с предыдущего уровня.
Наследование осуществляется в следующем порядке (от меньшего к большему):
- Базовое значение для платформы
- Значение из xcconfig-файла проекта
- Значение, установленное в проекте
- Значение из xcconfig-файла таргета
- Значение, установленное в таргете
Понять, на каком уровне установлено значение для той или иной настройки в Xcode можно с помощью кнопки Levels в разделе Build Settings. Зеленым цветом будет отмечено значение, которое будет установлено при сборке.
Важно знать, что мы можем добавлять свои User-Defined настройки и использовать их, например, в фазах сборки или в Info.plist-файле (хотя и с некоторыми ограничениями).
Также стоит упомянуть о возможности выставления условных настроек. То есть иметь разные значения для одной и той же настройки в зависимости от платформы, для которой продукт будет собран. Для более «умного» и гибкого разделения настроек нам следует воспользоваться конфигурациями сборки.
Build Configuration
К примеру мы решили, что каждая пицца будет представлена в обычном и диетическом варианте. Диетический вариант пиццы будет не таким поджаренными, жареная ветчина будет заменена на вареную, тесто будет без соли. Тогда два варианта одной и той же пиццы будут различаться целым набором настроек нашего принтера. Чтобы не создавать новые таргеты для каждой из уже имеющихся пицц, а применять лишь различные наборы настроек для печатания, мы используем конфигурации сборки.
Документация:
Конфигурация сборки определяет набор настроек сборки, используемых для сборки продукта таргета определенным образом.
По умолчанию Xcode создает нам две конфигурации Debug и Release, однако мы можем добавить столько конфигураций, сколько нам потребуется.
Таким образом, создав конфигурации для обычной и диетической пиццы, мы можем выставить для каждой отличающейся настройки два значения, соответствующих каждой из конфигураций, а при сборке просто устанавливать нужную в данный момент конфигурацию.
Так можно, например, разделять сборки приложения для работы с тестовым и продуктивным серверами.
Мы можем вынести конфигурации сборки в xcconfig-файлы, чтобы снять одну из ответственностей .pbxproj-файла и облегчить мёрдж при одновременном изменении настроек. Это можно сделать, например, с помощью этого инструмента.
Workspace
Вот мы начали печатать свою пиццу, добавили пару вариантов пицц, собрали обратную связь с наших клиентов и оказалось, что большинству из них нравится наша пицца, однако они бы хотели есть её вместе с роллами. На основе Pizza.project сделать ролл у нас не получится, поэтому мы добавляем в наш 3d-принтер проект Sushi.project, создаем на его основе пару моделей (таргетов) для конкретных роллов. Теперь мы можем печатать и пиццу, и роллы, однако раз наши клиенты хотят есть их вместе, сделаем им такое предложение. Мы создаем Pizza.workspace, в который включаем проект с суши. Теперь мы можем сформировать таргеты для комбинированных бизнес-ланчей, в которые будут включены как конкретная пицца, так и конкретный ролл. Также в этот проект мы можем включить стороннюю библиотеку по печати кальянов, которую будем использовать и при печати пиццы, и при печати роллов, и при печати комбинированных бизнес-ланчей. Например, пусть первый вариант бизнес-ланча будет включать в себя гавайскую пиццу, роллы «Филадельфия» и кальян из сторонней библиотеки:
Документация:
Workspace — Xcode-документ, группирующий проекты и другие документы для работы с ними как с единым целым.
Проще говоря, воркспейс позволяет нам объединить любое количество проектов и относящихся к ним файлов в едином контейнере. Что это нам даёт?
Функции workspace’a:
- Предоставляет доступ к каждому файлу в каждом включенном проекте.
- Расширяет область видимости. Благодаря этому автодополнение, поиск и переход к определению работают по всем включенным в workspace проектам.
- Помогает Xcode разрулить зависимости таргетов и собирает проекты в соответствующей последовательности. Например, мы можем импортировать Swift-модуль таргета одного проекта в другом проекте, если эти проекты лежат в одном воркспейсе.
Давайте заглянем внутрь .xcworkspace, как мы это сделали с .xcodeproj. Первым делом мы там увидим contents.xcworkspacedata — XMLку, описывающую включенные в воркспейс проекты и файлы.
Также мы можем увидеть уже знакомые нам папки .xcuserdata и .xcshareddata. Мы можем сами решать, куда, например, сохранить схему: на уровень проекта или на уровень воркспейса. В зависимости от нашего выбора она появится либо в одной из папок в .xcodeproj, либо в одной из папок в .xcworkspace.
Что в итоге
Сегодня мы узнали, что проект — это репозиторий для файлов проекта, таргетов, конфигураций и базовых настроек. Что таргет — это набор инструкций для сборки продукта на основе проекта, которому этот таргет принадлежит. Что настройки сборки — это пары «ключ-значение», которые описывают какой-то аспект сборки и могут выставляться как на уровне проекта, так и на уровне таргета. Что конфигурация сборки — набор значений для каждой настройки сборки, объединенных какой-то общей идеей. И наконец, что воркспейс — контейнер для удобного совместного использования нескольких проектов. Надеюсь, прочитав эту статью, вы не будете путаться в терминологии и вспоминать кто же отвечает за фазы сборки: воркспейс или таргет?
Автор: REDMADROBOT