markdown · 3671 bytes Raw Blame History

Sprint 6: TAPI TBD Text Stubs

Prerequisites

Sprint 5 — binary dylib reader works.

Goals

Read .tbd files (TAPI text dylib stubs). On modern SDKs libSystem, libc++, and CoreFoundation ship only as .tbd — linking without this sprint means no system libraries, full stop.

Deliverables

1. Minimal YAML subset

TBD is YAML with a well-defined schema. We implement only the subset TAPI emits, not a general YAML parser:

  • Flow scalars (plain, single-quoted, double-quoted).
  • Flow sequences: [ a, b, c ].
  • Block sequences: - item.
  • Block mappings: key: value.
  • Multi-document files with --- / ....
  • Tags: !tapi-tbd.
  • Version directives: %YAML 1.2.

No anchors, no aliases, no complex types, no folded scalars. If a real .tbd in the wild uses features outside the subset, the parser fails loudly with line/column.

2. TBD schema

afs-ld/src/macho/tbd.rs:

pub struct Tbd {
    pub tbd_version: u32,      // 3 or 4
    pub targets: Vec<Target>,  // arch + platform
    pub install_name: String,
    pub current_version: Option<String>,
    pub compatibility_version: Option<String>,
    pub parent_umbrella: Vec<Scoped<String>>,
    pub allowable_clients: Vec<Scoped<String>>,
    pub reexported_libraries: Vec<Scoped<String>>,
    pub exports: Vec<Scoped<Exports>>,
    pub reexports: Vec<Scoped<Exports>>,
}

pub struct Target { pub arch: Arch, pub platform: Platform }
pub struct Scoped<T> { pub targets: Vec<Target>, pub value: T }
pub struct Exports {
    pub symbols: Vec<String>,
    pub weak_symbols: Vec<String>,
    pub thread_local_symbols: Vec<String>,
    pub objc_classes: Vec<String>,
    pub objc_eh_types: Vec<String>,
    pub objc_ivars: Vec<String>,
}

v3 and v4 both supported; v4 is what modern Xcode ships.

3. Materialize into DylibFile

Tbd::into_dylib_file(tbd: Tbd, for_target: Target) -> DylibFile. Filters scoped entries to only those matching arm64 / macos. Produces the same DylibFile surface Sprint 5 produces, so downstream code doesn't care about source format.

4. SDK search implementation

Integrate with -syslibroot. Search order for -l<name>:

  1. ${SDK}/usr/lib/lib<name>.tbd
  2. ${SDK}/usr/lib/lib<name>.dylib
  3. ${SDK}/usr/local/lib/lib<name>.tbd
  4. ${SDK}/usr/local/lib/lib<name>.dylib
  5. -L<dir> entries in order, same four suffixes.

For frameworks (-framework Foo): ${SDK}/System/Library/Frameworks/Foo.framework/Foo.{tbd,dylib}.

5. Platform/arch filtering

Target { arch: Arm64, platform: MacOS } is what armfortas cares about. If the TBD has no matching target, produce a clear diagnostic: "<path> does not export for arm64-macos".

