石鹸で作り、力をやり直す

こんにちは!Makeビルドシステムの主な欠点について話したいと思います。これは、Makeビルドシステムを使用できなくすることがよくあります。また、問題の優れた代替手段と解決策についても話したいと思いますその単純さの中で最も独創的な、やり直しシステムです。暗号化がどこにも使用されていない有名なDJBのアイデア個人的には、やり直しは、人生を変えるシンプルさ、柔軟性、ビルドタスクのパフォーマンスの大幅な向上に非常に感銘を受け、ほとんどすべてのプロジェクトでMakeを完全に置き換えました(置き換えなかった場合は、まだ手に入れていませんでした)。そこからは何も見つかりませんでした。生き続けるための1つの利点または理由。





さらに別のメイク?



多くの人々はMakeに満足していません。さもなければ、他の何十ものビルドシステムやMakeだけの何十もの方言が存在しないでしょう。やり直しこの1さらに別の代替?もちろん、一方ではもちろんです。非常に単純ですが、Makeとまったく同じタスクを完全に解決することができます。一方で、共通の統一されたメイクはありますか?



ほとんどの「代替」ビルドシステムは、ネイティブのMake機能がなく、柔軟性がないために生まれました。多くのシステムは、Makefileの生成のみに関心があり、Makefileを自分で構築することには関心がありません。多くは、特定のプログラミング言語のエコシステムに合わせて調整されています。



以下にそのやり直しを表示しようとします は、単なる別のソリューションではなく、はるかに注目に値するシステムです。



とにかくメイクはいつもそこにあります



個人的には、この代替案はより複雑であるか、エコシステム/言語固有であるか、設定して使用方法を学ぶ必要がある追加の依存関係であるため、私は常にこの代替案全体を常に検討していました。そして、Makeは、プラスまたはマイナスで、誰もが基本的なレベルでの使用方法に精通し、知っているようなものです。したがって、私はいつでもどこでもPOSIX Makeを使おうとしましたが、これはCコンパイラなど、誰もが(POSIX)システムにすぐに使用できるものであると想定しています。また、Makeのタスクは、目的の目的でのみ実行します。つまり、目標の並列実行です。 )それらの間の依存関係を考慮に入れます。



Makeに書き込んで、それがどのシステムでも機能することを確認することの問題は何ですか?結局のところ、POSIXシェルに書き込むことができ(必須です!)、ユーザーに巨大な巨大なGNUBashをインストールするように強制することはできません。唯一の問題は、POSIX Makeダイアレクトのみが機能することです。これは、多くの小さな単純なプロジェクトでも十分に不足しています。最新のBSDシステムでのMakeは、より複雑で機能が豊富です。そうですね、GNU Makeでは、誰とでも比較できる人はほとんどいませんが、その機能を最大限に活用し、その使用方法を知らない人はほとんどいません。しかし、GNU Makeは、最新のBSDシステムの方言をサポートしていません。 BSDシステムにはGNUMakeが含まれていません(そしてそれらは理解できます!)。



BSD / GNUダイアレクトを使用するということは、とにかく箱から出てこない追加のソフトウェアをユーザーにインストールさせる可能性があることを意味します。この場合、Makeの考えられる利点(システム内での存在)は無効になります。



POSIX Makeでの使用と書き込みは可能ですが、難しいです。個人的に、私はすぐに2つの非常に厄介なケースを思い出します。



  • $(MAKE)-Cを実行すると、新しいMakeが実行されるディレクトリに「移動」するMake実装もあれば、実行しないMake実装もあります。どこでも同じように機能するようにMakefileを作成することは可能ですか?もちろん:



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


    便利ですか?絶対にありません。そして、そのような些細なことを常に覚えておかなければならないのは不快です。
  • POSIX Makeには、シェル呼び出しを実行してその結果を変数に格納するステートメントはありません。GNU Make up to version 4.xでは、次のことができます。



    VAR = $(shell cat VERSION)
    


    4.x以降、およびBSDダイアレクトでは、次のことができます。



    VAR != cat VERSION
    


    まったく同じアクションを実行できるわけではありません。



    VAR = `cat VERSION`
    


    ただし、ターゲットで説明されているシェルコマンドでは、文字通りこの式に置き換えられます。このアプローチはサックレスプロジェクトで使用されますが、もちろん、それはクラッチです。


個人的には、そのような場所で、一度に3つの方言(GNU、BSD、POSIX)のMakefileを作成することがよくありました。



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

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


便利ですか?それからは程遠い!タスクは非常に単純で一般的ですが。したがって、次のいずれかであることがわかります。



  • 複数のMakeダイアレクトに対して並行して書き込みます。ユーザーの便宜のために開発者の時間を取引する。
  • 多くのニュアンスとトリビアを念頭に置いて、おそらく非効率的な置換(`cmd ...`)を使用して、POSIXMakeで記述してみてください。個人的には、GNU / BSD Makeで長年の経験があるため、このオプションは最も時間がかかります(いくつかの方言で書く方が簡単です)。
  • Makeダイアレクトのいずれかを記述して、ユーザーにサードパーティのソフトウェアをインストールするように強制します。


技術的な問題を起こす



しかし、Makeは、割り当てられたタスクに(うまく)対処するとは言っていないため、すべてがはるかに悪化します。



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


正直に認めましょう。何かがコンパイルされなかったか、期待に反して再構築されなかったために、並列化せずにクリーンアップまたは再構築する必要があった頻度と量はどれくらいですか?もちろん、一般的なケースでは、これは、理想的に正しく、正しく、完全に記述されたMakefileによるものではありません。これは、有能で効率的な記述の複雑さを物語っています。ツール役立つはずです。



やり直しの要件



REDO の説明に移るには、まず、それが実装として何であるか、そして「ユーザー」(目標とそれらの間の依存関係を説明する開発者)が何を学ばなければならないかを説明します。



  • 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



ターゲットビルドルールは、target_name.doの通常のPOSIXシェルスクリプトです。前回、他の言語(shebangを追加した場合)または実行可能なバイナリファイルにすることができますが、デフォルトではPOSIXシェルであることを思い出させてください。スクリプトは、set-eと3つの引数を使用して実行さます。



  • $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 , .
    • Makeの後、何かが(再)収集されないのはすでに習慣であるため、やり直しを行うための反射を学習しないのに1か月以上かかりました


    たくさんの例と説明を含むapenwarr / redo実装ドキュメントをお勧めします。



    Sergey Matveevサイファーパンク、Python / Go / C-開発者、FSUE STCAtlasのチーフスペシャリスト。



All Articles