Привет, Хабр! В преддверии старта курса "iOS Developer. Professional" подготовили для вас традиционный перевод полезного материала. Также приглашаем желающих на онлайн-встречу с преподавателем курса, на которой можно задать преподавателю интересующие вас вопросы об обучении.

И напоследок, предлагаем посмотреть запись вебинара
«Пишем приложение на SwiftUI и Combine».


I - Введение

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

Источник изображения: https://medium.com/@kautilyasave/xcode-merge-conflict-debugging-5904c7e0cc59
Источник изображения: https://medium.com/@kautilyasave/xcode-merge-conflict-debugging-5904c7e0cc59

К концу этой статьи у вас будет рабочая среда, обеспечивающая бесконфликтное слияние.

II - Прежде чем мы начнем

1. Знакомство с XcodeGen

yonaskolb/XcodeGen

github.com

XcodeGen — это консольная утилита, написанная на Swift, которая генерирует Xcode-проект на основе вашей структуры папок и спецификации проекта.

Для установки XcodeGen мы будем использовать Brew. Просто запустите в терминале brew install xcodegen (дополнительные параметры установки и инструкции можно найти здесь).

2. Проект

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

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

Пример структуры проекта
Пример структуры проекта

Как мы видим выше, проект содержит:

  • Зависимости фреймворков

  • Сборочные скрипты

III - Настройка

Образцы экранов приложения
Образцы экранов приложения

1. Настраиваем проект

Проект (sample project), который мы будем использовать в этом туториале, имеет carthage-зависимости. Просто запустите makeProject.sh в терминале. Это настроит проект со всеми его зависимостями, и мы сможем начать туториал.

Установка проекта (Brew, Carthage, XcodeGen, зависимости…)
Установка проекта (Brew, Carthage, XcodeGen, зависимости…)

2. Создание файла спецификаций проекта XcodeGen

В корневом каталоге проекта создайте папку с именем XcodeGen (имя папки необязательно должно быть таким) с файлом названным project.yml (это имя файла тоже опционально).

Этот файл будет служить спецификацией нашего .xcodeproj
Этот файл будет служить спецификацией нашего .xcodeproj

XcodeGen/project.yml: Первоначальная настройка:

name: GoodToGo
options:
  bundleIdPrefix: com.GoodToGo
  xcodeVersion: '12.0.1'
  deploymentTarget: '12.0'
  groupSortPosition: top
  generateEmptyDirectories: true
  findCarthageFrameworks: true
  minimumXcodeGenVersion: '2.18.0'

 Во время нашей первоначальной настройки для файла .xcodeproj мы выбираем имя GoodToGo — , префикс пакета — com.GoodToGo — , версию Xcode, целевую платформу развертывания…

3. Первый запуск XcodeGen

Используя описанную выше конфигурацию, мы можем создать наш новый проект, запустив xcodegen -s ./XcodeGen/project.yml -p ./ в терминале.

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

XcodeGen/project.yml: Первый запуск
XcodeGen/project.yml: Первый запуск

Зеленое сообщение говорит о том, что проект был создан. Ура, мы успешно создали наш проект!

Теперь давайте откроем его и посмотрим, как он выглядит:

Не то, что мы ожидали… Отсутствуют все наши схемы, все фреймворки, вообще все. Они отсутствуют, потому что мы не определили их в нашем файле project.yml.

4. Добавление конфигураций и основного таргета проекта

XcodeGen/project.yml: Добавление конфигураций. Просто добавьте это в свой файл project.yml:

configs:
  Debug.Dev: debug   
  Debug.Prod: debug
  Release: release

targets:
  GoodToGo:
    type: application
    platform: iOS
    deploymentTarget: 12.0
    settings:
      base:
        MARKETING_VERSION: 1.0
    sources:
       - path: ../GoodToGo

По сути, мы создаем 3 конфигурации — Debug.Dev, Debug.Prod и Release, и заявляем что основной таргет нашего приложения будет называться GoodToGo, с целевой платформой iOS 12 (минимальная версия), а файлы исходного кода находятся в папке GoodToGo.

Папка должна существовать в системных папках, иначе у вас будет следующая ошибка:

Spec validation error: Target “GoodToGo” has a missing source directory 
“/Users/ricardosantos/Desktop/GitHub/RJPS_Articles/7/sourcecode/THE_FOLDER_THAT_DOES_NOT_EXISTS_NAME”

Попробуйте снова с нашей обновленной конфигурацией (предыдущее изображение) и…

… теперь у нас есть (предыдущее изображение) схемы, конфигурации и таргет приложения!

5. Добавление фреймворков

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

Мы знаем с самого начала, что обычно все фреймворки имеют одни и те же настройки, поэтому мы создадим шаблон и назовем его Framework.