Testing Strategy

  • Fixtures: copies of ${SDK}/usr/lib/libSystem.tbd, libc++.tbd, libobjc.tbd checked into tests/corpus/tbd/ (small, just headers of exported symbols — confirm they're not under a license that forbids redistribution; if so, generate equivalent fixtures).
  • Parse libSystem.tbd, assert that _dyld_stub_binder, _malloc, _free, _printf are all in exports.
  • Verify DylibFile produced is byte-level equivalent (in the fields we populate) to one produced by loading an actual libSystem.dylib from an older SDK.
  • Malformed YAML: missing install_name, tabs in indentation, unterminated quoted scalar — each with a precise diagnostic.

Definition of Done

  • Can read a modern Xcode libSystem.tbd and enumerate its exports.
  • SDK + -l + -framework resolution picks the right file on a real toolchain.
  • Differential test: hello-world link with libSystem.tbd produces the same bind entries as with a binary dylib (on older SDKs where both exist).
View source
1 # Sprint 6: TAPI TBD Text Stubs
2
3 ## Prerequisites
4 Sprint 5 — binary dylib reader works.
5
6 ## Goals
7 Read `.tbd` files (TAPI text dylib stubs). On modern SDKs `libSystem`, `libc++`, and `CoreFoundation` ship only as `.tbd` — linking without this sprint means no system libraries, full stop.
8
9 ## Deliverables
10
11 ### 1. Minimal YAML subset
12 TBD is YAML with a well-defined schema. We implement only the subset TAPI emits, not a general YAML parser:
13
14 - Flow scalars (plain, single-quoted, double-quoted).
15 - Flow sequences: `[ a, b, c ]`.
16 - Block sequences: `- item`.
17 - Block mappings: `key: value`.
18 - Multi-document files with `---` / `...`.
19 - Tags: `!tapi-tbd`.
20 - Version directives: `%YAML 1.2`.
21
22 No anchors, no aliases, no complex types, no folded scalars. If a real `.tbd` in the wild uses features outside the subset, the parser fails loudly with line/column.
23
24 ### 2. TBD schema
25 `afs-ld/src/macho/tbd.rs`:
26
27 ```rust
28 pub struct Tbd {
29 pub tbd_version: u32, // 3 or 4
30 pub targets: Vec<Target>, // arch + platform
31 pub install_name: String,
32 pub current_version: Option<String>,
33 pub compatibility_version: Option<String>,
34 pub parent_umbrella: Vec<Scoped<String>>,
35 pub allowable_clients: Vec<Scoped<String>>,
36 pub reexported_libraries: Vec<Scoped<String>>,
37 pub exports: Vec<Scoped<Exports>>,
38 pub reexports: Vec<Scoped<Exports>>,
39 }
40
41 pub struct Target { pub arch: Arch, pub platform: Platform }
42 pub struct Scoped<T> { pub targets: Vec<Target>, pub value: T }
43 pub struct Exports {
44 pub symbols: Vec<String>,
45 pub weak_symbols: Vec<String>,
46 pub thread_local_symbols: Vec<String>,
47 pub objc_classes: Vec<String>,
48 pub objc_eh_types: Vec<String>,
49 pub objc_ivars: Vec<String>,
50 }
51 ```
52
53 v3 and v4 both supported; v4 is what modern Xcode ships.
54
55 ### 3. Materialize into DylibFile
56 `Tbd::into_dylib_file(tbd: Tbd, for_target: Target) -> DylibFile`. Filters scoped entries to only those matching `arm64 / macos`. Produces the same `DylibFile` surface Sprint 5 produces, so downstream code doesn't care about source format.
57
58 ### 4. SDK search implementation
59 Integrate with `-syslibroot`. Search order for `-l<name>`:
60 1. `${SDK}/usr/lib/lib<name>.tbd`
61 2. `${SDK}/usr/lib/lib<name>.dylib`
62 3. `${SDK}/usr/local/lib/lib<name>.tbd`
63 4. `${SDK}/usr/local/lib/lib<name>.dylib`
64 5. `-L<dir>` entries in order, same four suffixes.
65
66 For frameworks (`-framework Foo`): `${SDK}/System/Library/Frameworks/Foo.framework/Foo.{tbd,dylib}`.
67
68 ### 5. Platform/arch filtering
69 `Target { arch: Arm64, platform: MacOS }` is what armfortas cares about. If the TBD has no matching target, produce a clear diagnostic: "`<path>` does not export for arm64-macos".
70
71 ## Testing Strategy
72 - Fixtures: copies of `${SDK}/usr/lib/libSystem.tbd`, `libc++.tbd`, `libobjc.tbd` checked into `tests/corpus/tbd/` (small, just headers of exported symbols — confirm they're not under a license that forbids redistribution; if so, generate equivalent fixtures).
73 - Parse `libSystem.tbd`, assert that `_dyld_stub_binder`, `_malloc`, `_free`, `_printf` are all in exports.
74 - Verify `DylibFile` produced is byte-level equivalent (in the fields we populate) to one produced by loading an actual `libSystem.dylib` from an older SDK.
75 - Malformed YAML: missing `install_name`, tabs in indentation, unterminated quoted scalar — each with a precise diagnostic.
76
77 ## Definition of Done
78 - Can read a modern Xcode `libSystem.tbd` and enumerate its exports.
79 - SDK + `-l` + `-framework` resolution picks the right file on a real toolchain.
80 - Differential test: hello-world link with `libSystem.tbd` produces the same bind entries as with a binary dylib (on older SDKs where both exist).