Hacer jabón, rehacer el poder

¡Saludos! Quiero hablar sobre las principales, no siempre obvias, deficiencias del sistema de compilación Make , que a menudo lo hacen inutilizable, y también hablar de una excelente alternativa y solución al problema: la más ingeniosa en su simplicidad, el sistema de rehacer . La idea del famoso DJB , cuya criptografía no se usa en ningún lado. Personalmente, rehacer me impresionó tanto con la simplicidad, la flexibilidad y el rendimiento mucho mejor de las tareas de construcción que me cambiaron la vida, que reemplacé por completo a Make con él en casi todos mis proyectos (donde no lo reemplacé, significa que aún no lo pude conseguir), de los cuales no pude encontrar ninguno un beneficio o razón para seguir vivo.





¿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.



All Articles