Dependências do Git e do projeto

Nicola Paolucci
Imagine as seguintes questões. Como você lida com as dependências do projeto com o git
? O projeto é composto por vários repositórios interdependentes. No momento, eles são gerenciados com svn:externals
. Qual é a melhor maneira de lidar com esse caso com o git
? Como você divide um repositório muito grande em componentes menores usando o git? Estes são alguns exemplos das perguntas mais frequentes que a gente recebeu na etapa europeia do tour Getting Git Right.
O tópico parece ser um grande problema para muitas equipes de software que adotam o git
; portanto, neste artigo, vou tentar esclarecer o problema.
É óbvio que, as dependências do projeto e a infraestrutura de build são duas áreas interligadas e, mesmo dentro da Atlassian, foi iniciada uma discussão sobre o "Futuro dos builds".
Ter repositórios separados em vez de ter um único pode tornar algumas coisas mais difíceis. Mas é um passo natural em comparação e — às vezes obrigatório — na evolução de um projeto de software por pelo menos dois motivos principais: aumento dos tempos de compilação e compartilhamento de dependências entre projetos.
Pintura com traços amplos: diretrizes e soluções abaixo do ideal
Então, voltando à pergunta: Como você rastreia e gerencia as dependências do projeto com o git
?
Se for possível, não faça!
Brincadeiras à parte, deixe-me responder com traços amplos primeiro e ir mais fundo depois. Perceba que não há nenhuma bala de prata — no git
ou não — que vai resolver sem problemas todos os problemas relacionados às dependências do projeto.
Depois que um projeto cresce em um determinado tamanho, faz sentido que ele seja dividido em componentes lógicos, mas não espere ter mais de 100 milhões de linhas de código em um único repositório antes de fazer essa divisão. Portanto, a seguir estão apenas diretrizes para que você possa criar sua própria abordagem.
Primeira opção: use uma ferramenta de compilação/dependência apropriada em vez do git
Uma ferramenta de gerenciamento de dependências é o meu caminho atual recomendado para lidar com as dores de crescimento e os tempos de compilação de projetos consideráveis.
Mantenha seus módulos separados em repositórios individuais e gerencie a interdependência usando uma ferramenta criada para o trabalho. Existe um para (quase) todas as pilhas de tecnologia existentes. Alguns exemplos:
- Maven (ou Gradle) se você usar Java
- npm para aplicativos de ponto central
- Bower, Component.io, etc. se você usa Javascript (atualizado!)
- Pip e requirements.txt se você usa Python
- RubyGems,Bundler se você usa Ruby
- NuGet para.NET
- Ivy (ou alguma ação personalizada do CMake) para C++ (atualizado!)
- Aplicativos CocoaPods para Cocoa iOS
- Composer ou Phing para PHP (adicionado!)
- Em Go, a infraestrutura de build/dependência é um pouco incorporada à linguagem (embora as pessoas estejam trabalhando em uma solução mais completa, veja godep). Para o servidor Git Bitbucket Server, usamos o Maven e o Bower. No momento da compilação, a ferramenta escolhida vai extrair as versões corretas das dependências para que seu projeto principal possa ser compilado. Algumas dessas ferramentas têm limitações e fazem suposições que não são ideais, mas são comprovadas e viáveis.
As dificuldades de dividir o projeto
No início de um projeto, a estrutura é simples, tudo é embalado em um único build. Mas, à medida que o projeto cresce, essa simplicidade pode resultar em seu build muito lento — nesse ponto você precisa de armazenamento em "cache," que é onde o gerenciamento de dependências entra em ação. Aliás, essa informação indica que os submódulos (veja abaixo) se prestam muito bem a linguagens dinâmicas, por exemplo. Em resumo, acho que a maioria das pessoas precisa se preocupar com os tempos de compilação em algum momento, e é por esse motivo que você deve usar uma ferramenta de gerenciamento de dependências.
Dividir componentes em repositórios separados traz muita dor. Em nenhuma ordem específica:
- Fazer uma alteração em um componente requer uma versão
- Leva tempo e pode falhar por vários motivos idiotas
- Parece idiota para pequenas mudanças
- Exige a configuração manual de novos builds para cada componente
- Dificulta a capacidade de descoberta dos repositórios
- Refatoramento quando nem toda a fonte está disponível em um único repositório
- Em algumas configurações (como essa), a atualização de APIs requer um lançamento marco do produto e, em seguida, o plug-in e, em seguida, o produto de novo. É provável que tenhamos esquecido alguma coisa, mas você entendeu. Uma solução perfeita para o problema está longe daqui.
Segunda opção: Usar o submódulo git
Se você não pode ou não quer usar uma ferramenta de dependência, o git
tem uma facilidade para lidar com submódulos
. Os submódulos podem ser convenientes, em particular para linguagens dinâmicas. No entanto, eles não vão sempre salvá-lo de tempos de compilação lentos. Já escrevi algumas diretrizes e dicas sobre elas e também explorei alternativas. A Internet em geral também tem argumentos contra eles.
Correspondência 1:1 entre svn:external e git
MAS! Se você está procurando uma correspondência 1-para-1 entre svn:externals
e git,
você precisa usar submódulos
garantindo que os submódulos
rastreiem apenas branches de lançamento e não commits aleatórios.
Terceira opção: usar outras ferramentas de dependência de build e pilha cruzada
Nem sempre você vai desfrutar de um projeto uniforme no total e que pode ser construído e montado com uma única ferramenta. Por exemplo, alguns projetos móveis vão precisar manipular as dependências Java e C++ ou usar ferramentas proprietárias para gerar ativos. Para essas situações mais complexas, você pode aprimorar o git
com uma camada extra no topo. Um ótimo exemplo nessa área é o repositório do Android.
Outras ferramentas de build que valem a pena explorar:
Conclusões e leituras adicionais
Leituras adicionais sobre o tópico de infraestrutura de build (e Maven) foram sugeridas com cuidado por Charles O'Farrell, um combustível muito interessante para reflexão:
- Em busca da melhor ferramenta de build
- E o acompanhamento do Maven é defeituoso por padrão
- Maven recursivo considerado prejudicial
Gostaria de concluir com esta excelente citação do último artigo acima. Mesmo que seja sobre o Maven, ele também pode ser aplicado a outras ferramentas de build e dependência:
Um cache não faz nada além de acelerar as coisas. Você poderia remover um cache por completo e o sistema circundante funcionaria da mesma forma, apenas com mais lentidão. Um cache também não tem efeitos colaterais. Não importa o que você tenha feito com um cache no passado, uma determinada consulta ao cache vai devolver o mesmo valor para a mesma consulta no futuro.
A experiência do Maven é muito diferente do que eu descrevo! Os repositórios Maven são usados como caches, mas sem ter as propriedades dos caches. Quando você pede algo de um repositório Maven, é muito importante o que você fez no passado. Ele retorna a coisa mais recente que você colocou nele. Pode até falhar, se você pedir algo antes de colocar.
Agradeço a Charles O'Farrell pelo feedback atencioso sobre o rascunho deste texto. Espero que você tenha gostado das reflexões acima e entre em contato em@durdn e @AtlDevtools para mais travessuras do Git.