Experiência CICD móvel: um padrão fastlane para muitas aplicações móveis

Experiência CICD móvel: um padrão fastlane para muitas aplicações móveis
Gostaria de falar sobre integração e entrega contínua para aplicativos móveis usando fastlane. Como implementamos CI/CD em todas as aplicações móveis, como chegamos lá e o que aconteceu no final.

Já existe material suficiente na rede sobre a ferramenta, que tanto faltou no início, então deliberadamente não descreverei a ferramenta em detalhes, mas apenas me referirei ao que tínhamos então:

O artigo consiste em duas partes:

  • Antecedentes do surgimento do CI/CD móvel na empresa
  • Solução técnica para implementação de CI/CD para N aplicações

A primeira parte é mais uma nostalgia dos velhos tempos, e a segunda é uma experiência que você pode aplicar em si mesmo.

Foi assim que aconteceu historicamente

Ano 2015

Acabamos de começar a desenvolver aplicativos móveis, mas não sabíamos nada sobre integração contínua, sobre DevOps e outras coisas da moda. Cada atualização do aplicativo foi lançada pelo próprio desenvolvedor em sua máquina. E se para Android é bem simples - montado, assinado .apk e carreguei-o no Google Developer Console, depois para iOS a ferramenta de distribuição via Xcode nos deixou ótimas noites - as tentativas de baixar o arquivo muitas vezes terminavam em erros e tínhamos que tentar novamente. Descobriu-se que o desenvolvedor mais avançado não escreve código várias vezes por mês, mas sim lança o aplicativo.

Ano 2016

Crescemos, já pensávamos em como liberar desenvolvedores de um dia inteiro para um lançamento, e também apareceu um segundo aplicativo, o que só nos empurrou mais para a automação. Nesse mesmo ano, instalamos o Jenkins pela primeira vez e escrevemos um monte de scripts assustadores, muito parecidos com aqueles que o fastlane mostra em sua documentação.

$ xcodebuild clean archive -archivePath build/MyApp 
    -scheme MyApp

$ xcodebuild -exportArchive 
                        -exportFormat ipa 
                        -archivePath "build/MyApp.xcarchive" 
                        -exportPath "build/MyApp.ipa" 
                        -exportProvisioningProfile "ProvisioningProfileName"

$ cd /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/

$ ./altool —upload-app 
-f {abs path to your project}/build/{release scheme}.ipa  
-u "[email protected]" 
-p "PASS_APPLE_ID"

Infelizmente, até agora apenas nossos desenvolvedores sabiam como esses scripts funcionam e por que essa pilha interminável de chaves é necessária, e quando algo quebrava novamente, eles tinham “noites lindas” para analisar logs.

Ano 2017

Este ano aprendemos que existe fastlane. Não havia tanta informação como há agora – como iniciar um, como usá-lo. E a ferramenta em si ainda era rudimentar naquela época: erros constantes só nos decepcionavam e era difícil acreditar na automação mágica que prometiam.

No entanto, os principais utilitários incluídos no núcleo fastlane são gym и pilot, conseguimos iniciá-lo.

Nossos scripts foram um pouco melhorados.

$ fastlane gym  —-workspace "Example.xcworkspace" 
                --scheme "AppName" 
                —-buildlog_path "/tmp" 
                -—clean

Eles foram melhorados, até porque nem todos os parâmetros necessários para xcodebuild, você precisa indicar - gym compreenderá de forma independente onde e o que está. E para um ajuste mais fino, você pode especificar as mesmas teclas que em xcodebuild, apenas a nomenclatura das chaves é mais clara.

Desta vez, graças ao ginásio e ao formatador xcpretty integrado, os logs de construção tornaram-se muito mais legíveis. Isso começou a economizar tempo no conserto de montagens quebradas e, às vezes, a equipe de lançamento conseguia resolver o problema por conta própria.

Infelizmente, as medições de velocidade de montagem xcodebuild и gym Não fizemos isso, mas confiaremos na documentação - até 30% de aceleração.

Processo único para todas as aplicações

Ano 2018 e presente

Em 2018, o processo de construção e implementação de aplicativos foi completamente transferido para Jenkins, os desenvolvedores pararam de lançar em suas máquinas e apenas a equipe de lançamento tinha o direito de lançar.

Já queríamos melhorar o lançamento de testes e análises estáticas, e nossos scripts foram crescendo cada vez mais. Cresceu e mudou junto com nossas aplicações. Naquela época, havia cerca de 10 aplicativos. Considerando que temos duas plataformas, são cerca de 20 scripts “vivos”.

