markdown · 7602 bytes Raw Blame History

Sprint 0: Scaffolding, References, Harness

Prerequisites

None — this is where afs-ld begins. Assumes armfortas and afs-as already exist and compile.

Current state to remediate

The afs-ld/ directory currently sits inside armfortas's working tree as a plain subdirectory. afs-ld/.gitignore was accidentally committed to armfortas history (commit 85d5ba8 "init"), tracking a file that will shortly belong to a separate repo. Before anything else in this sprint, afs-ld must be extracted into its own repo and wired back in as a Git submodule. The tracked .gitignore must be removed from armfortas's index (history can stay; removing a single file at HEAD is clean) so that afs-ld's contents live in the submodule and nowhere else.

Goals

Extract afs-ld into its own repo, wire it back as a submodule, stand up the crate (CLAUDE.md, README, Cargo.toml, skeleton source), clone reference linkers, build the differential harness. End state: cargo test -p afs-ld runs from the parent workspace, at least one test passes meaningfully, and git submodule status lists afs-ld alongside afs-as.

Deliverables

1. Submodule remediation (do first)

Goal: move from "afs-ld is a tracked subdirectory of armfortas" to "afs-ld is a submodule pointing at git@github.com:FortranGoingOnForty/afs-ld.git". Exact sequence:

  1. Preserve the current afs-ld contents: copy armfortas/afs-ld/ to a temp location (the .docs/overview.md and sprint files produced in planning are the primary content to preserve; .fackr/ is scratch and can be dropped).
  2. Untrack from armfortas: git rm --cached afs-ld/.gitignore && git commit -m "remove accidentally-tracked afs-ld/.gitignore". Confirm git ls-files afs-ld returns empty.
  3. Delete the directory from armfortas's working tree (submodule-add will recreate it): rm -rf afs-ld/.
  4. Create the external repo FortranGoingOnForty/afs-ld on GitHub (empty, no README — submodule-add will seed it).
  5. Initialize locally and push: in a scratch directory,
    git init afs-ld
    cd afs-ld
    <copy preserved contents back>
    git add -A
    git commit -m "init"
    git remote add origin git@github.com:FortranGoingOnForty/afs-ld.git
    git push -u origin trunk
    
  6. Add as submodule in armfortas:
    cd <armfortas root>
    git submodule add git@github.com:FortranGoingOnForty/afs-ld.git afs-ld
    git commit -m "add afs-ld submodule"
    
    Confirm .gitmodules gained the stanza:
    [submodule "afs-ld"]
        path = afs-ld
        url = git@github.com:FortranGoingOnForty/afs-ld.git
    
  7. Verify with git submodule status — afs-as and afs-ld both listed, each pinned to a commit hash.

Note: the old git rm --cached commit stays in armfortas history. Rewriting history to erase it is more destructive than it's worth for a single .gitignore file. The file at HEAD is gone; that is sufficient.

2. Crate wiring

  • Root Cargo.toml adds "afs-ld" to [workspace] members alongside afs-as.
  • afs-ld/Cargo.toml: binary + library, zero external dependencies, edition = "2021". Mirror afs-as/Cargo.toml — same keywords, categories, license = "GPL-3.0-only", adjusted description and repository.
  • afs-ld/src/lib.rs: public re-exports of Linker, LinkOptions, OutputKind (types are stubs this sprint).
  • afs-ld/src/main.rs: CLI that prints usage and exits 0 when run with no args; forwards real args to Linker::run (which errors with "not yet implemented" this sprint).

3. CLAUDE.md and README.md

  • afs-ld/CLAUDE.md: mirror afs-as/CLAUDE.md, replacing assembler-isms with linker-isms. Non-negotiable rules: Rust std only, exhaustive matching, caret diagnostics, per-chunk commits, no co-authors, no sprint-number references in commits.
  • afs-ld/README.md: one-page intro, supported CLI subset at current state (nothing yet — say so), build/test commands.
  • afs-ld/.gitignore: target/, .fackr/, no .docs/ since those files live in the repo. This one is intentionally tracked because it lives inside afs-ld's own repo now, not armfortas's.

