Machen Sie Seife, wiederholen Sie die Kraft

Schöne Grüße! Ich möchte über die wichtigsten, nicht immer offensichtlichen Mängel des Make- Build-Systems sprechen, die es oft unbrauchbar machen, und auch über eine hervorragende Alternative und Lösung des Problems - das genialste in seiner Einfachheit, das Redo- System . Die Idee der berühmten DJB , deren Kryptographie nirgendwo verwendet wird. Persönlich beeindruckte mich Redo so sehr mit lebensverändernder Einfachheit, Flexibilität und viel besserer Leistung von Build-Aufgaben, dass ich Make in fast allen meinen Projekten vollständig ersetzt habe (wo ich es nicht ersetzt habe, bedeutet dies, dass ich es noch nicht bekommen habe), was ich nicht finden konnte. ein Vorteil oder Grund, am Leben zu bleiben.





Noch eine Marke?



Viele Leute sind mit Make nicht zufrieden, sonst gäbe es nicht Dutzende anderer Build-Systeme und Dutzende von Dialekten von Make allein. Ein Redo dieses noch eine Alternative? Einerseits natürlich ja - nur extrem einfach, aber in der Lage, absolut dieselben Aufgaben wie Make zu lösen . Haben wir andererseits ein gemeinsames und einheitliches Make?



Die meisten "alternativen" Build-Systeme wurden geboren, weil ihnen die nativen Make-Funktionen und die Flexibilität fehlten. Viele Systeme befassen sich nur mit dem Generieren von Makefiles, nicht mit dem Erstellen von Makefiles. Viele sind auf das Ökosystem bestimmter Programmiersprachen zugeschnitten.



Unten werde ich versuchen, diese Wiederholung zu zeigen ist ein viel bemerkenswerteres System, nicht nur eine andere Lösung.



Make ist sowieso immer da



Persönlich habe ich diese ganze Alternative immer noch schief betrachtet, weil sie entweder komplexer oder ökosystem- / sprachspezifisch ist oder eine zusätzliche Abhängigkeit darstellt, die festgelegt und gelernt werden muss, wie man sie verwendet. Und Make ist so etwas, dass jeder, ob Plus oder Minus, damit vertraut ist und weiß, wie man es auf einer grundlegenden Ebene verwendet. Daher habe ich immer und überall versucht, POSIX Make zu verwenden, vorausgesetzt, dass dies auf jeden Fall jeder im (POSIX) -System sofort hat, wie zum Beispiel der C-Compiler. ) unter Berücksichtigung der Abhängigkeiten zwischen ihnen.



Was ist das Problem, wenn Sie nur in Make schreiben und sicherstellen, dass es auf jedem System funktioniert? Schließlich können (müssen!) Sie in die POSIX-Shell schreiben und Benutzer nicht zwingen, einen monströsen riesigen GNU Bash zu installieren. Das einzige Problem ist, dass nur der POSIX Make-Dialekt funktioniert, was selbst für viele kleine einfache Projekte selten genug ist. Make auf modernen BSD-Systemen ist komplexer und funktionsreicher. Nun, mit GNU Make können sich nur wenige mit irgendjemandem vergleichen, obwohl fast niemand seine vollen Fähigkeiten nutzt und nicht weiß, wie man sie benutzt. GNU Make unterstützt jedoch keinen Dialekt moderner BSD-Systeme. BSD-Systeme enthalten kein GNU Make (und sie sind verständlich!).



Die Verwendung eines BSD / GNU-Dialekts bedeutet, dass der Benutzer möglicherweise gezwungen wird, zusätzliche Software zu installieren, die ohnehin nicht sofort einsatzbereit ist. In diesem Fall wird der potenzielle Vorteil von Make - seine Präsenz im System - zunichte gemacht.



