Skip to content

Commit 1d610bf

Browse files
committed
pack.binfmt: a Mach-O reader for needed_names (#630 §4)
`needed_names` returned "not implemented" for Mach-O; `elf_needed` and `pe_needed` already read a dependency list without executing anything. Mach-O gets the same treatment: - `detail::macho_thin_needed` walks a thin object's load commands: `LC_LOAD_DYLIB`, `LC_LOAD_WEAK_DYLIB`, `LC_REEXPORT_DYLIB` and `LC_LOAD_UPWARD_DYLIB` contribute names, `LC_RPATH` contributes search entries, in load-command order. Both widths (`mach_header`/ `mach_header_64`) and both endiannesses (the CIGAM magics) are handled. - `detail::macho_needed` / the exported `macho_needed(path, arch)` add the FAT (universal) case: a slice is selected by `cputype` against the caller's `arch` (mcpp's canonical spelling), defaulting to the first slice when none is given -- which is what `needed_names` does, since it has no triple to pass. `FAT_MAGIC` and `FAT_MAGIC_64` are both handled; FAT headers are always big-endian on disk regardless of a slice's own endianness. - `needed_names` completes for Mach-O by delegating to `macho_needed`, and `is_system_lib` gains a Mach-O row: `/usr/lib/` and `/System/Library/` are the operating system's, case-sensitively (`@rpath/...` is a search, not a root, so it is never system). - `resolve_macho_names` is a pure resolver: `@executable_path`, `@loader_path` and `@rpath/<x>` (tried against every rpath entry, in order, with the same two substitutions) resolve to the first path that exists; a name that resolves nowhere is reported as `unresolved`, not dropped. NOT wired into `pack::run`'s Mach-O closure step. Bundling a resolved dylib beside the program and rewriting its `LC_RPATH` needs a load-command editor -- a load command has no free space to grow a longer path into -- and the record (§4.2) designs that editor after resolution is measured on a real macOS build, not before. `pack::run`'s Mach-O branch still reports "not-walked" (see the sibling #630 §3 commit); this reader and resolver are exposed for that measurement and for whoever wires them in next. Tests: unit fixtures built in-process (a tiny Mach-O/fat-Mach-O byte writer, no external tools, same shape the ELF/PE fixtures already use) -- a thin arm64 object with two `LC_LOAD_DYLIB`, one `LC_LOAD_WEAK_DYLIB` and two `LC_RPATH`; a fat x86_64+arm64 object whose slices carry different names, asserting the wrong slice is never read; a big-endian-magic thin object; the wrong-arch request on a thin file (ignored, as documented); `is_system_lib`'s Mach-O row; and the resolver's rpath-order and unresolved-reporting behavior. Docs: docs/10-pack-and-release.md and the zh mirror note that the closure is read now, and narrow the "planned" gap to bundling + the `LC_RPATH` rewrite.
1 parent ed28442 commit 1d610bf

4 files changed

Lines changed: 633 additions & 17 deletions

File tree

docs/10-pack-and-release.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,19 @@ Linux either.
482482
A `kind = "lib"` / `"shared"` target packs normally on macOS — a library package
483483
never runs the artifact. This restriction is only for programs.
484484

485+
The dependency closure can now be **read** rather than run — `mcpp.pack.binfmt`
486+
walks a Mach-O's load commands (`LC_LOAD_DYLIB` and its weak/re-export/upward
487+
siblings for names, `LC_RPATH` for search entries) the same way it already
488+
reads a PE's import table, and resolves `@executable_path`, `@loader_path` and
489+
`@rpath` the way `dyld` would, without loading anything. `mcpp pack` does not
490+
call it yet: bundling a resolved dylib beside the program needs an editor for
491+
`LC_RPATH` (a load command has no free space to grow into), and that editor is
492+
designed once resolution is measured on a real macOS build, not before. Until
493+
then a Mach-O program still stages without its closure — see [Producing a
494+
distributable](30-build-mcpp.md#producing-a-distributable-pack_format--stage_dir-20269111)
495+
for what a dispatched format (`.app`, `.ipa`) can already do with a tree that
496+
has a program and no closure.
497+
485498
## Configuration
486499

487500
Packaging behavior is configured via the `[pack]` section in `mcpp.toml`. The
@@ -513,11 +526,12 @@ The `static` mode additionally requires a musl toolchain configured under
513526

514527
## Planned Support
515528

516-
macOS **program** bundling (the Mach-O dependency closure, via `otool -L` /
517-
`LC_LOAD_DYLIB`, and `install_name_tool` for relocation) is still on the
518-
roadmap; until it lands `mcpp pack <program>` refuses on that format rather than
519-
producing something that only looks like a bundle. Windows DLL bundling beyond
520-
the current `.zip` is also on the roadmap.
529+
macOS **program** bundling is still on the roadmap. The closure is read now
530+
(`mcpp.pack.binfmt`'s Mach-O load-command walk — no `otool` needed), but
531+
bundling a resolved dylib beside the program and rewriting `LC_RPATH` for it
532+
is not; until that lands `mcpp pack <program>` still refuses on that format
533+
rather than producing something that only looks like a bundle. Windows DLL
534+
bundling beyond the current `.zip` is also on the roadmap.
521535

522536
Distribution formats such as `.deb`, `.rpm`, AppImage and `.msi` are **not** on
523537
this list, and that is a decision rather than an omission: they live in

docs/zh/10-pack-and-release.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,17 @@ mcpp pack --target x86_64-windows-gnu # 在 Linux 宿主上
408408
`kind = "lib"` / `"shared"` 目标在 macOS 上照常打包 —— 库打包从不运行产物。
409409
这条限制只针对程序。
410410

411+
依赖闭包现在可以被**读出来**而不必运行了 —— `mcpp.pack.binfmt` 走一遍 Mach-O 的
412+
load command(`LC_LOAD_DYLIB` 及其 weak/re-export/upward 三个变体给出名字,
413+
`LC_RPATH` 给出搜索项),读法与它读 PE 导入表一样;并按 `dyld` 的规则解析
414+
`@executable_path``@loader_path``@rpath`,全程不加载任何东西。`mcpp pack`
415+
还没有调用它:把解析到的 dylib 拷到程序旁边、再重写它的 `LC_RPATH`,需要一个
416+
load command 编辑器(一条 load command 里没有空间可以塞进更长的路径),这个编辑器
417+
要等在真实的 macOS 构建上量过解析结果之后才设计,而不是先设计。在那之前,Mach-O
418+
程序仍然是"暂存但没有闭包"——一个被分发出去的格式(`.app``.ipa`)已经能拿这样一棵
419+
"有程序、没闭包"的树做什么,见
420+
[产出可分发物](30-build-mcpp.md#产出可分发物pack_format-与-stage_dir20269111)
421+
411422
## 配置项
412423

413424
打包行为通过 `mcpp.toml` 中的 `[pack]` 节配置,常用字段如下:
@@ -435,10 +446,11 @@ force_bundle = ["libfoo.so"] # 即使命中 PEP 600 名单也强制打包
435446

436447
## 待支持
437448

438-
macOS **程序** bundling(Mach-O 依赖闭包,走 `otool -L` / `LC_LOAD_DYLIB`,
439-
重定位走 `install_name_tool`)仍在规划中;在它落地之前,`mcpp pack <程序>`
440-
会在该格式上拒绝,而不是产出一个只是看起来像 bundle 的东西。当前 `.zip`
441-
之外的 Windows DLL 分发,同样在规划中。
449+
macOS **程序** bundling 仍在规划中。闭包现在已经能读出来了(`mcpp.pack.binfmt`
450+
走一遍 Mach-O 的 load command,不需要 `otool`),但把解析到的 dylib 拷到程序旁边、
451+
再重写它的 `LC_RPATH` 还没做;在这落地之前,`mcpp pack <程序>` 仍会在该格式上拒绝,
452+
而不是产出一个只是看起来像 bundle 的东西。当前 `.zip` 之外的 Windows DLL 分发,
453+
同样在规划中。
442454

443455
`.deb``.rpm`、AppImage、`.msi` 这些分发格式**不在**这份清单上,而这是一个决定而不是
444456
一处遗漏:它们住在包里,经 `--format <name>` 到达用户,理由见上一节。`[pack]` 的内建

0 commit comments

Comments
 (0)