4. Reference clones

Add to parent .refs/ (gitignored):

  • .refs/ld64/git clone --depth 1 https://github.com/apple-oss-distributions/ld64.git. Apple's last publicly released ld64. Authoritative for Apple-parity edge cases.
  • .refs/mold/git clone --depth 1 https://github.com/rui314/mold.git. Performance reference and a second Rust-adjacent angle on Mach-O.

.refs/llvm/lld/MachO/ already exists from armfortas Sprint 0 — primary architectural reference.

5. Differential harness

afs-ld/tests/common/harness.rs:

pub struct LinkCase {
    pub name: &'static str,
    pub inputs: Vec<PathBuf>,       // .o / .a / .tbd
    pub args: Vec<String>,          // -o, -e, -syslibroot, -l, ...
}

pub struct LinkOutputs {
    pub ours: Vec<u8>,              // afs-ld output
    pub theirs: Vec<u8>,            // system ld output
}

pub fn link_both(case: &LinkCase) -> LinkOutputs;
pub fn diff_macho(ours: &[u8], theirs: &[u8]) -> DiffReport;

DiffReport categorizes byte differences as Tolerated (UUID, timestamp, temp-path hashes) or Critical (anything else). Critical diffs fail the test.

Closeout note: the current Sprint 0 surface is intentionally synthetic. diff_macho exists and is tested, but link_both remains a placeholder until afs-ld can emit real linked output. That means the current harness validates diff categorization logic, not end-to-end linker parity yet.

6. Skeleton CLI and first failing test

  • afs-ld/src/args.rs: hand-rolled argv parser stub that recognizes -o, -e, -arch, and positional inputs. Unknown flags error loudly with a hint.
  • afs-ld/tests/reader_empty.rs: attempts to link 0 inputs → empty output, expects the diagnostic "afs-ld: error: no input files". Passes today by producing that exact string.
  • afs-ld/tests/diff_harness_sanity.rs: exercises the diff surface against two identical synthetic byte slices and expects zero diffs. Passes.
  • afs-ld/tests/diff_harness_finds_critical.rs: feeds the harness two synthetic binaries that differ in a non-tolerated byte range and asserts the harness reports Critical. Passes.

Testing Strategy

  • cargo build -p afs-ld compiles from a fresh clone of the parent with git submodule update --init --recursive.
  • cargo test -p afs-ld runs harness-sanity, critical-detection, and empty-input tests.
  • cargo clippy -p afs-ld -- -D warnings clean.
  • Manual verification of submodule state:
    • git ls-files | grep afs-ld in armfortas prints only the .gitmodules entry (and nothing under afs-ld/).
    • git submodule status shows both afs-as and afs-ld with valid commit hashes.
    • git submodule update --init --recursive on a fresh armfortas clone populates afs-ld correctly.

Definition of Done

  • The accidentally-tracked afs-ld/.gitignore is removed from armfortas's index at HEAD.
  • afs-ld exists as a standalone GitHub repo under FortranGoingOnForty.
  • afs-ld is wired into armfortas as a Git submodule, visible in .gitmodules and git submodule status.
  • armfortas/Cargo.toml lists afs-ld in [workspace] members.
  • afs-ld/CLAUDE.md, README.md, Cargo.toml, src/lib.rs, src/main.rs, src/args.rs all committed in the new repo.
  • .refs/ld64/ and .refs/mold/ cloned.
  • Differential harness substrate runs, correctly reports zero diffs on identical byte slices, correctly reports critical diffs on intentionally-different byte slices.
  • cargo test --workspace green.