Es ist möglich, in POSIX Make zu verwenden und zu schreiben, aber schwierig. Persönlich erinnere ich mich sofort an zwei sehr ärgerliche Fälle:



  • Einige Make-Implementierungen "gehen" beim Ausführen von $ (MAKE) -C in das Verzeichnis, in dem das neue Make ausgeführt wird, andere nicht. Ist es möglich, ein Makefile so zu schreiben, dass es überall gleich funktioniert? Natürlich:



    tgt:
        (cd subdir ; $(MAKE) -C ...)
    


    Praktisch? Definitiv nicht. Und es ist unangenehm, dass man sich ständig an solche Kleinigkeiten erinnern muss.
  • In POSIX Make gibt es keine Anweisung, die einen Shell-Aufruf ausführt und das Ergebnis in einer Variablen speichert. In GNU Make up to 4.x können Sie Folgendes tun:



    VAR = $(shell cat VERSION)
    


    und beginnend mit 4.x sowie in BSD-Dialekten können Sie Folgendes tun:



    VAR != cat VERSION
    


    Es kann nicht ganz die gleiche Aktion ausgeführt werden:



    VAR = `cat VERSION`
    


    Dieser Ausdruck wird jedoch buchstäblich durch Ihre in den Zielen beschriebenen Shell-Befehle ersetzt. Dieser Ansatz wird in saugfreien Projekten verwendet, ist aber natürlich eine Krücke.


Persönlich habe ich an solchen Orten oft Makefiles für drei Dialekte gleichzeitig geschrieben (GNU, BSD und POSIX):



$ cat BSDmakefile
GOPATH != pwd
VERSION != cat VERSION
include common.mk

$ cat GNUmakefile
GOPATH = $(shell pwd)
VERSION = $(shell cat VERSION)
include common.mk


Praktisch? Weit davon entfernt! Obwohl die Aufgaben extrem einfach und häufig sind. Es stellt sich also heraus, dass entweder:



  • Schreiben Sie parallel für mehrere Make-Dialekte. Handel mit Entwicklerzeit für Benutzerfreundlichkeit.
  • Unter Berücksichtigung vieler Nuancen und Kleinigkeiten, möglicherweise mit ineffizienten Substitutionen ( `cmd ...` ), versuchen Sie, in POSIX Make zu schreiben. Für mich persönlich ist diese Option mit langjähriger Erfahrung mit GNU / BSD Make am zeitaufwändigsten (es ist einfacher, in mehreren Dialekten zu schreiben).
  • Schreiben Sie in einen der Make-Dialekte und zwingen Sie den Benutzer, Software von Drittanbietern zu installieren.


Machen Sie technische Probleme



Aber alles ist viel schlimmer, weil jedes Make nicht sagt, dass es die ihm zugewiesenen Aufgaben (gut) bewältigt.



  • 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, . ?


Lassen Sie uns ehrlich zugeben: Wie oft und wie viel mussten Sie ohne Parallelisierung bereinigen oder neu erstellen, weil etwas nicht kompiliert oder nicht wider Erwarten neu erstellt wurde? Im allgemeinen Fall liegt dies natürlich nicht an ideal korrekten, korrekt und vollständig geschriebenen Makefiles, was von der Komplexität ihres kompetenten und effizienten Schreibens spricht. Das Tool sollte helfen.



Anforderungen wiederholen



Um zur Beschreibung von Redo überzugehen , werde ich Ihnen zunächst sagen, was es sich um eine Implementierung handelt und was der "Benutzer" (der Entwickler, der die Ziele und Abhängigkeiten zwischen ihnen beschreibt) lernen muss.



  • 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



Die Zielerstellungsregeln sind ein reguläres POSIX-Shell-Skript in target_name.do . Ich möchte Sie zum letzten Mal daran erinnern, dass es sich um eine andere Sprache (wenn Sie einen Shebang hinzufügen) oder nur um eine ausführbare Binärdatei handeln kann. Standardmäßig handelt es sich jedoch um eine POSIX-Shell. Das Skript wird mit set -e und drei Argumenten ausgeführt:



  • $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 , .
    • Ich habe mehr als einen Monat gebraucht, um den Reflex für das Wiederherstellen zu verlernen , da es nach Make bereits eine Gewohnheit ist, dass sich etwas nicht (wieder) sammelt.


    Ich empfehle die Dokumentation zur Implementierung von apenwarr / redo mit unzähligen Beispielen und Erklärungen.



    Sergey Matveev , Cypherpunk , Python / Go / C-Entwickler, Chefspezialist des FSUE STC Atlas.



All Articles