Ниже вы можете увидеть простой шаблон, на который мы будем ссылаться в нашем основном файле project.yml, а затем и там, где это необходимо.

XcodeGen/project.yml: Создание шаблона:

targetTemplates:
  Framework:
    type: framework
    platform: iOS
    deploymentTarget: 11.0
    settings:
      base:
        MARKETING_VERSION: 1.0

В этом шаблоне указано, что все целевые объекты будут иметь следующие условия:

  • тип framework,

  • платформа iOS,

  • версию 1.0,

  • целевую платформу для развертывания, равную или выше, iOS 11.0.

В нашем образце проекта у нас были следующие зависимости фреймворков (смотрите ниже).

Мы добавим парочку для проверки: AppTheme и AppResources.

Начиная с нашего файла project.yml в разделе таргетов мы добавим 2 новые записи (AppTheme и AppResources). Для этих таргетов мы должны определить исходную папку и имя шаблона, как объяснялось в предыдущем шаге.

 AppTheme:
    templates: 
      - Framework
    sources: 
      - path: ../AppTheme
  AppResources:
    templates: 
      - Framework
    sources: 
      - path: ../AppResources

XcodeGen/project.yml: Добавление таргетов с помощью шаблона

Попробуем еще раз с нашей обновленной конфигурацией (предыдущее изображение) и…

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

Небольшое резюме: раздел таргетов в нашем project.yml должен выглядеть так:

targets:
  GoodToGo:
    type: application
    platform: iOS
    deploymentTarget: 12.0
    settings:
      base:
        MARKETING_VERSION: 1.0
    sources:
       - path: ../GoodToGo
  AppTheme:
    templates: 
      - Framework
    sources: 
      - path: ../AppTheme
  AppResources:
    templates: 
      - Framework
    sources: 
      - path: ../AppResources

Для завершения настройки нам нужно добавить остальные фреймворки, после чего наш файл project.yml должен выглядеть следующим образом:

name: GoodToGo

## options section ##

options:
  bundleIdPrefix: com.GoodToGo
  xcodeVersion: '12.0.1'
  deploymentTarget: '12.0'
  groupSortPosition: top
  generateEmptyDirectories: true
  findCarthageFrameworks: true
  minimumXcodeGenVersion: '2.18.0'

## configs section ##

configs:
  Debug.Dev: debug
  Debug.Prod: debug
  Release: release

## targetTemplates section ##

targetTemplates:
  Framework:
    type: framework
    platform: iOS
    deploymentTarget: 11.0
    settings:
      base:
        MARKETING_VERSION: 1.0

## targets section ##
        
targets:
  GoodToGo:
    type: application
    platform: iOS
    deploymentTarget: 11.0
    settings:
      base:
        MARKETING_VERSION: 1.0
    sources:
       - path: ../GoodToGo
  AppTheme:
    templates: 
      - Framework
    sources: 
      - path: ../AppTheme
  AppResources:
    templates: 
      - Framework
    sources: 
      - path: ../AppResources
  AppConstants:
    templates: 
      - Framework
    sources: 
      - path: ../AppConstants
  Core:
    templates: 
      - Framework
    sources: 
      - path: ../Core
  Core.GalleryApp:
    templates: 
      - Framework
    sources: 
      - path: ../Core.GalleryApp
  Domain:
    templates: 
      - Framework
    sources: 
      - path: ../Domain
  Core.GalleryApp:
    templates: 
      - Framework
    sources: 
      - path: ../Core.GalleryApp
  Designables:
    templates: 
      - Framework
    sources: 
      - path: ../Designables    
  DevTools:
    templates: 
      - Framework
    sources: 
      - path: ../DevTools   
  Extensions:
    templates: 
      - Framework
    sources: 
      - path: ../Extensions   
  Factory:
    templates: 
      - Framework
    sources: 
      - path: ../Factory   
  PointFreeFunctions:
    templates: 
      - Framework
    sources: 
      - path: ../PointFreeFunctions   
  Repositories:
    templates: 
      - Framework
    sources: 
      - path: ../Repositories    
  Repositories.WebAPI:
    templates: 
      - Framework
    sources: 
      - path: ../Repositories.WebAPI         
  UIBase:
    templates: 
      - Framework
    sources: 
      - path: ../UIBase  

  Test.GoodToGo:
    type: bundle.unit-test
    platform: iOS
    sources:
       - path: ../Test.GoodToGo
    scheme: {}

XcodeGen/project.yml: Полная конфигурация (пока)

https://seattle.eater.com/2019/7/2/20679237/july-4th-seattle-where-to-eat-drink-and-watch-the-fireworks
https://seattle.eater.com/2019/7/2/20679237/july-4th-seattle-where-to-eat-drink-and-watch-the-fireworks

