Evitando conflitos no Xcode
Tags: ios, xcode, git, tuist
Published: 2021-10-15 00:32:13

## Introdução

O objetivo desse texto é mostrar como resolvi alguns problemas no meu time utilizando o [Tuist](https://tuist.io) para gerar projetos e workspaces do Xcode e que com isso nós evitamos conflitos na hora do merge, resolvemos problemas para gerenciar dependências e também podemos padronizar configurações dos projetos e abrir individualmente o projeto que vai ser alterado (modo foco).

## Instalação
A instalação é muito simples, basta executar o comando no terminal:
`curl -Ls https://install.tuist.io | bash`

## Criando um projeto novo
Se for um novo projeto a utilização do Tuist é super simples.
Basta criar uma pasta e executar o `tuist init` 
```
mkdir MyApp
cd MyApp
tuist init --platform ios
```
O comando init irá gerar os arquivos base, `Info.plist`, `AppDelegate.swift`, arquivos de teste e o `Project.swift` que contém as configurações do projeto.

## Migrando um projeto existente
A migração no meu caso foi tranquila porque o projeto é simples e tenho um Workspace com vários projetos que dependem do Core e uma única dependência externa em um Target de teste.

Comecei migrando o Core, porque precisava definir a dependência dele nos outros projetos, resolvi todas as outras dependências e por último configurei o Workspace.
Na pasta de cada projeto criei o arquivo `Project.swift`, no diretório raíz o `Workspace.swift`e a pasta `Tuist` com o arquivo `Dependencies.swift`

## Configurações do projeto
O [Project.swift](https://docs.tuist.io/manifests/project) contém a configuração do projeto. É nesse arquivo onde são definidos nome, organização, targets, schemas, entre outras coisas.

Comecei fazendo o import do framework `ProjectDescription` esse arquivo deve inicializar uma variável do tipo `Project` que pode ter qualquer nome, mas a recomendação da documentação é nomear como `project` mesmo.

Um project precisa obrigatoriamente dos parâmetros: `name` e `targets` que é um array do tipo Target.

No Target é especificado o nome, plataforma, tipo do produto, bundle e os arquivos de código.

```swift
let target = Target(name: "App",
                    platform: .iOS,
                    product: .app,
                    bundleId: "com.rafa.example",
                    infoPlist: "Info.plist",
                    sources: "Sources/**",
                    dependencies: [])
```

```swift
let project = Project(
    name: "MyProject",
    targets: [target]
)
```

## Dependências externas
> A integração com dependências externas ainda está em alpha, como é avisado na [documentação](https://docs.tuist.io/guides/third-party-dependencies/)

> No momento que estou escrevendo esse texto a integração com Cocoapods ainda não é suportada. 

Cada projeto pode ter suas dependências configuradas individualmente, basta criar a pasta `Tuist` com o arquivo `Dependencies.swift` dentro dela.
No meu caso, fiz isso na pasta raiz do projeto porque tenho uma única dependência externa.

```swift
import ProjectDescription

let dependencies = Dependencies(
    carthage: [
        .github(path: "Alamofire/Alamofire", requirement: .exact("5.0.4")),
    ],
    swiftPackageManager: [
        .remote(url: "https://github.com/Alamofire/Alamofire", requirement: .upToNextMajor(from: "5.0.0")),
    ],
    platforms: [.iOS]
)
```

Feito isso baixe as dependências rodando
`tuist dependencies fetch`. Uma pasta chamada Dependencies será criada e o código será baixado nela.

De volta ao `Project.swift` é necessário registrar no Target a dependência baixada.

```swift
let target = Target(name: "App",
                    platform: .iOS,
                    product: .app,
                    bundleId: "com.rafa.example",
                    infoPlist: "Info.plist",
                    sources: "Sources/**",
                    dependencies: [
                .external(name: "Alamofire"),
            ])
```
 
## Configurações do Workspace
Por padrão o `tuist generate` cria um Workspace com o mesmo nome do projeto e já inclui as dependências.
No meu caso os projetos estão num monorepo, então preciso configurar um Workspace e adicionar todos os projetos nele.

```swift
import ProjectDescription

let workspace = Workspace(
    name: "ProjectWorkspace",
    projects: [
        "Project1",
        "Project2",
        "Project3"
    ],
    schemes: []
)
```

Por padrão os [schemas](https://docs.tuist.io/manifests/project#scheme) são gerados automaticamente, mas podem ser personalizados para configurar as ações de cada um.

## Gerando os arquivos
A configuração é essa, dependendo do tamanho do projeto pode ser um pouco cansativa mas os ganhos são compensatórios. 

Depois de tudo configurado, é só rodar o `tuist dependencies fetch` para baixar as dependências e depois `tuist generate` para gerar os projetos/workspaces.

Testei outras ferramentas mas resolvi adotar o Tuist por utilizar Swift e não Yaml, possibilidade de fazer cache dos módulos, cache dos testes, screenshot de dependências e por considerar a adoção fácil no projeto em que trabalho.
Finalizei implementando o [xcdiff](https://github.com/bloomberg/xcdiff) evitando que o `.xcodeproj` fosse alterado manualmente.

E é isso! :)

### Links úteis
[Tuist.io](https://tuist.io/)
[Tuist docs](https://docs.tuist.io/)
[xcdiff](https://github.com/bloomberg/xcdiff)