Cada vez que queríamos adicionar uma nova etapa ao script, tínhamos que copiar e colar as peças em todos os scripts de shell. Talvez pudéssemos ter trabalhado com mais cuidado, mas muitas vezes essas mudanças terminavam em erros de digitação, que se transformavam em noites para a equipe de lançamento corrigir scripts e descobrir qual cara esperto adicionou esse comando e o que ele realmente faz. Em geral, não se pode dizer que os scripts de montagem para uma plataforma fossem pelo menos um pouco semelhantes. Embora eles certamente tenham feito a mesma coisa.

Para iniciar o processo de uma nova aplicação, era necessário passar um dia selecionando uma versão “fresca” desses scripts, depurá-la e dizer que “sim, funciona”.

No verão de 2018, olhámos mais uma vez para a via rápida ainda em desenvolvimento.

Tarefa nº 1: resumir todas as etapas do script e reescrevê-las no Fastfile

Quando começamos, nossos scripts pareciam uma palmilha composta por todas as etapas e muletas em um script de shell no Jenkins. Ainda não mudamos para pipeline e divisão por etapas.

Analisamos o que temos e identificamos 4 etapas que se enquadram na descrição do nosso CI/CD:

  • build - instalação de dependências, montagem do arquivo,
  • test — executando testes de unidade de desenvolvedor, calculando cobertura,
  • sonar - lança todos os linters e envia relatórios para o SonarQube,
  • implantar — enviando um artefato para alfa (TestFlight).

E se você não entrar em detalhes, omitindo as chaves usadas nas ações, você obterá este Fastfile:

default_platform(:ios)

platform :ios do
  before_all do
    unlock
  end

  desc "Build stage"
  lane :build do
    match
    prepare_build
    gym
  end

  desc "Prepare build stage: carthage and cocoapods"
  lane :prepare_build do
    pathCartfile = ""
    Dir.chdir("..") do
      pathCartfile = File.join(Dir.pwd, "/Cartfile")
    end
    if File.exist?(pathCartfile)
      carthage
    end
    pathPodfile = ""
    Dir.chdir("..") do
      pathPodfile = File.join(Dir.pwd, "/Podfile")
    end
    if File.exist?(pathPodfile)
      cocoapods
    end
  end

  desc "Test stage"
  lane :test do
    scan
    xcov
  end

  desc "Sonar stage (after run test!)"
  lane :run_sonar do
    slather
    lizard
    swiftlint
    sonar
  end

  desc "Deploy to testflight stage"
  lane :deploy do
    pilot
  end

  desc "Unlock keychain"
  private_lane :unlock do
    pass = ENV['KEYCHAIN_PASSWORD']
    unlock_keychain(
      password: pass
    )
  end
end

Na verdade, nosso primeiro Fastfile acabou sendo monstruoso, considerando algumas das muletas que ainda precisávamos e a quantidade de parâmetros que substituímos:

lane :build do
carthage(
  command: "update",
  use_binaries: false,
  platform: "ios",
  cache_builds: true)
cocoapods(
  clean: true,
    podfile: "./Podfile",
    use_bundle_exec: false)

gym(
  workspace: "MyApp.xcworkspace",
  configuration: "Release",
  scheme: "MyApp",
  clean: true,
  output_directory: "/build",
  output_name: "my-app.ipa")
end 

lane :deploy do
 pilot(
  username: "[email protected]",
  app_identifier: "com.example.app",
  dev_portal_team_id: "TEAM_ID_NUMBER_DEV",
  team_id: "ITS_TEAM_ID")
end

No exemplo acima, apenas parte dos parâmetros que precisamos especificar: estes são os parâmetros de construção - esquema, configuração, nomes de perfil de provisionamento, bem como parâmetros de distribuição - ID Apple da conta do desenvolvedor, senha, ID do aplicativo e assim sobre. Como primeira aproximação, colocamos todas essas chaves em arquivos especiais - Gymfile, Matchfile и Appfile.

Agora no Jenkins você pode chamar comandos curtos que não desfocam a visão e são facilmente legíveis a olho nu:

# fastlane ios <lane_name>

$ fastlane ios build
$ fastlane ios test
$ fastlane ios run_sonar
$ fastlane ios deploy

Viva, estamos ótimos

O que você conseguiu? Comandos claros para cada etapa. Scripts limpos, organizados em arquivos fastlane. Alegres, corremos até os desenvolvedores pedindo-lhes que adicionassem tudo o que precisassem aos seus repositórios.

