
Ainda outra marca?
Muitas pessoas não estão satisfeitas com o Make, caso contrário não haveria dezenas de outros sistemas de construção e dezenas de dialetos do Make sozinho. A refazer esse mais uma alternativa? Por um lado, é claro, sim - apenas extremamente simples, mas capaz de resolver absolutamente todas as tarefas do Make. Por outro lado, temos alguma marca comum e uniforme?
A maioria dos sistemas de construção "alternativos" nasceu porque não tinha os recursos nativos do Make, faltou flexibilidade. Muitos sistemas estão preocupados apenas em gerar Makefiles, não em construí-los eles próprios. Muitos são adaptados ao ecossistema de certas linguagens de programação.
Abaixo vou tentar mostrar que refazer é um sistema muito mais notável, não apenas outra solução.
Make está sempre lá de qualquer maneira
Pessoalmente, eu ainda sempre olhei com desconfiança para toda essa alternativa, porque ela é mais complexa ou específica do ecossistema / linguagem, ou é uma dependência adicional que precisa ser definida e aprendido como usá-la. E Make é uma coisa com a qual, mais ou menos, todos estão familiarizados e sabem como usar em um nível básico. Portanto, sempre e em todo lugar eu tentei usar POSIX Make, assumindo que isso é algo que em qualquer caso, todos têm no sistema (POSIX) pronto para uso, como o compilador C. E as tarefas em Make devem ser executadas apenas para as quais se destina: execução paralelizável de metas (comandos ) levando em consideração as dependências entre eles.
Qual é o problema em apenas escrever em Make e ter certeza de que funciona em qualquer sistema? Afinal, você pode (deve!) Escrever no shell POSIX e não forçar os usuários a instalar um GNU Bash enorme e monstruoso. O único problema é que apenas o dialeto POSIX Make funcionará, o que é escasso o suficiente mesmo para muitos projetos pequenos e simples. Make em sistemas BSD modernos é mais complexo e cheio de recursos. Bem, com o GNU Make, poucos podem se comparar a ninguém, embora quase ninguém use seus recursos ao máximo e não saiba como usá-los. Mas GNU Make não suporta um dialeto de sistemas BSD modernos. Os sistemas BSD não possuem GNU Make (e são compreensíveis!).
Usar um dialeto BSD / GNU significa potencialmente forçar o usuário a instalar software adicional que não sai da caixa de qualquer maneira. Nesse caso, a possível vantagem do Make - sua presença no sistema, é anulada.
É possível usar e escrever em POSIX Make, mas difícil. Pessoalmente, lembro-me imediatamente de dois casos muito irritantes:
- Algumas implementações de Make, ao executar $ (MAKE) -C, "vão" para o diretório onde o novo Make é executado, e outras não. É possível escrever um Makefile para que funcione da mesma forma em todos os lugares? Claro:
tgt: (cd subdir ; $(MAKE) -C ...)
Convenientemente? Definitivamente não. E é desagradável que tenhamos de nos lembrar constantemente dessas ninharias. - No POSIX Make, não há nenhuma instrução que execute uma chamada de shell e armazene seu resultado em uma variável. No GNU Make up até a versão 4.x, você pode fazer:
VAR = $(shell cat VERSION)
e começando com 4.x, bem como em dialetos BSD, você pode fazer:
VAR != cat VERSION
Não é exatamente a mesma ação que pode ser realizada:
VAR = `cat VERSION`
mas literalmente substitui essa expressão em seus comandos shell descritos nos destinos. Essa abordagem é usada em projetos sem sucção , mas é, obviamente, uma muleta.
Pessoalmente, em tais lugares, muitas vezes escrevi Makefiles para três dialetos de uma vez (GNU, BSD e POSIX):
$ cat BSDmakefile
GOPATH != pwd
VERSION != cat VERSION
include common.mk
$ cat GNUmakefile
GOPATH = $(shell pwd)
VERSION = $(shell cat VERSION)
include common.mk
Convenientemente? Longe disso! Embora as tarefas sejam extremamente simples e comuns. Então, acontece que:
- Escreva em paralelo para vários dialetos Make. Negociar o tempo do desenvolvedor para conveniência do usuário.
- Tendo em mente muitas nuances e trivialidades, talvez com substituições ineficientes ( `cmd ...` ), tente escrever no POSIX Make. Para mim, pessoalmente, com muitos anos de experiência com GNU / BSD Make, esta opção é a mais demorada (é mais fácil de escrever em vários dialetos).
- Escreva em um dos dialetos Make, forçando o usuário a instalar software de terceiros.
Faça problemas técnicos
Mas tudo é muito pior porque qualquer Make não diz que (bem) dá conta das tarefas que lhe são atribuídas.
- mtime , Make mtime, . , , Make . mtime ! mtime , , ! mtime — , . FUSE mtime . mmap mtime… -, msync ( POSIX ). NFS? , Make : ( ), , FUSE/NFS/mmap/VCS.
- . ? Make . :
tgt-zstd: zstd -d < tgt-zstd.zst > tgt tgt-fetch: fetch -o tgt-fetch SOME://URL
, , Make , , , , Make, .
:
tgt-zstd: zstd -d < tgt-zstd.zst > tgt-zstd.tmp fsync tgt-zstd.tmp mv tgt-zstd.tmp tgt-zstd
tmp/fsync/mv ? , Make-, tgt.tmp. - . ( ) Makefile, Make ? . - $(CFLAGS)? .
Makefile! . Makefile , , . , , - , .
Makefile :
$ cat Makefile include tgt1.mk include tgt2.mk ...
. ? !
- , . Recursive Make Considered Harmful , Makefile-, Makefile- - , , Make , . Makefile — . ? , Makefile.
? , . FreeBSD , , , , .
- . , #include «tgt.h», .c tgt.h, .c - sed .
tgt.o: tgt.c `sed s/.../ tgt.c`
. .mk Makefile include. ? Make, : .mk , , Makefile- include-.
- Makefile- shell, , - , \\$, , .sh , Make. Make /, shell shell, . ?
Vamos admitir honestamente: quantas vezes e quanto você teve que fazer para limpar ou reconstruir sem paralelização, porque algo estava incompleto ou não reconstruiu ao contrário do esperado? No caso geral, é claro, isso não se deve a Makefiles idealmente corretos, corretos e completos, o que demonstra a complexidade de sua escrita competente e eficiente. A ferramenta deve ajudar.
Requisitos de refazer
Para passar para a descrição de redo , primeiro direi o que é uma implementação e o que o "usuário" (o desenvolvedor que descreve os objetivos e dependências entre eles) terá que aprender.
- redo, , - . redo . POSIX shell . Python . : , , .
- redo : POSIX shell, GNU bash, Python, Haskell, Go, C++, Inferno Shell. .
- C , SHA256, 27KB. POSIX shell 100 . , POSIX shell redo tarball- .
- Make-, ( ).
redo
As regras de construção de destino são um script de shell POSIX regular em target_name.do . Deixe-me lembrá-lo pela última vez que pode ser qualquer outra linguagem (se você adicionar um shebang) ou apenas um arquivo binário executável, mas por padrão é um shell POSIX. O script é executado com set -e e três argumentos:
- $1 —
$2 — ( )
$3 —
redo . stdout $3 . ? - , - stdout. redo:
$ cat tgt-zstd.do zstd -d < $1.zst $ cat tgt-fetch.do fetch -o $3 SOME://URL
, fetch stdout. stdout , $3. , fsync . ! , fsync — .
, (make) clean, , . redo , . , all .
default
. POSIX Make .c:
.c: $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
redo default.do , default.---.do. Make :
$ cat default.c.do $CC $CLFAGS $LDFLAGS -o $3 $1
— $2 , $1 «» redo . default- :
a.b.c.do -> $2=a.b.c default.do -> $2=a.b.c default.c.do -> $2=a.b default.b.c.do -> $2=a
, , . cd dir; redo tgt redo dir/tgt. .do . , .
-.do , default.do . , .do ../a/b/xtarget.y :
./../a/b/xtarget.y.do ./../a/b/default.y.do ./../a/b/default.do ./../a/default.y.do ./../a/default.do ./../default.y.do ./../default.do
2/3 redo .
redo-ifchange :
$ cat hello-world.do redo-ifchange hello-world.o ../config . ../config $CC $CFLAGS -o $3 hello-world.o $ cat hello-world.o.do redo-ifchange hw.c hw.h ../config . ../config $CC $CFLAGS -c -o $3 hw.c $ cat ../config CC=cc CFLAGS=-g $ cat ../all.do # , , <em>redo</em>, # hw/hello-world redo-ifchange hw/hello-world # $ cat ../clean.do redo hw/clean $ cat clean.do rm -f *.o hello-world
redo : state. . redo-ifchange , - , - , , , , . .do . , config hello-world .
state? . - TSV-like -.do.state, - , .redo , - SQLite3 .redo .
stderr - , - state, « - ».
state? redo : , FUSE/mmap/NFS/VCS, . ctime, inode number, — , .
state lock- Make — . ( ) state lock- . .
, redo-ifchange - , . — . redo-ifchange , :
redo-ifchange $2.c gcc -o $3 -c $2.c -MMD -MF $2.deps read deps < $2.deps redo-ifchange ${deps#*:}
, include-:
$ cat default.o.do deps=`sed -n 's/^#include "\(.*\)"$/\1/p' < $2.c` redo-ifchange ../config $deps [...]
*.c?
for f in *.c ; do echo ${f%.c}.o ; done | xargs redo-ifchange
.do (....do.do ) . .do $CC $CFLAGS..., « »:
$ cat tgt.do redo-ifchange $1.c cc ./cc $3 $1.c $ cat cc.do redo-ifchange ../config . ../config cat > $3 <<EOF #!/bin/sh -e $CC $CFLAGS $LDFLAGS -o \$1 \$@ $LDLIBS EOF chmod +x $3
compile_flags.txt Clang LSP ?
$ cat compile_flags.txt.do redo-ifchange ../config . ../config echo "$PCSC_CFLAGS $TASN1_CFLAGS $CRYPTO_CFLAGS $WHATEVER_FLAGS $CFLAGS" | tr " " "\n" | sed "/^$/d" | sort | uniq
$PCSC_CFLAGS, $TASN1_CFLAGS? , pkg-config, autotools!
$ cat config.do cat <<EOF [...] PKG_CONFIG="${PKG_CONFIG:-pkgconf}" PCSC_CFLAGS="${PCSC_CFLAGS:-`$PKG_CONFIG --cflags libpcsclite`}" PCSC_LDFLAGS="${PCSC_LDFLAGS:-`$PKG_CONFIG --libs-only-L libpcsclite`}" PCSC_LDLIBS="${PCSC_LDLIBS:-`$PKG_CONFIG --libs-only-l libpcsclite`}" TASN1_CFLAGS="${TASN1_CFLAGS:-`$PKG_CONFIG --cflags libtasn1`}" TASN1_LDFLAGS="${TASN1_LDFLAGS:-`$PKG_CONFIG --libs-only-L libtasn1`}" TASN1_LDLIBS="${TASN1_LDLIBS:-`$PKG_CONFIG --libs-only-l libtasn1`}" [...] EOF
- .do , Makefile:
foo: bar baz hello world .c: $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
:
$ cat default.do case $1 in foo) redo-ifchange bar baz hello world ;; *.c) $CC $CFLAGS $LDFLAGS -o $3 $1 ;; esac
, default.do . .o ? special.o.do, fallback default.o.do default.do .
redo , , « , !?» ( default ). , , , , . suckless ( , CMake, GCC, pure-C redo — ).
- - .
- (*BSD vs GNU) — POSIX shell , (Python, C, shell) redo .
- / Makefile-.
- .
- ( ) , , .
- — , , l **.do.
/?
- Make , .
- Levei mais de um mês para desaprender o reflexo de refazer limpar , já que é um hábito depois de fazer que algo não (re) se reúna
Eu recomendo a documentação de implementação apenwarr / redo , com toneladas de exemplos e explicações.
Sergey Matveev , cypherpunk , desenvolvedor Python / Go / C, especialista chefe do FSUE STC Atlas.