בנייה משוחזרת ושרשרת אספקה¶
הקוד עבר בדיקות במחשב שלנו, אבל מפעיל אחר לא מצליח לבנות אותו בלי להוריד גרסה חדשה של תלות native. זהו כשל של המוצר כמו באג בזמן ריצה: לא ניתן לדעת איזה binary נוצר ואיך לשחזר אותו. נפריד בין בנייה שחוזרת על אותה פונקציונליות לבין בנייה שמפיקה אותם byte-ים, ונשמור ראיות לשתיהן.
ה־Cargo.lock קובע את גרסאות התלויות שנפתרו עבור האפליקציה. הוא אינו נועל את rustc, את ה־linker, את sysroot של C או את הכלים ש־build.rs מפעיל. סקריפט build יכול לקרוא קבצים, להריץ פקודות ולהכניס נתונים מן הסביבה לתוצר. לכן סביבת הבנייה עצמה היא חלק מהקלט שצריך לתעד ולהגביל.
נכין manifest של הבנייה בתיקיית מעבדה נקייה. הפקודות הבאות אינן מתקינות כלים ואינן מעדכנות lockfile; הן אוספות מידע ומבקשות בנייה עם הגרסאות שכבר נבחרו.
set -eu
mkdir -p build-evidence
rustc -vV > build-evidence/rustc.txt
cargo -V > build-evidence/cargo.txt
cc --version > build-evidence/cc.txt
ld --version > build-evidence/ld.txt
cargo metadata --locked --format-version 1 > build-evidence/metadata.json
cargo tree --locked > build-evidence/tree.txt
cargo test --locked --all-targets
cargo build --locked --release
sha256sum Cargo.lock > build-evidence/lock.sha256
ה־metadata מאפשר לבדוק גם features ונתיבי מקור, ולא רק רשימת שמות crates. שני builds עם אותו lockfile יכולים להפעיל קבוצת features שונה. ה־target triple, משתני RUSTFLAGS, בחירת allocator ו־panic strategy משנים גם הם את התוצר ואת ההנחות שנבדקו. נתעד אותם ליד hash של ה־binary, ולא נסתפק בשם הגרסה של האפליקציה.
כדי לעבוד בלי תלות זמינה ברשת נוכל לבצע cargo vendor בסביבת הכנה ולשמור את התלויות ואת הגדרת source replacement. הפקודה מדפיסה configuration שיש לשלב ב־.cargo/config.toml, ולא לדרוס בעיוורון קובץ קיים שמגדיר linker או target. אחר כך ננסה cargo build --frozen בסביבה שבה אין גישה לרשת. ה־frozen משלב נעילת resolution עם מצב offline; הוא אינו מונע מ־build.rs שרירותי לקרוא רשת דרך פקודה משלו, ולכן נדרשת גם אכיפה סביבתית כשזה חלק מן המודל.
נבדוק reproducibility בשתי תיקיות שונות. נתיבי מקור מוחלטים יכולים להיכנס ל־debug information, וזמני build יכולים להופיע בקבצים שנוצרו. האפשרות remap-path-prefix יכולה לנרמל נתיבים בהתאם לחוזה הכלים, ו־SOURCE_DATE_EPOCH מאפשר לכלים שתומכים בו להשתמש בזמן קבוע שמקורו במקור. הוא אינו מתקן אוטומטית כל כלי שאינו מכבד אותו. כש־hash שונה נחקור את ההבדל במקום להכריז שהקוד שגוי או שהבדיקה מיותרת.
set -eu
export SOURCE_DATE_EPOCH=1700000000
export RUSTFLAGS="--remap-path-prefix=$(pwd)=/src"
CARGO_TARGET_DIR=build-a cargo build --frozen --release
CARGO_TARGET_DIR=build-b cargo build --frozen --release
cmp build-a/release/amitjobs build-b/release/amitjobs
הערך בדוגמה הוא זמן קבוע לצורך הניסוי; במוצר נבחר אותו ממדיניות הגרסה ונשמור את מקורו. הדוגמה מניחה שה־binary נקרא amitjobs ושהתלויות כבר זמינות. השוואה באותה מכונה ובאותו checkout היא בדיקה ראשונה בלבד; בהמשך נשווה builds בסביבות מבודדות עם אותם קלטים מתועדים.
בסקירת תלות חדשה נשאל אם היא נדרשת, איזה unsafe ו־native code היא מוסיפה, אילו features באמת מופעלים ומה מדיניות העדכון והתחזוקה שלה. נבדוק advisories ורישיונות בכלים המתאימים, אבל היעדר advisory אינו הוכחת בטיחות. התיעוד על cargo vendor ועל SOURCE_DATE_EPOCH מספק את אבני הבניין. התוצר שלנו הוא חבילת מקור וסביבת בנייה שאדם אחר יכול לשחזר בלי להסתמך על ההיסטוריה של המחשב שלנו.