На этом этапе после запуска XcodeGen у нас должен получиться проект со всеми фреймворками!

Настраивая подобные вещи, мы всегда натыкаемся на некоторые неровности на дороге. Одна из них — пропавшая ссылка на файл .plist. Это можно исправить 2-мя способами: первый заключается в указании в project.yml пути к файлу .plist, а второй вариант заключается в переименовании GoodToGo-info.plist в Info.plist.

6. Подтягиваем зависимости фреймворков

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

Например, мы заметили, что Domain зависит от RxCocoa и RxSwift, а эти зависимости разрешаются с помощью carthage.

Во-первых, нам нужно найти таргет Domain в нашем файле project.yml
XcodeGen/project.yml: Таргет Domain без зависимостей:

  Domain:
    templates: 
      - Framework
    sources: 
      - path: ../Domain

… а затем добавить недостающие (carthage) зависимости. Вот так просто!
XcodeGen / project.yml: Таргет Domain с зависимостями:

Domain:
    templates: 
      - Framework
    sources: 
      - path: ../Domain
    dependencies:
      - carthage: RxSwift
      - carthage: RxCocoa

Для добавления зависимостей у нас есть несколько вариантов, но в нашем проекте мы используем всего 3:

  • Добавить carthage-зависимость,

  • Добавить зависимость проекта с привязкой (link: true),

  • Добавить зависимость проекта без привязки (link: false).

    XcodeGen/project.yml: Пример трех вариантов зависимостей (carthage, с привязкой и без):

 dependencies:
      - carthage: RxSwift
      - carthage: RxCocoa
      - target: BaseUI
        link: false
      - target: DevTools
        link: true
      - target: UICarTrack
        link: false

Вы можете узнать больше о добавлении зависимостей здесь.

Пришло время подтянуть все зависимости в нашем проекте, и в целом все готово! «В целом» — это просто способ сказать, что эта задача, вероятно, является наиболее трудоемкой, т.к. она состоит из цикла:

  • 1. Создание .xcodeproject и его открытие,

  • 2. Компиляция проекта и определение недостающих зависимостей,

  • 3. Добавление недостающих зависимостей в project.yml,

  • 4. Возвращение к шагу 1.

7. Дополнительно: скрипты сборки

В исходном проекте у нас было несколько скриптов сборки (как мы видим на изображении ниже):

Мы можем добиться того же результата, добавив чайлда postCompileScript.

GoodToGo:
    type: application
    platform: iOS
    deploymentTarget: 12.0
    settings:
      base:
        MARKETING_VERSION: 1.0
    sources:
       - path: ../GoodToGo
    dependencies:
       ...
    postCompileScripts:
      - script: |
                if which swiftlint >/dev/null; then
                   swiftlint
                else
                   echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
                fi
        name: Run SwiftLint

8. Дополнительно: папка Documents

Мы также можем добавить папку Documents. Файлы внутри этой папки не будут добавлены ни в какой таргет и, следовательно, не будут обрабатываться как часть сборки. Просто добавьте путь к папке с документами в раздел fileGroups (подробнее о fileGroups здесь).

fileGroups:
  - ../Documents

IV - Резюме

  • Мы создали базовый файл project.yml и использовали XcodeGen, чтобы он сгенерировал наш проект (подробнее здесь)

  • Мы добавили некоторые конфигурации проекта (подробнее здесь)

  • Мы добавили все необходимые нам таргеты

  • Мы подтянули зависимости наших таргетов (подробнее здесь)

  • Мы добавили папку с документами (подробнее здесь)

  • Мы добавили скрипты сборки

1. Проблемы, с которыми вы можете столкнуться при настройке проекта в первый раз: Потерянные файлы

Иногда такое может случится с каждым из нас. При удалении некоторых старых/устаревших файлов из проекта мы выбираем опцию Remove Reference вместо перемещения файла в корзину. Эти файлы появятся снова, когда мы будем использовать XcodeGen (помните, что все файлы внутри папки будут добавлены в таргет). Если вы действительно хотите сохранить эти файлы, поместите их в папку Documents.

2. Проблемы, с которыми вы можете столкнуться при настройке проекта в первый раз: Разные файлы внутри папок

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

V - Ссылки

  • Проект перед преобразованием можно найти здесь.

  • Код финального проекта можно найти здесь.

  •  Финальный project.yml можно найти здесь.

  • Больше примеров подобных проектов можно найти здесь.


Узнать подробнее о курсе "iOS Developer. Professional".

Смотреть запись вебинара
«Пишем приложение на SwiftUI и Combine».