Eu gosto de Makefiles
(switowski.com)- Mesmo com o surgimento constante de novas ferramentas de build, o Makefile continua útil como uma ferramenta básica e leve para reunir tarefas repetitivas de um projeto
- Graças a comandos convencionais como
make,make build,make installemake dev, é possível tentar rapidamente compilar, instalar e executar o servidor de desenvolvimento mesmo em um projeto desconhecido - Seja com Jekyll, Hugo, 11ty, frameworks web em Python, Docker ou gulp, o Makefile funciona como uma interface de execução comum independentemente da stack
- Em projetos pessoais, muitas vezes bastam alguns alvos simples como
dev,buildedeploy, em vez de condicionais ou flags - O GNU Make é amplamente difundido e, mesmo quando não está disponível, é possível executar diretamente os comandos de shell do Makefile, o que reduz a carga em ambientes restritos
Reunindo comandos diferentes de cada projeto em um único ponto de entrada
- O Makefile é usado para envolver projetos com formas diferentes de compilar, executar e fazer deploy em um conjunto familiar de comandos
- Quando um projeto desconhecido tem um
Makefile, em geral dá para começar tentando os seguintes comandosmakemake buildmake install
- Mesmo ao reabrir um projeto pessoal antigo, é possível configurar tudo para que apenas
make devexecute de uma vez as etapas de build necessárias e o servidor de desenvolvimento - Mesmo que a stack tecnológica varie, como em Jekyll, Hugo, 11ty ou vários frameworks web em Python, é possível manter o mesmo ponto de entrada
- Mesmo ao usar Docker ou gulp, o Makefile não substitui essas ferramentas, mas atua como uma camada de orquestração
- Exemplo:
make buildcompila as imagens Docker necessárias passando argumentos adicionais específicos do projeto
- Exemplo:
Uma forma simples de usar em projetos pessoais
- O uso de Makefiles se concentra mais em agrupar comandos de shell do que em recursos complexos
- Não usa recursos como condicionais ou flags
- Tecnicamente, a unidade de trabalho do make é o target, mas na prática ele é tratado como uma tarefa
- Isso também poderia ser feito com funções em scripts bash, mas o Makefile é visto como mais fácil e mais rápido
- Em projetos pessoais, normalmente há 2 ou 3 tarefas usadas com frequência
dev: iniciar o servidor de desenvolvimentobuild: compilar o projeto, quando necessáriodeploy: fazer deploy ou publicar o projetowatch: tarefa adicional usada para rerodar automaticamente o build quando arquivos-fonte mudam
- O Makefile do blog atual tem apenas um alvo,
dev- Em
dev:, executanpm run dev
- Em
- Em projetos pessoais mais complexos, as seguintes tarefas são reunidas no Makefile
dev: executar o servidor de desenvolvimento combundle exec jekyll servebuild: compilar os assets comnpm run gulp buildwatch: monitorar uma pasta específica comnpm run gulp watchdeploy: após compilar o Jekyll em ambiente de produção, executarmake encryptenetlify deploy --prodencrypt: criptografar os arquivos HTML da pasta_sitecomnpx staticrypt
Limitações e vantagens que vale conhecer
- Nos exemplos, os phony targets foram omitidos, mas se existirem arquivos com nomes como
dev,build,watch,deployouencrypt, o Makefile pode não funcionar como esperado- Leitores do Hacker News recomendam adicionar phony targets nesses casos
- GNU Make é o software que executa makefiles; é bem provável que já esteja instalado no Linux e é tão comum em MacBooks que nem se lembra de precisar instalar separadamente
- O Make não exige muitas dependências adicionais, então pode ser útil em ambientes restritos onde instalar pacotes é difícil ou impossível por motivos de segurança
- Mesmo sem Make, é possível copiar e executar diretamente no shell os comandos contidos no Makefile
- Em contraste, se o gulp não estiver no servidor, é difícil simplesmente colar código JavaScript no terminal e executá-lo
- Mesmo que você descubra ferramentas de build novas, melhores e mais rápidas, o Make continua útil como uma camada comum de comandos para gerenciar ferramentas e configurações diferentes
2 comentários
Se o
makefilenão define dependências, substituí-lo por umjustfileoferece uma usabilidade melhor.Opiniões no Hacker News
Não há motivo para ficar desanimado com quem diz nesta thread que você “está usando make errado”. Um dos motivos pelos quais Make é uma boa ferramenta é que ela permite começar de forma mais simples do que parece
Não usar
.PHONYpode causar problemas, mas, em um projeto pequeno, talvez seja uma armadilha em que você só caia daqui a um ano e, mesmo então, é bem provável que baste coçar a cabeça por uma horinha para resolver. Em 99% dos casos, você pode simplesmente começar sem se preocupar com o “jeito certo”, e o Make só exige complexidade suficiente para não desmoronarO Make recebe arquivos de entrada e dependências e cria exatamente um arquivo de saída. Regras que não geram saída, como
install,alleclean, ou todos os alvos do texto, precisam depender de um alvo mágico especial como.PHONY, mas.PHONYnão existia no POSIX de 2017 (IEEE Std 1003.1-2017, https://pubs.opengroup.org/onlinepubs/9699919799/utilities/m...) e só entrou no padrão atual (IEEE Std 1003.1-2024, https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...). Se você quiser gerar vários arquivos, como arquivos objeto, módulos e headers pré-compilados, precisa criar por conta própria gambiarras frágeis. Nem todo Make é GNU Make, e variantes nix como BSD e Solaris/Illumos ainda existemIsso não quer dizer que o Make seja inútil. Ele é útil para projetos que já são complexos o bastante, mas ainda não a ponto de precisarem de um sistema de build “melhor”. O problema é que esses projetos vão ficando mais complexos conforme o código cresce e, no fim, acabam tendo scripts/programas que geram o Makefile ou partes dele, criando um meta-sistema de build improvisado
O problema maior não é usá-lo, mas propô-lo como solução para “todo mundo”. E, no momento em que aparece no diretório raiz do projeto um diretório ou arquivo chamado
buildoudev, aquele Makefile para de funcionarHoje em dia eu até mudo para pnpm, mas ainda assim não considero tão complexo. É preciso conhecer um pouco da sintaxe, mas é menos difícil do que fazer outras ferramentas funcionarem direito desde o início
.PHONYcom que as pessoas tanto se preocupamÉ simples, fácil de ler, modificável, combinável e já vem instalado em todo lugar. Faz o que está escrito na superfície e quase nada além disso
Aliás, quando preciso lidar em outros projetos com sistemas de build da moda ou sistemas de build de pesadelo, eu os envolvo com make
Todo mundo precisa começar em algum lugar e, uma vez que algo começa a rodar, melhorias incrementais podem ser adicionadas facilmente
$@. O Makefile inteiro pode produzir um número arbitrário de artefatos de saída, já que regras podem depender de outras regrasDaí vem uma boa regra prática para alvos phony. Se uma receita não mexe apenas em
$@, então$@deve ser marcado como phony. Gerenciar alvos phony em uma lista torna tudo muito mais fácilphonies :=phonies += somethingsomething:./do-somethingphonies += something-elsesomething-else: something./do-something-else# touches $@ and thus does not need to be phonycreate-file:./generate-some-output > $@.PHONY: $(phonies)Makefile é uma tecnologia péssima. Só que é um pouco menos ruim do que a maioria dos outros sistemas de build que criamos, então se torna útil num sentido masoquista
Sistemas de build geralmente são básicos demais, virando uma confusão quando se tenta construir algo além de um brinquedo; ou complexos demais, exigindo conhecimento prévio, burocracia, sincronização e boilerplate em excesso; ou não têm uma biblioteca-padrão, fazendo os mesmos padrões de build se espalharem em 10 mil implementações incompatíveis; ou são restritivos demais, obrigando uma migração quando os requisitos crescem; ou têm magia demais; ou a sintaxe é obscura e inconsistente
Mais da metade do software que uso só usa Makefiles comuns e, às vezes, um script configure. Sem autotools, basta executar
./configureopcionalmente e depoismake,make install, e simplesmente funciona. Isso não é de modo algum uma configuração de brinquedo; é composto por pequenos programas que fazem bem uma coisa sóMake e a forma como ele funciona são fáceis de entender, pelo menos para mim. Acho que as experiências ruins das pessoas muitas vezes vêm mais de configurações de build ruins em projetos complexos, ou da falta do conhecimento necessário, do que do Make em si
A ausência de uma biblioteca-padrão é uma intenção de independência de ferramentas, e por isso ele se aplica de forma universal a várias ferramentas, linguagens e usos. Vejo isso como recurso, não como defeito. A camada simples é rastreamento de dependências, e os alvos e dependências também são claros, então é difícil dizer que seja mágico. A sintaxe certamente poderia melhorar
Os únicos sistemas de build gerais que abrangem várias linguagens são sistemas que miram o
make, como omakeou o CMake. O motivo de isso não ser ótimo é que omakenão é ótimo.makeé realmente difícil de usar corretamente, tem problemas como make recursivo e não recursivo, muitas variantes incompatíveis como Unix/POSIX make, BSD make, GNU make e Windowsnmake, e é bem feioAinda assim, um
makebem usado é bem bom. É realmente uma sorte termos omakecomo mínimo denominador comumÉ bem absurdo que o Make nem consiga lidar de forma limpa com subdiretórios. Fico curioso se existe algo melhor que o Make. Existe o Ninja, mas ele não foi projetado para ser escrito diretamente por humanos
Quanto você acha que contribui? Uns 20%, 40%, 70% não parece correto?
Gosto muito de Make. Nos últimos 20 anos, escrevi pelo menos um para todo trabalho ou projeto em que mexi
Não há nada inteligente; é só uma colagem de trechos com algumas variáveis. Coisas como
make run,make test,make lintmake recentfaz lint e depois executa o script modificado mais recentemente. Dá para fazer isso com Bash ou outro shell, mas aí você é sugado para o mundo do desenvolvedor, só aumentando a complexidade sem valor adicional. Make é apenas uma linguagem específica de domínio para “este tipo de arquivo é feito a partir deste tipo de arquivo e, nesse momento, execute este ou dois comandos”. Por isso é extremamente poderosoDécadas atrás, criei um framework de testes em Java que permitia especificar testes e classes dependentes com uma sintaxe parecida com make. Havia um conjunto de classes de teste que definia um “pacote base de testes”, e outras camadas de classes de teste dependentes dele só eram executadas se a etapa anterior tivesse sucesso
Não sei por que hoje tudo precisa ser tão complicado. Sempre aconselhei a manter as ferramentas Unix padrão e sua forma de fazer as coisas, ou seja, métodos comprovados pelo tempo. A menos que seja uma situação em que isso seja absolutamente impossível. O tempo é finito, então quero gastá-lo no que é central para resolver o problema: projeto de sistemas/programas, modelagem, estrutura, padrões etc.; o resto é secundário
É um grande alívio saber que, independentemente da stack, da linguagem ou do repositório, executar
make testfuncionaO Makefile do meu projeto anterior tinha quatro comandos principais:
build,test,frontend,deploy. build e test chamavam maven, frontend chamava npm, e deploy chamava docker e awsTodas essas ferramentas têm seu próprio rastreamento de estado, cache e processamento incremental, mas não informam o que fizeram. Por isso não dá para escrever regras em nível molecular como “implante apenas se o build tiver sido atualizado”. Porque maven/cargo/dotnet/npm/go não expõem essa informação
O autor nem sequer usa rastreamento de dependências baseado em mtime. Os alvos também deveriam ser
.PHONY, mas não estão marcados assim. Isso poderia ter sido substituído por um script shell que lê$1, faz o matching e decide o que fazerhttps://just.systems/
.PHONY: $(MAKECMDGOALS)./build.sh devé menos claro quemake dev. Outro motivo para usar Make, mesmo sem nenhuma etapa não phony, é poder adicionar essas etapas depois, se necessário. Claro que concordo que o autor deveria marcar{dev,build,deploy}como phonyO Makefile dele poderia ser tecnicamente melhorado, mas o mais importante é que a experiência de entrar em um projeto e “começar” seja consistente
As pessoas querem que isso faça muitas outras coisas, como linguagem de script, rastreador de dependências etc., e acabam trazendo ferramentas inchadas. Novo não quer dizer necessariamente melhor. Autoconf e automake não são make
Tecnicamente, todos esses alvos do make procuram arquivos com o mesmo nome do alvo. Cada alvo deveria, de fato, ser definido como
.PHONYAntigamente eu também usava Makefiles assim o tempo todo, mas nos últimos anos migrei para just e justfile. Esse comportamento é o padrão lá, e no geral é mais fácil de usar. Coisas como parâmetros também são mais simples
https://github.com/casey/just
:, para que fosse possível distingui-los só pelo nomeClaro que agora é tarde demais
Acho que já passou da hora de aparecer algo como uma nova distribuição de coreutils que agrupe ferramentas novas e úteis, como just e ripgrep, e seja instalada em qualquer lugar onde você queira usá-las
É curioso que make provoque debates tão acalorados. Lembra as antigas discussões vi versus emacs, quase guerras religiosas
Concordo totalmente com o post original e, em especial, acho inteligente encapsular ferramentas de build mais sofisticadas com um Makefile de nível superior. A vantagem é a padronização. Sem precisar lembrar de nada: se é um projeto meu, sei que basta digitar
makee vai funcionarPor exemplo, alguém que usa C e quer compilar um projeto Rust não precisa procurar como usar cargo; basta digitar
makeougmake. Tento seguir POSIX sem usar recursos específicos do GNU, mas também é verdade que quase 100% dos makes reais são gmakeA sugestão de usar o atemporal
makecomo uma espécie de acionador de sistema de build de nível superior provavelmente ainda vai funcionar daqui a 250 anoscargo buildO Make em si é excelente, mas, na maioria das vezes em que trabalhei com projetos C, era uma combinação de cmake/autotools e instalação global de pacotes, e isso eu frequentemente precisava consultar
Concordo com o sentimento daqui, mas estou reescrevendo muita coisa como Justfile
https://github.com/casey/just
Dá para evitar muitas esquisitices específicas de Makefile
Sim, Make é excelente. Uso para muita coisa. É uma boa forma de automatizar tarefas
Por exemplo, no meu site pessoal, um Makefile chama scripts bash para recriar as páginas atualizadas, faz git push para o servidor, e então um git hook no servidor chama o Make para fazer o deploy. Só que não quero colocar no repositório Git blobs binários que mudam com frequência, como PDFs de material de aula. Tudo bem. O Makefile tem um alvo
uploadsque envia para o servidor apenas os PDFs modificados, e esse alvo é dependência do alvodeploy, que faz o git push, então não preciso me preocupar com issoAlém disso, PDFs atualizados de material de aula são colocados automaticamente na árvore de código-fonte do site por outro Makefile. Com esse Makefile, posso gerar a partir do código-fonte LaTeX o PDF que uso, ou versões alternativas para alunos a partir da mesma fonte, por exemplo uma versão sem respostas dos exercícios, publicá-las automaticamente na versão local do site e, quando eu quiser, enviá-las para o site atualizado
É uma espécie de cadeia infinita de Makefiles. Gosto de Makefiles
Nesse caso, o que o autor realmente precisa não é make, é just
https://just.systems/man/en/
Como executor de tarefas, o make não é ruim, mas hoje em dia há alternativas melhores, como
just, conforme outros comentáriosComo sistema de build, o make é ok até você esbarrar nas desvantagens. make/Makefile não é padronizado, por isso existe o automake. Então você não escreve um Makefile diretamente; escreve um template e faz com que o makefile real seja gerado. Se você for dono de toda a toolchain, tudo bem, mas a maioria não é, então isso é feito para garantir um Makefile portável
O make não faz nenhum tipo de resolução de dependências e presume que o necessário está exatamente ali. Por isso surgiram os scripts
configure; e, como scriptsconfiguretambém não são um padrão, você acaba gerando o scriptconfigurecom autoconf/autoreconf antes de executar o alvo do makeO make e ferramentas adjacentes como automake/autoconf/autoreconf usam mtime para decidir se as entradas estão desatualizadas. É possível cair numa situação em que, mesmo executando autoconf/autoreconf/automake/configure, as entradas continuam permanentemente desatualizadas e nada pode ser buildado. A propósito, muitos sistemas de build ficam bem usando mtime, desde que haja rastreamento de dependências adequado
No geral, a falha fundamental de projeto do make é que ele segue a filosofia Unix, isto é, “faça uma coisa e faça-a bem”. Essa uma coisa é “rebuildar o alvo se a entrada estiver desatualizada”, mas isso é uma ferramenta muito limitada, então sistemas de build modernos precisam empilhar muita coisa em cima dela para torná-la útil além de um executor básico de tarefas
makenão resolve dependências?dependency:...target: dependency...make configure. Isso configura o que é necessário paramake [build]Por isso acabamos usando touchfiles; é bagunçado, mas ainda funciona melhor do que muitas outras coisas. Estou falando especialmente do cache de
docker build