Cocoapods: como trabalhar com dependências device-only

Essa semana tive que enfrentar um problema que eu já vinha postergando faz muito tempo.

Desde que a Apple lançou o seus primeiros MacBook com processadores M1 nós desenvolvedores tivemos que começar a pensar em frameworks para simulador com arquitetura arm64.

Isso foi praticamente a sentença de morte dos fat binaries uma vez que um binário não pode conter duas arquitetura iguais: uma arm64 para devices e uma arm64 pra simuladores.

Com isso começamos a trabalhar com Xcframeworks: um pacote contendo duas ou mais plataformas dentro de si.

Foi então que um novo problema surgiu: como linkar bibliotecas que são devices-only e contém arquitetura arm64 com simuladores usando cocoapods.

Antes da arquitetura arm64 pra simuladores, quando tentávamos linkar uma biblioteca device-only em um build para simulador, o Linker simplesmente ignorava a biblioteca, por ela não conter nenhuma arquitetura válida; gerava um warning e a vida seguia normalmente. Mas agora que a arquitetura arm64 passou a ser uma arquitetura válida para simulador, o linker, ao tentar linkar o build com a biblioteca device-only passou a exibir o seguinte erro:

#praCegoVer: a imagem mostra um erro do linker, com a seguinte mensagem: ” ld: building for iOS Simulator, but linking In dlyb built for iOS”, com o path para a lib, seguido de “for architecture arm64”.

Chegamos então a um problema: como informar ao cocoapods que a biblioteca que eu estou tentando importar deve ser utilizada apenas para Devices e não pra simuladores? Nós podemos especificar configurações para um Pod, mas não uma plataforma para uma configuração.

A solução: um podspec customizado

Criar um podspec customizado não é uma tarefa difícil, mas encontrar os parâmetros certos para a solução que você deseja pode ser um desafio.

Nesse caso, precisamos remover a biblioteca do vendored_frameworks:

#praCegoVer: a imagem mostra um arquivo podspec, com a propriedade vendored_frameworks selecionada, com o valor MyDeviceOnlyFramework.framework.

Quando é especificada como um vendored framework, o Cocoapods já inclui a biblioteca em todo lugar onde ela se faz necessária, desde frameworks search paths até o próprio Linker.

Nesse caso, iremos fazer o processo na mão, uma vez que queremos uma configuração bastante específica.

Para que o Cocoapods baixe e mantenha nossa biblioteca como dependência, precisamos especificar um path que será preservado durante a instalação do Pod (nesse caso, nosso framework):

    s.preserve_paths = "MyDeviceOnlyFramework.framework/*"

Em seguida, precisamos informar ao Cocoapods toda a configuração adicional que a biblioteca precisa para ser usada pelo nosso projeto principal. Para isso, incluímos a propriedade xcconfig, passando as informações necessárias para o arquivo de configuração que será gerado.

    s.xcconfig = {
        'FRAMEWORK_SEARCH_PATH[sdk=iphoneos*]' => '$(inherited) "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHERCFLAGS[sdk=iphoneos*]' => '$(inherited) -iframework "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHER_LDFLAGS[sdk=iphoneos*]' => '$(inherited) -framework MyDeviceOnlyFramework'
    }

Note que usamos a variante de iphoneos* para nossas configurações. É essa variante que vai nos permitir setar os valores apenas para devices, e não mais para simuladores.

Feito isso, é só subir o seu podspec customizado para seu repositório de specs ou armazená-lo em um local que o cocoapods tenha acesso, e dar um pod install no seu projeto (não esqueça do —repo-update para atualizar suas dependências, se necessário).

A solução final

Após o pod install, sua dependência exibirá suas configurações da seguinte forma no Xcode:

#praCegoVer: a imagem mostra o bloco de configuração Other Linker Flags, com a config debug e release, com a dependência MyDeviceOnlyFramework setada apenas para a plataforma Any iOS SDK.

No final, seu podspec será parecido com o abaixo:

Pod::Spec.new do |s|

    s.name          = "MyDeviceOnlyFramework"
    s.version       = "1.0.0"
    s.summary       = "This is a Device Only Framework"

    s.description   = <<-DESC
                        Device Only Framework Podspec Example.
                      DESC
    
    s.homepage      = "http://mywebpage.xpto/MyDeviceOnlyFramework"
    s.license       = { :type => "Copyright", :file => "LICENSE" }

    s.author        = { "Andre Salla" => "contato@andresalla.com" }
    s.platform      = :ios, "10.0"

    s.source        = { :git => "http://mygitrepo.xpto/MyDeviceOnlyFramework.git", :tag => "1.0.0"}

    s.preserve_paths = "MyDeviceOnlyFramework.framework/*"

    s.xcconfig = {
        'FRAMEWORK_SEARCH_PATH[sdk=iphoneos*]' => '$(inherited) "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHERCFLAGS[sdk=iphoneos*]' => '$(inherited) -iframework "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHER_LDFLAGS[sdk=iphoneos*]' => '$(inherited) -framework MyDeviceOnlyFramework'
    }

end

Agradecimento

Quero agradecer o August Jaenicke, que com o seu post conseguiu me tirar dessa situação complicada e me mostrou a luz no fim do túnel.

Link para o post: https://blog.carbonfive.com/cocoapods-for-device-only-ios-libraries/