View source
1 # Sprint 0: Scaffolding, References, Harness
2
3 ## Prerequisites
4 None — this is where afs-ld begins. Assumes armfortas and afs-as already exist and compile.
5
6 ## Current state to remediate
7
8 The `afs-ld/` directory currently sits inside armfortas's working tree as a plain subdirectory. `afs-ld/.gitignore` was accidentally committed to armfortas history (commit `85d5ba8 "init"`), tracking a file that will shortly belong to a separate repo. Before anything else in this sprint, afs-ld must be extracted into its own repo and wired back in as a Git submodule. The tracked `.gitignore` must be removed from armfortas's index (history can stay; removing a single file at HEAD is clean) so that afs-ld's contents live in the submodule and nowhere else.
9
10 ## Goals
11 Extract afs-ld into its own repo, wire it back as a submodule, stand up the crate (CLAUDE.md, README, Cargo.toml, skeleton source), clone reference linkers, build the differential harness. End state: `cargo test -p afs-ld` runs from the parent workspace, at least one test passes meaningfully, and `git submodule status` lists afs-ld alongside afs-as.
12
13 ## Deliverables
14
15 ### 1. Submodule remediation (do first)
16
17 Goal: move from "afs-ld is a tracked subdirectory of armfortas" to "afs-ld is a submodule pointing at `git@github.com:FortranGoingOnForty/afs-ld.git`". Exact sequence:
18
19 1. **Preserve the current afs-ld contents**: copy `armfortas/afs-ld/` to a temp location (the `.docs/overview.md` and sprint files produced in planning are the primary content to preserve; `.fackr/` is scratch and can be dropped).
20 2. **Untrack from armfortas**: `git rm --cached afs-ld/.gitignore && git commit -m "remove accidentally-tracked afs-ld/.gitignore"`. Confirm `git ls-files afs-ld` returns empty.
21 3. **Delete the directory from armfortas's working tree** (submodule-add will recreate it): `rm -rf afs-ld/`.
22 4. **Create the external repo** `FortranGoingOnForty/afs-ld` on GitHub (empty, no README — submodule-add will seed it).
23 5. **Initialize locally and push**: in a scratch directory,
24 ```
25 git init afs-ld
26 cd afs-ld
27 <copy preserved contents back>
28 git add -A
29 git commit -m "init"
30 git remote add origin git@github.com:FortranGoingOnForty/afs-ld.git
31 git push -u origin trunk
32 ```
33 6. **Add as submodule in armfortas**:
34 ```
35 cd <armfortas root>
36 git submodule add git@github.com:FortranGoingOnForty/afs-ld.git afs-ld
37 git commit -m "add afs-ld submodule"
38 ```
39 Confirm `.gitmodules` gained the stanza:
40 ```
41 [submodule "afs-ld"]
42 path = afs-ld
43 url = git@github.com:FortranGoingOnForty/afs-ld.git
44 ```
45 7. **Verify** with `git submodule status` — afs-as and afs-ld both listed, each pinned to a commit hash.
46
47 Note: the old `git rm --cached` commit stays in armfortas history. Rewriting history to erase it is more destructive than it's worth for a single `.gitignore` file. The file at HEAD is gone; that is sufficient.
48
49 ### 2. Crate wiring
50
51 - Root `Cargo.toml` adds `"afs-ld"` to `[workspace] members` alongside `afs-as`.
52 - `afs-ld/Cargo.toml`: binary + library, zero external dependencies, `edition = "2021"`. Mirror `afs-as/Cargo.toml` — same `keywords`, `categories`, `license = "GPL-3.0-only"`, adjusted `description` and `repository`.
53 - `afs-ld/src/lib.rs`: public re-exports of `Linker`, `LinkOptions`, `OutputKind` (types are stubs this sprint).
54 - `afs-ld/src/main.rs`: CLI that prints usage and exits 0 when run with no args; forwards real args to `Linker::run` (which errors with "not yet implemented" this sprint).
55
56 ### 3. CLAUDE.md and README.md
57
58 - `afs-ld/CLAUDE.md`: mirror `afs-as/CLAUDE.md`, replacing assembler-isms with linker-isms. Non-negotiable rules: Rust std only, exhaustive matching, caret diagnostics, per-chunk commits, no co-authors, no sprint-number references in commits.
59 - `afs-ld/README.md`: one-page intro, supported CLI subset at current state (nothing yet — say so), build/test commands.
60 - `afs-ld/.gitignore`: `target/`, `.fackr/`, no `.docs/` since those files live in the repo. This one is intentionally tracked because it lives inside afs-ld's own repo now, not armfortas's.
61
62 ### 4. Reference clones
63
64 Add to parent `.refs/` (gitignored):
65
66 - `.refs/ld64/` — `git clone --depth 1 https://github.com/apple-oss-distributions/ld64.git`. Apple's last publicly released ld64. Authoritative for Apple-parity edge cases.
67 - `.refs/mold/` — `git clone --depth 1 https://github.com/rui314/mold.git`. Performance reference and a second Rust-adjacent angle on Mach-O.
68
69 `.refs/llvm/lld/MachO/` already exists from armfortas Sprint 0 — primary architectural reference.
70
71 ### 5. Differential harness
72
73 `afs-ld/tests/common/harness.rs`:
74
75 ```rust
76 pub struct LinkCase {
77 pub name: &'static str,
78 pub inputs: Vec<PathBuf>, // .o / .a / .tbd
79 pub args: Vec<String>, // -o, -e, -syslibroot, -l, ...
80 }
81
82 pub struct LinkOutputs {
83 pub ours: Vec<u8>, // afs-ld output
84 pub theirs: Vec<u8>, // system ld output
85 }
86
87 pub fn link_both(case: &LinkCase) -> LinkOutputs;
88 pub fn diff_macho(ours: &[u8], theirs: &[u8]) -> DiffReport;
89 ```
90
91 `DiffReport` categorizes byte differences as `Tolerated` (UUID, timestamp, temp-path hashes) or `Critical` (anything else). Critical diffs fail the test.
92
93 Closeout note: the current Sprint 0 surface is intentionally synthetic. `diff_macho` exists and is tested, but `link_both` remains a placeholder until afs-ld can emit real linked output. That means the current harness validates diff categorization logic, not end-to-end linker parity yet.
94
95 ### 6. Skeleton CLI and first failing test
96
97 - `afs-ld/src/args.rs`: hand-rolled argv parser stub that recognizes `-o`, `-e`, `-arch`, and positional inputs. Unknown flags error loudly with a hint.
98 - `afs-ld/tests/reader_empty.rs`: attempts to link `0 inputs → empty output`, expects the diagnostic `"afs-ld: error: no input files"`. Passes today by producing that exact string.
99 - `afs-ld/tests/diff_harness_sanity.rs`: exercises the diff surface against two identical synthetic byte slices and expects zero diffs. Passes.
100 - `afs-ld/tests/diff_harness_finds_critical.rs`: feeds the harness two synthetic binaries that differ in a non-tolerated byte range and asserts the harness reports `Critical`. Passes.
101
102 ## Testing Strategy
103
104 - `cargo build -p afs-ld` compiles from a fresh clone of the parent with `git submodule update --init --recursive`.
105 - `cargo test -p afs-ld` runs harness-sanity, critical-detection, and empty-input tests.
106 - `cargo clippy -p afs-ld -- -D warnings` clean.
107 - Manual verification of submodule state:
108 - `git ls-files | grep afs-ld` in armfortas prints only the `.gitmodules` entry (and nothing under `afs-ld/`).
109 - `git submodule status` shows both afs-as and afs-ld with valid commit hashes.
110 - `git submodule update --init --recursive` on a fresh armfortas clone populates afs-ld correctly.
111
112 ## Definition of Done
113
114 - The accidentally-tracked `afs-ld/.gitignore` is removed from armfortas's index at HEAD.
115 - afs-ld exists as a standalone GitHub repo under `FortranGoingOnForty`.
116 - afs-ld is wired into armfortas as a Git submodule, visible in `.gitmodules` and `git submodule status`.
117 - `armfortas/Cargo.toml` lists `afs-ld` in `[workspace] members`.
118 - `afs-ld/CLAUDE.md`, `README.md`, `Cargo.toml`, `src/lib.rs`, `src/main.rs`, `src/args.rs` all committed in the new repo.
119 - `.refs/ld64/` and `.refs/mold/` cloned.
120 - Differential harness substrate runs, correctly reports zero diffs on identical byte slices, correctly reports critical diffs on intentionally-different byte slices.
121 - `cargo test --workspace` green.