Mas percebemos com o tempo que encontraríamos as mesmas dificuldades - ainda teríamos 20 scripts de montagem que de uma forma ou de outra começariam a viver suas próprias vidas, seria mais difícil editá-los, pois os scripts seriam transferidos para repositórios, e não tivemos acesso lá. E, em geral, não será possível resolver a nossa dor desta forma.

Experiência CICD móvel: um padrão fastlane para muitas aplicações móveis

Tarefa nº 2: obtenha um único Fastfile para N aplicativos

Agora parece que resolver o problema não é tão difícil - defina as variáveis ​​e vamos lá. Sim, na verdade, foi assim que o problema foi resolvido. Mas no momento em que estragamos tudo, não tínhamos experiência no fastlane em si, nem no Ruby, no qual o fastlane está escrito, nem exemplos úteis na rede - todos que escreveram sobre o fastlane estavam limitados a um exemplo para um aplicativo para um desenvolvedor.

Fastlane pode lidar com variáveis ​​de ambiente, e já tentamos isso definindo a senha do Keychain:

ENV['KEYCHAIN_PASSWORD']

Depois de analisar nossos scripts, identificamos as partes comuns:

#for build, test and deploy
APPLICATION_SCHEME_NAME=appScheme
APPLICATION_PROJECT_NAME=app.xcodeproj
APPLICATION_WORKSPACE_NAME=app.xcworkspace
APPLICATION_NAME=appName

OUTPUT_IPA_NAME=appName.ipa

#app info
APP_BUNDLE_IDENTIFIER=com.example.appName
[email protected]
TEAM_ID=ABCD1234
FASTLANE_ITC_TEAM_ID=123456789

Agora, para começar a usar essas chaves em arquivos fastlane, tivemos que descobrir como entregá-las lá. Fastlane tem uma solução para isso: carregando variáveis ​​via dotenv. A documentação diz que se for importante carregar chaves para finalidades diferentes, crie vários arquivos de configuração no diretório fastlane .env, .env.default, .env.development.

E então decidimos usar essa biblioteca de uma forma um pouco diferente. Vamos colocar no repositório dos desenvolvedores não os scripts fastlane e suas meta informações, mas as chaves exclusivas deste aplicativo no arquivo .env.appName.

Eles Mesmos Fastfile, Appfile, Matchfile и Gymfile, nós o escondemos em um repositório separado. Um arquivo adicional com chaves de senha de outros serviços estava oculto lá - .env.
Você pode ver um exemplo aqui.

Experiência CICD móvel: um padrão fastlane para muitas aplicações móveis

No CI, a chamada não mudou muito; foi adicionada uma chave de configuração para uma aplicação específica:

# fastlane ios <lane_name> --env appName

$ fastlane ios build --env appName
$ fastlane ios test --env appName
$ fastlane ios run_sonar --env appName
$ fastlane ios deploy --env appName

Antes de executar os comandos, carregamos nosso repositório com scripts. Não parece tão legal:

git clone [email protected]/FastlaneCICD.git fastlane_temp

cp ./fastlane_temp/fastlane/* ./fastlane/
cp ./fastlane_temp/fastlane/.env fastlane/.env

Deixei esta solução por enquanto, embora Fastlane tenha uma solução para baixar Fastfile via açao import_from_git, mas funciona apenas para Fastfile, mas não para outros arquivos. Se você quiser “realmente bonito”, você pode escrever o seu próprio action.

Um conjunto semelhante foi feito para aplicativos Android e ReactNative, os arquivos estão no mesmo repositório, mas em ramos diferentes iOS, android и react_native.

Quando a equipe de lançamento deseja adicionar alguma nova etapa, as alterações no script são registradas via MR no git, não há mais necessidade de procurar os culpados dos scripts quebrados e, em geral, agora você precisa tentar quebrá-los.

Agora é isso com certeza

Anteriormente, gastávamos tempo mantendo todos os scripts, atualizando-os e corrigindo todas as consequências das atualizações. Foi muito decepcionante quando os motivos dos erros e do tempo de inatividade nos lançamentos foram simples erros de digitação que eram tão difíceis de controlar na confusão de scripts de shell. Agora, esses erros são reduzidos ao mínimo. As alterações são implementadas em todos os aplicativos de uma só vez. E leva 15 minutos para colocar um novo aplicativo no processo – configurar um pipeline de modelo no CI e adicionar as chaves ao repositório do desenvolvedor.

Parece que a questão do Fastfile para Android e da assinatura do aplicativo permanece inexplicável, se o artigo for interessante, escreverei uma continuação. Ficarei feliz em ver suas dúvidas ou sugestões “como você resolveria esse problema” nos comentários ou no Telegram Bashkirova.

Fonte: habr.com

Adicionar um comentário