涂上肥皂,重做力量

问候!我想谈一谈Make构建系统的主要缺点,但并非总是显而易见,这通常会使它变得无法使用,还想谈一谈解决该问题的绝佳方法和解决方案-最简单的重做系统redo system 著名的DJB的想法,其加密技术没有在任何地方使用。就个人而言,重做留下了深刻的印象,即改变生活的简单性,灵活性和更好的构建任务性能,使我几乎在几乎所有项目中都完全替换了Make(在我没有替换的情况下,这意味着我还没有亲身体验),我找不到。维持生命的一种好处或理由。





另一个品牌?



许多人对Make不满意,否则就不会有数十个其他构建系统和Make的方言。一个重做这一个又一个替代?在一方面,当然是-只有极其简单,但能够解决的绝对所有相同的任务,制作。另一方面,我们是否有一些通用且统一的Make?



大多数“替代”构建系统之所以诞生,是因为它们缺乏本机的Make功能和灵活性。许多系统只关心生成Makefile,而不是自己生成。许多工具是针对某些编程语言的生态系统量身定制的。



下面我将尝试显示重做 是一个更加值得注意的系统,而不仅仅是另一个解决方案。



无论如何,Make总是在那里



就我个人而言,我仍然总是对整个替代方案感到困惑,因为它要么更复杂,要么特定于生态系统/语言,要么是需要设置并学习如何使用它的附加依赖项。而且Make是这样一个东西,每个人都在加或减之间熟悉并知道如何在基本级别上使用。因此,无论何时何地,我都尝试使用POSIX Make,假设这是任何情况下(POSIX)系统中每个人都可以使用的东西,例如C编译器。Make中的任务只能按预期执行:并行执行目标(命令),并考虑到它们之间的依赖性。



仅用Make编写并确保其在任何系统上都能工作有什么问题?毕竟,您可以(必须!)在POSIX shell中进行编写,而不必强迫用户安装一些巨大的GNU Bash。唯一的问题是仅POSIX Make方言可以使用,即使对于许多小型简单项目而言,这也足够稀缺。在现代BSD系统上进行制作更加复杂且功能齐全。好的,使用GNU Make,几乎没有人可以与任何人进行比较,尽管几乎没有人充分利用其功能并且不知道如何使用它们。但是GNU Make不支持现代BSD系统的方言。 BSD系统中没有GNU Make(它们是可以理解的!)。



使用BSD / GNU方言意味着潜在地迫使用户安装并非开箱即用的其他软件。在这种情况下,Make的可能优势-它在系统中的存在将被抵消。



可以在POSIX Make中使用和编写,但是很困难。我个人立即想到了两个非常烦人的案例:



  • 有些Make实现在执行$(MAKE)-C时会“转到”执行新Make的目录,有些则没有。是否可以编写一个Makefile,使其在任何地方都相同?当然:



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


    方便吗 当然不。令人不愉快的是,人们必须不断记住这种琐事。
  • 在POSIX Make中,没有执行外壳调用并将其结果存储在变量中的语句。在GNU Make up to 4.x版本中,您可以执行以下操作:



    VAR = $(shell cat VERSION)
    


    从4.x开始,以及在BSD方言中,您可以执行以下操作:



    VAR != cat VERSION
    


    可以做的动作不完全相同:



    VAR = `cat VERSION`
    


    但实际上是用目标表达式中描述的shell命令替换了该表达式。这种方法用于无精打采的项目中,但它当然是拐杖。


就个人而言,我经常在这样的地方一次为三个方言(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 ...`),尝试用POSIX Make编写。对我个人而言,凭借多年使用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而引起的,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 Shell脚本。让我最后一次提醒您,它可以是任何其他语言(如果添加了shebang)或只是可执行的二进制文件,但是默认情况下,它是POSIX shell。该脚本使用set -e和三个参数运行:



  • $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之后,这已经成为一种习惯,有些东西不会(重新)聚集起来。


    我推荐apenwarr / redo实施文档,其中包含大量示例和说明。



    Sergey Matveevcypherpunk,Python / Go / C开发人员,FGUP STC Atlas首席专家。



All Articles