اصنع على الصابون ، وأعد القوة

تحية طيبة! أريد أن أتحدث عن أوجه القصور الرئيسية وغير الواضحة دائمًا في نظام Make build ، والتي غالبًا ما تجعله غير قابل للاستخدام ، وأتحدث أيضًا عن بديل ممتاز وحل للمشكلة - وهو الأكثر إبداعًا في بساطته ، وهو نظام الإعادة . فكرة DJB الشهير ، الذي لا يستخدم تشفيره في أي مكان. شخصيًا ، أثارت إعادة العرض إعجابي كثيرًا بالبساطة المتغيرة للحياة والمرونة والأداء الأفضل لمهام الإنشاء التي استبدلت بها تمامًا في جميع مشروعاتي تقريبًا (حيث لم أستبدلها ، فهذا يعني أنني لم أحصل عليها بعد) ، وهو ما لم أجده. فائدة أو سبب واحد للبقاء على قيد الحياة.





نوع آخر؟



كثير من الناس غير راضين عن Make ، وإلا فلن يكون هناك العشرات من أنظمة البناء الأخرى وعشرات اللهجات من Make Only. A إعادة هذا أحد حتى الآن بديل آخر؟ من ناحية ، بالطبع ، نعم - فقط بسيط للغاية ، ولكنه قادر على حل جميع المهام نفسها تمامًا مثل Make. من ناحية أخرى ، هل لدينا طراز عام وموحد؟



ولدت معظم أنظمة البناء "البديلة" لأنها تفتقر إلى قدرات الصنع الأصلية ، وتفتقر إلى المرونة. العديد من الأنظمة معنية فقط بتوليد ملفات Makefiles وليس بنائها بأنفسهم. تم تصميم العديد منها وفقًا للنظام البيئي لبعض لغات البرمجة.



أدناه سأحاول إظهار ذلك الإعادة هو نظام جدير بالملاحظة ، وليس مجرد حل آخر.



جعل دائما هناك على أي حال



شخصيًا ، ما زلت دائمًا ما ألقي نظرة شائبة على هذا البديل بأكمله ، لأنه إما أكثر تعقيدًا ، أو متعلقًا بالنظام البيئي / اللغة ، أو أنه تبعية إضافية تحتاج إلى تعيينها وتعلم كيفية استخدامها. و 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 ، لا يوجد بيان ينفذ استدعاء shell ويخزن نتيجته في متغير. في GNU Make up إلى الإصدار 4.x يمكنك القيام بما يلي:



    VAR = $(shell cat VERSION)
    


    وبدءًا من 4.x ، وكذلك في لهجات BSD ، يمكنك القيام بما يلي:



    VAR != cat VERSION
    


    لا يمكن القيام بنفس الإجراء:



    VAR = `cat VERSION`
    


    لكنها تحل محل هذا التعبير حرفياً في أوامر shell الموصوفة في الأهداف. يتم استخدام هذا النهج في المشاريع التي لا تمتص ، لكنه بالطبع عكاز.


شخصيًا ، غالبًا ما أكتب ملفات Makefiles في مثل هذه الأماكن لثلاث لهجات في وقت واحد (GNU و BSD و POSIX):



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

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


ملائم؟ بعيد عنه! على الرغم من أن المهام بسيطة للغاية وشائعة. لذلك اتضح أن إما:



  • اكتب بالتوازي لعدة لهجات. وقت مطور التداول لراحة المستخدم.
  • مع الأخذ في الاعتبار الكثير من الفروق الدقيقة والتفاهات ، ربما مع الاستبدالات غير الفعالة ( `cmd ...` ) ، حاول الكتابة في POSIX Make. بالنسبة لي شخصيًا ، مع سنوات عديدة من الخبرة في GNU / BSD 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, . ?


دعنا نعترف بصراحة: كم مرة وكم كان عليك القيام بالنظافة أو إعادة البناء دون موازاة ، لأن شيئًا ما لم يتم تجميعه أو إعادة بنائه على عكس التوقعات؟ في الحالة العامة ، بالطبع ، لا يرجع هذا إلى ملفات Makefiles الصحيحة والصحيحة والمكتوبة بشكل مثالي ، والتي تتحدث عن تعقيد كتابتها المختصة والفعالة. يجب أن تساعد الأداة .



متطلبات الإعادة



للانتقال إلى وصف الإعادة ، سأخبرك أولاً بما هو تطبيق وما الذي يجب أن يتعلمه "المستخدم" (المطور الذي يصف الأهداف والتبعيات بينهما).



  • 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



قواعد البناء الهدف هي برنامج نصي POSIX shell عادي في target_name.do . اسمحوا لي أن أذكرك للمرة الأخيرة أنه يمكن أن تكون أي لغة أخرى (إذا أضفت 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 ، مع الكثير من الأمثلة والتوضيحات.



    سيرجي ماتفيف ، cypherpunk ، Python / Go / C-developer ، المتخصص الرئيسي في FSUE STC Atlas.



All Articles