Por que um sistema de compilação?

Informar um problema Ver código-fonte

Esta página discute o que são sistemas de compilação, o que eles fazem, por que usar um sistema de compilação e por que os compiladores e scripts de compilação não são a melhor opção à medida que sua organização começa a escalonar. Ela é destinada a desenvolvedores que não têm muita experiência com um sistema de compilação.

O que é um sistema de compilação?

Em resumo, todos os sistemas de compilação têm uma finalidade simples: eles transformam o código-fonte escrito por engenheiros em binários executáveis que podem ser lidos por máquinas. Os sistemas de compilação não são destinados apenas a códigos criados por humanos. Eles também permitem que as máquinas criem builds automaticamente, seja para testes ou para produção. Em uma organização com milhares de engenheiros, é comum que a maioria das versões seja acionada automaticamente, em vez de diretamente pelos engenheiros.

Não posso só usar um compilador?

A necessidade de um sistema de compilação pode não ser tão óbvia. A maioria dos engenheiros não usa um sistema de compilação enquanto aprende a programar: a maioria começa invocando ferramentas como gcc ou javac diretamente da linha de comando ou o equivalente em um ambiente de desenvolvimento integrado (IDE, na sigla em inglês). Desde que todo o código-fonte esteja no mesmo diretório, um comando como este funcionará bem:

javac *.java

Isso instrui o compilador Java a transformar todos os arquivos de origem Java no diretório atual em um arquivo de classe binária. No caso mais simples, você só precisa disso.

No entanto, assim que o código se expande, as complicações começam. javac é inteligente o suficiente para procurar nos subdiretórios do diretório atual e encontrar o código a ser importado. Mas não há maneira de encontrar o código armazenado em outras partes do sistema de arquivos (talvez uma biblioteca compartilhada por vários projetos). Ela também só sabe como criar código Java. Grandes sistemas geralmente envolvem diferentes partes escritas em uma variedade de linguagens de programação com redes de dependências entre elas. Isso significa que nenhum compilador para uma única linguagem pode criar todo o sistema.

Ao lidar com código de várias linguagens ou várias unidades de compilação, a criação de código não é mais um processo de uma única etapa. Agora você precisa avaliar do que o código depende e criar essas partes na ordem correta, possivelmente usando um conjunto diferente de ferramentas para cada uma. Se alguma dependência mudar, repita esse processo para evitar depender de binários desatualizados. Para uma base de código com tamanho até mesmo moderado, esse processo rapidamente se torna entediante e propenso a erros.

O compilador também não sabe nada sobre como lidar com dependências externas, como arquivos JAR de terceiros em Java. Sem um sistema de compilação, você poderia gerenciar isso fazendo o download da dependência da Internet, colocando-a em uma pasta lib no disco rígido e configurando o compilador para ler as bibliotecas desse diretório. Com o tempo, fica difícil manter as atualizações, versões e fontes dessas dependências externas.

E os scripts de shell?

Suponha que seu projeto de hobby seja simples o suficiente para ser compilado usando apenas um compilador, mas você comece a enfrentar alguns dos problemas descritos anteriormente. Talvez você ainda não ache que precisa de um sistema de compilação e pode automatizar as partes tediosas usando alguns scripts de shell simples que cuidam da criação das coisas na ordem correta. Isso ajuda por um tempo, mas logo você começará a ter ainda mais problemas:

  • Ela é entediante. À medida que o sistema fica mais complexo, você começa a passar mais tempo trabalhando nos scripts de compilação e em códigos reais. Depurar scripts de shell é complicado, com cada vez mais hacks sendo sobrepostos.

  • Ele é lento. Para garantir que você não dependa acidentalmente de bibliotecas desatualizadas, seu script de compilação precisa criar cada dependência em ordem sempre que você executá-la. Você pensa em adicionar lógica para detectar quais partes precisam ser recriadas, mas isso parece muito complexo e propenso a erros para um script. Ou pense em especificar quais peças precisam ser recriadas todas as vezes, mas depois você retorna à primeira imagem.

  • Boas novas: é hora de um lançamento! Melhor descobrir todos os argumentos que você precisa passar para o comando jar para criar seu build final. E lembre-se de como fazer o upload e o enviar para o repositório central. Crie e envie as atualizações da documentação e envie uma notificação aos usuários. Talvez isso precise de outro script...

  • Desastre! Seu disco rígido falha e agora você precisa recriar todo o sistema. Você tinha inteligência suficiente para manter todos os arquivos de origem no controle de versão, mas e as bibliotecas que foram transferidas por download? Você pode encontrá-los novamente e garantir que foram da mesma versão que foram transferidos pela primeira vez? Seus scripts provavelmente dependiam da instalação de ferramentas específicas em determinados locais. É possível restaurar o mesmo ambiente para que os scripts funcionem novamente? E todas as variáveis de ambiente que você definiu há muito tempo para fazer o compilador funcionar da forma certa e depois esquecer?

  • Apesar dos problemas, seu projeto é suficientemente bem-sucedido para que você comece a contratar mais engenheiros. Agora você sabe que não precisa dar desastre para os problemas anteriores surgirem. É necessário passar pelo mesmo processo de inicialização cada vez que um novo desenvolvedor entra na sua equipe. E, mesmo com seus melhores esforços, ainda há pequenas diferenças no sistema de cada pessoa. Frequentemente, o que funciona na máquina de uma pessoa não funciona na outra e sempre que leva algumas horas de caminhos de ferramentas de depuração ou versões de biblioteca para descobrir a diferença.

  • Você decide que precisa automatizar o sistema de compilação. Teoricamente, isso é tão simples quanto conseguir um novo computador e configurá-lo para executar seu script de compilação todas as noites usando o cron. Você ainda precisa passar pelo processo de configuração doloroso, mas agora não tem a vantagem de um cérebro humano ser detectável e resolver problemas menores. Todas as manhãs, quando você entra, vê que o build da noite passada falhou porque ontem um desenvolvedor fez uma mudança que funcionou no sistema dele, mas não funcionou no sistema de compilação automatizado. Cada vez que é uma correção simples, mas acontece com tanta frequência, você acaba gastando muito tempo todos os dias descobrindo e aplicando essas correções simples.

  • Os builds ficam mais lentos à medida que o projeto cresce. Um dia, enquanto aguarda a conclusão de uma versão, você olha engraçadamente a área de trabalho inativa do seu colega de trabalho, que está de férias, e quer que haja uma maneira de aproveitar todo o poder computacional desperdiçado.

Você encontrou um problema clássico de escalonamento. Para um único desenvolvedor que trabalha em algumas centenas de linhas de código por no máximo uma semana ou duas (que pode ter sido toda a experiência de um desenvolvedor júnior que acabou de se formar na universidade), só é necessário um compilador. Os scripts podem demorar um pouco. Mas, assim que você precisa coordenar vários desenvolvedores e as máquinas deles, um script de compilação perfeito não é suficiente, porque se torna muito difícil considerar as pequenas diferenças nessas máquinas. Nesse ponto, essa abordagem simples é dividida, e é hora de investir em um sistema de compilação real.