¿Otra marca más?
Mucha gente no está satisfecha con Make, de lo contrario no habría docenas de otros sistemas de compilación y docenas de dialectos de Make solo. ¿ Rehacer esta otra alternativa? Por un lado, por supuesto, sí, solo extremadamente simple, pero capaz de resolver absolutamente todas las mismas tareas que Make. Por otro lado, ¿tenemos alguna marca común y uniforme?
La mayoría de los sistemas de construcción "alternativos" nacieron porque carecían de las capacidades nativas de Make, carecían de flexibilidad. Muchos sistemas solo se preocupan por generar Makefiles, no construirlos ellos mismos. Muchos se adaptan al ecosistema de ciertos lenguajes de programación.
A continuación intentaré mostrar que rehacer es un sistema mucho más notable, no una solución más.
Make siempre está ahí de todos modos
Personalmente, todavía siempre miré con recelo toda esta alternativa, porque es más compleja o específica del ecosistema / lenguaje, o es una dependencia adicional que debe configurarse y aprender a usarla. Y Make es algo que, más o menos, todo el mundo conoce y sabe utilizar a un nivel básico. Por lo tanto, siempre y en todas partes intenté usar POSIX Make, asumiendo que esto es algo que, en cualquier caso, todos tienen en el sistema (POSIX) listo para usar, como el compilador de C. Y las tareas en Make se realizan solo para lo que está destinado: ejecución paralelizada de objetivos ) teniendo en cuenta las dependencias entre ellos.
¿Cuál es el problema con solo escribir en Make y asegurarse de que funcione en cualquier sistema? Después de todo, puede (¡debe!) Escribir en el shell POSIX y no obligar a los usuarios a instalar un monstruoso y enorme GNU Bash. El único problema es que solo funcionará el dialecto POSIX Make, que es lo suficientemente escaso incluso para muchos proyectos pequeños y simples. Hacer en los sistemas BSD modernos es más complejo y con muchas funciones. Bueno, con GNU Make pocos pueden compararse con nadie, aunque casi nadie aprovecha al máximo sus capacidades y no sabe cómo usarlas. Pero GNU Make no admite un dialecto de los sistemas BSD modernos. Los sistemas BSD no tienen GNU Make (¡y son comprensibles!).
Usar un dialecto BSD / GNU significa potencialmente obligar al usuario a instalar software adicional que de todos modos no viene de fábrica. En este caso, la ventaja potencial de Make, su presencia en el sistema, queda anulada.
Es posible usar y escribir en POSIX Make, pero es difícil. Personalmente, recuerdo inmediatamente dos casos muy molestos:
- Algunas implementaciones de Make, al ejecutar $ (MAKE) -C, "van" al directorio donde se ejecuta el nuevo Make, y otras no. ¿Es posible escribir un Makefile para que funcione igual en todas partes? Por supuesto:
tgt: (cd subdir ; $(MAKE) -C ...)
Convenientemente? Definitivamente no. Y es desagradable que uno deba recordar constantemente sobre tales tonterías. - En POSIX Make, no hay ninguna declaración que ejecute una llamada de shell y almacene su resultado en una variable. En GNU Make up hasta la versión 4.x puede hacer:
VAR = $(shell cat VERSION)
y comenzando con 4.x, así como en dialectos BSD, puede hacer:
VAR != cat VERSION
No se puede realizar la misma acción:
VAR = `cat VERSION`
pero literalmente sustituye esta expresión en sus comandos de shell descritos en los destinos. Este enfoque se usa en proyectos sin succión , pero, por supuesto, es una muleta.
Personalmente, en esos lugares, a menudo escribía Makefiles para tres dialectos a la vez (GNU, BSD y POSIX):
$ cat BSDmakefile
GOPATH != pwd
VERSION != cat VERSION
include common.mk
$ cat GNUmakefile
GOPATH = $(shell pwd)
VERSION = $(shell cat VERSION)
include common.mk
Convenientemente? ¡Lejos de ahi! Aunque las tareas son extremadamente simples y comunes. Entonces resulta que:
- Escribe en paralelo para varios dialectos Make. Intercambiar tiempo de desarrollador por comodidad del usuario.
- Teniendo en cuenta muchos matices y trivialidades, quizás con sustituciones ineficientes ( `cmd ...` ), intente escribir en POSIX Make. Para mí, personalmente, con muchos años de experiencia con GNU / BSD Make, esta opción es la que consume más tiempo (es más fácil de escribir en varios dialectos).
- Escribe en uno de los dialectos Make, lo que obliga al usuario a instalar software de terceros.
Hacer problemas técnicos
Pero todo es mucho peor porque ninguna Marca no dice que (bien) haga frente a las tareas que se le asignan.
- 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, . ?
Admitamos honestamente: ¿con qué frecuencia y cuánto tuvo que hacer para limpiar o reconstruir sin paralelización, porque algo estaba incompleto o no se reconstruyó contrariamente a las expectativas? En el caso general, por supuesto, esto no se debe a Makefiles idealmente correctos, correctamente y completamente escritos, lo que habla de la complejidad de su escritura competente y eficiente. La herramienta debería ayudar.
Rehacer requisitos
Para pasar a la descripción de rehacer , primero te diré qué es como implementación y qué tendrá que aprender el "usuario" (el desarrollador describe los objetivos y las dependencias entre ellos).
- 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
Las reglas de compilación de destino son un script de shell POSIX normal en target_name.do . Déjeme recordarle por última vez que puede ser cualquier otro idioma (si agrega un shebang) o simplemente un archivo binario ejecutable, pero por defecto es un shell POSIX. El script se ejecuta con set -e y tres 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 , .
- Me tomó más de un mes desaprender el reflejo de rehacer limpio , ya que después de Make ya es un hábito que algo no (re) junte.
Recomiendo la documentación de implementación de apenwarr / redo , con toneladas de ejemplos y explicaciones.
Sergey Matveev , cypherpunk , desarrollador de Python / Go / C, especialista en jefe de FSUE STC Atlas.