moonar

    Create, edit and compare ar, GNU thin and Microsoft COFF libraries in MoonBit

    ar
    archive
    static-library
    binary
    diff
    Download zip
    Author
    Version
    0.7.2
    License
    MIT
    Last updated
    7 days ago
    Downloads
    93

    #MoonAr

    CI

    MoonAr 是用 MoonBit 编写的通用 Unix ar 归档库与命令行工具。它覆盖普通归档和 GNU thin 归档的读取、创建、编辑、转换、符号索引维护与比较,可用于构建静态库 .a、处理构建产物, 以及读取 .deb 的外层容器。也支持 Microsoft COFF .lib 静态库与导入库。 核心库直接处理字节,不依赖外部 ar 程序。

    0.7.2 使用 MoonBit 0.10.16 工具链,提供三平台 39 组性能基准和 macOS 本机消费者。 Clang 的 full LTO、ThinLTO 对象可参与创建、编辑、 thin 归档和格式转换,支持原始 bitcode 与 Mach-O 包装。保留 COFF 双 linker-member、 bigobj、短导入对象、ELF、Mach-O 以及 GNU 直接/嵌套 thin 的完整工作流。

    #创建并使用静态库

    安装 MoonBit 工具链,要求 moonc 0.10.16 或更高版本, native 后端需要 C 编译器。可用 moon version --all 查看编译器版本;复现 CI 时使用 编译器与 core 0.10.16+b1a0e846e。自动检查入口为 python tools/verify_toolchain.py(Python 3.9+)。

    git clone https://github.com/xmyd0915/moonar.git cd moonar moon update moon build --target native --deny-warn

    生成的命令位于 _build/native/debug/build/cmd/main/main.exe,Linux 和 macOS 也使用此文件名。 以下用 moonar 指代该命令。将两个已有对象创建为带索引的静态库:

    cc -c first.c -o first.o cc -c second.c -o second.o moonar create libsample.a first.o second.o moonar list libsample.a cc main.c libsample.a -o demo

    默认自动发现对象中可索引的定义,并使用确定性元数据。修改其中一个对象后:

    cc -c second.c -o second.o moonar replace libsample.a 1 second.o moonar move libsample.a 1 0 moonar extract libsample.a 0 copied.o

    位置是 list 给出的普通成员零基序号,因此多个同名对象也能分别编辑。 list --json 使用相同序号;inspect 则展示包含名称表和符号表的物理记录。

    无需准备 C 文件即可运行仓库中的 读取与比较示例。完整的真实对象 创建、编辑和链接流程由 verify_cli.py 自动执行。

    #GNU thin 工作流

    moonar create libthin.a first.o second.o --thin moonar replace libthin.a 1 second.o moonar convert libthin.a libpacked.a moonar create libnested.a libpacked.a --thin

    thin 保存外部成员引用,普通归档保存成员数据。编辑 thin 会保留 thin 存储方式; convert 将成员加载后写成普通归档。--output 写到另一个目录时会重新计算引用路径。 thin 创建或追加时,普通归档输入展开为指向其成员头部的 archive-offset 引用;thin 输入展开为 外部引用。读取嵌套引用会缓存共享容器,并检查目标头部、声明长度、循环引用和累计预算。 物化与逻辑比较使用被引用成员的实际名称;直接文件引用保留路径名称。 默认加载范围为归档所在目录及其子目录,真实路径检查会处理符号链接与 Windows 目录联接。 构建目录确需引用父目录时显式加 --allow-parent-paths。

    check 默认校验容器和索引;加 --external 会加载全部 thin 成员,核对路径、实际长度和 累计预算。删除已丢失的 thin 成员后仍可重新建立有效索引。

    moonar check libthin.a --external moonar diff before/libthin.a after/libthin.a --logical --json moonar diff libthin.a libpacked.a --logical

    逻辑比较直接支持 thin 与普通归档的任意组合,比较实际加载的内容、元数据、顺序及原索引映射。 bytes_equal 始终比较容器字节,因此两个 thin 容器可以字节相同、外部内容不同。 差异中的成员位置仍对应原归档的物理记录,无需先写出临时归档。

    #命令与格式

    命令功能
    list FILE / inspect FILE / check FILE普通成员列表、物理记录、结构校验
    create OUT [FILES...]创建普通归档;--thin 创建 GNU thin
    add ARCHIVE FILES... [--at N]追加或从指定位置插入
    replace ARCHIVE INDEX FILE替换一个成员;普通归档保留其原名称
    delete ARCHIVE INDEX...删除指定成员
    move ARCHIVE FROM TO移到最终位置,保留其他成员的相对顺序
    rename ARCHIVE INDEX NAME修改名称;thin 中修改的是外部引用
    extract ARCHIVE INDEX OUT将指定成员写入明确的输出路径
    convert ARCHIVE OUT转换普通归档布局,或将 thin 物化
    index ARCHIVE从对象重新发现符号并建立索引
    diff BEFORE AFTER [--logical]普通归档物理比较;普通/thin 归档的直接逻辑比较

    写出选项:

    • --format gnu|gnu64|bsd|bsd64|darwin|darwin64|coff,默认 gnu;thin 支持 GNU32/64。
    • --index auto|none,默认 auto。非对象数据可正常打包;已识别但不支持的对象格式 在自动模式下明确报错。none 跳过发现;Darwin 与 COFF 布局仍包含必要的空索引。
    • --output FILE 将编辑结果写到新路径;新输出存在时需要 --force。
    • --preserve-metadata 保留已有成员的元数据;新输入采用确定性元数据。
    • --big-endian-index 选择 BSD/Darwin 大端索引,默认小端。
    • --json 输出结构化结果;文件名以 - 开头时使用 --。

    文件修改在完成校验后写入同目录的独立短名临时文件,再原子发布,避免给长输出文件名追加后缀。 失败保留原文件;新建默认禁止覆盖, 原位编辑允许替换。输出不能覆盖提取源或 thin 依赖成员。native 和 Node.js CLI 提供文件 写入能力;核心库及不依赖文件系统的命令逻辑支持 native、JavaScript、wasm、wasm-gc。 Node.js CLI 使用系统原生真实路径解析,要求 Node.js 20.16+ 或 22.3+。

    文件系统路径按宿主规则处理:Windows 支持盘符根目录、反斜杠分隔及盘符相对路径; POSIX 文件名中的字面反斜杠会保留。归档成员的 rename 名称保持原始字节含义,thin 成员引用继续遵循可移植路径规则。JSON 中的输出路径保留调用时的参数拼写。

    查询成功退出 0,无效归档退出 1,参数或读取错误退出 2。编辑成功退出 0,失败退出 2。 diff 相同退出 0、不同退出 1、输入错误退出 2;逻辑比较还将配对歧义视为不同。 详细约定见 API 与 JSON。

    Windows 上使用 moonar create sample.lib first.obj second.obj --format coff 创建 Microsoft 静态库;编辑和重建 .lib 时同样指定 --format coff。bigobj 和短导入对象参与自动索引, COFF 两份 linker-member 会交叉校验并以第二份的排序映射公开。真实 MSVC 静态链接与 DLL 导入消费者见 Windows 验证脚本。

    #作为 MoonBit 库使用

    moon add xmyd0915/moonar@0.7.2

    在 moon.pkg 中导入 "xmyd0915/moonar" @ar:

    let plan = @ar.ArchiveBuilder::from_members([
    @ar.WriteMember::new(b"hello.txt", b"hello\n"),
    ]).unwrap()
    let bytes = plan.write(format=@ar.Gnu).unwrap()
    let archive = @ar.parse(bytes).unwrap()
    let data = archive.member_bytes(0).unwrap()

    对象归档使用 plan.discover_symbols() 获取最终成员映射,再传给 plan.write(symbols=Some(symbols))。thin 使用独立的 ThinArchiveBuilder 和 parse_thin; 加载器由调用方提供,解析不隐式访问磁盘。thin.snapshot(loader) 捕获已加载内容, 再用 snapshot.compare_logical(other) 与另一个 thin 快照或 ordinary.snapshot() 比较。库接口返回 Result,示例中的 unwrap() 适用于固定有效输入。详见 读写 API 和 生成接口。

    #格式范围与比较

    能力支持范围
    读取与写出GNU32/64、BSD32/64、Darwin32/64、Microsoft COFF 双索引;BSD/Darwin 支持大小端
    thinGNU 直接文件与嵌套 archive-offset 引用,GNU32/64 索引,创建、编辑、加载、重定位与物化
    自动符号发现ELF32/64、Mach-O32/64、标准 COFF、bigobj、短导入对象;带版本 3 摘要表的 LLVM bitcode
    成员原始名称字节、长名称、重名、空内容、二进制数据和元数据
    比较物理字节、普通成员内容、元数据、顺序、符号映射与重名配对歧义

    自动发现保留可索引的全局、弱定义、公共符号和绝对符号,排除未定义与局部符号。 GNU 保留名称可作为长名称普通成员写出;BSD/Darwin 会拒绝与索引记录冲突的名称。 Darwin 的对齐字节可能包含在成员声明大小内,提取时保留,与 LLVM 行为一致。

    diff --logical 可以识别 GNU 名称表与 BSD 内嵌名称等布局变化,分别报告内容、元数据、 顺序与符号映射。重名成员优先按相同内容配对,无法唯一识别时报告 ambiguities。 设计和案例见 比较说明。

    LLVM bitcode 使用内嵌的版本 3 符号摘要表,读取编译器生成的已修饰名称及全局、弱、公共和 别名定义;过滤局部、未定义和格式专用记录。支持模块组与包装偏移,并检查表范围、字符串、 分组和共享预算。LLVM IR 指令、优化与代码生成由链接工具链处理。

    明确的格式边界:缺少支持的摘要表的旧 bitcode、GCC slim LTO 插件解析、fat Mach-O 和 AIX big archive 不在 0.7.2 自动发现范围内。GCC slim 标记会明确报错;fat LTO 的原生符号 仍可使用。库调用方也可提供显式符号映射。 check 校验容器和索引结构,--external 另检查 thin 外部成员,不等价于对象 ABI 或链接结果验证。目录批量解压、.deb 内层 tar 解压、链接器实现和包签名校验不属于本库职责。

    #验证与实际消费者

    python tools/verify_toolchain.py moon fmt --check moon check --target all --deny-warn moon test --target all --deny-warn moon build --target native --deny-warn python tools/verify_cli.py --moonar _build/native/debug/build/cmd/main/main.exe --require-llvm python tools/verify_io.py --moonar _build/native/debug/build/cmd/main/main.exe python tools/verify_zlib.py --moonar _build/native/debug/build/cmd/main/main.exe python tools/verify_windows.py --moonar _build/native/debug/build/cmd/main/main.exe python tools/verify_lto.py --moonar _build/native/debug/build/cmd/main/main.exe python tools/verify_package.py python tools/benchmark.py

    GNU/LLVM 验证需要 GNU ar/nm、LLVM ar、C 编译器和 Python 3.9+;Windows 专项使用 MSVC。 macOS Apple Silicon 使用 Xcode 命令行工具运行 python tools/verify_macos.py --moonar _build/native/debug/build/cmd/main/main.exe: 对照 Apple libtool、ar 与 nm,覆盖四种 BSD/Darwin 布局的全部编辑命令、GNU32/64 thin 和嵌套 thin 物化,并实际执行 16 个链接消费者。保留元数据后核对与 Apple 参考库的完整逻辑比较。 嵌套 thin 与 GNU ar 对照,物化结果再由 LLVM 消费。真实项目验证固定使用 zlib 1.3.1:校验源码摘要,编译对象,用 MoonAr 分别创建普通及 thin 静态库,链接执行 压缩/解压往返,再替换对象并重新链接执行。源码在临时目录处理,不随 MoonAr 分发。 完整入口、预期结果及平台范围见 复现指南。 LTO 验证使用 Clang、LLD、LLVM ar,或 verify_lto.py --zig PATH:对照符号映射并完成 普通/thin 创建、编辑、物化与混合原生对象的实际链接运行;嵌套引用先物化再交给 LLD。 发行 ZIP 另通过独立 MoonBit 消费模块验证公开接口。 已发布版本可运行 python tools/verify_registry.py,从 MoonCakes 安装后执行同一组公共接口 消费者,并核对安装内容与本地发行 ZIP 的全部文件。详见发布包验证。

    Linux / Windows / macOS Apple Silicon CI 固定编译器与 core 0.10.16+b1a0e846e,先检查实际编译器版本,再运行四后端测试、真实文件操作和独立消费者检查。 GNU/LLVM、Python 与 C 编译器仅用于构建或验证;核心库只依赖 MoonBit core。

    性能基准覆盖 100/1000/10000 个成员、唯一名称与重名比较,以及深度 1/4/16 的嵌套 thin,共 39 组场景。 每组检查语义结果和缓存加载次数,报告操作耗时的中位数及波动、整个进程的峰值驻留内存。 运行参数、测量范围和报告入口见 性能基准。

    #资源与文档

    默认预算:64 MiB 输入/输出、100,000 条物理记录、64 MiB 展开成员名、1,000,000 个符号、 64 MiB 展开符号名。CLI 通过 --max-bytes、--max-members、--max-name-bytes、 --max-symbols、--max-symbol-name-bytes 调整。符号发现对扫描数量计费,重复引用按 展开长度计费。大小和偏移使用有符号 32 位整数。

    嵌套容器另用 NestedLimits 累计限制字节、记录、展开名称和符号;同路径容器每次批量操作 只加载解析一次。CLI 的 --max-archive-bytes 默认 64 MiB,--max-depth 默认 16、最大 64。 嵌套路径与所解析容器的成员名共用名称预算;COFF 两份索引均计入符号数及名称预算。 同一已解析来源路径在一次批量操作中只计费一次,重复经过该路径不额外消耗名称预算。

    thin 内容检查与快照对每个输入的全部外部成员累计使用字节预算,重复引用分别计费。 CLI 先读取完整文件再检查预算;比较、输出和对象发现需要额外内存。当前接口使用内存输入, 不提供流式读写。文件系统路径检查与打开之间存在并发替换窗口,调用方应控制构建目录的写入。

    项目使用 MIT 许可证。

    Archive

    An in-memory archive, including special records in physical order.

    Archive::byte_length

    fn Archive::byte_length(self : Archive) -> Int

    Archive::edit

    fn Archive::edit(self : Archive, drop_symbol_indexes? : Bool) -> Result[ArchiveBuilder, WriteError]

    Import ordinary members for editing. An existing symbol index must be explicitly discarded; rebuilding uses fresh mappings passed to write(). Ordinary-entry positions do not include name or symbol table records.

    Archive::get_member

    fn Archive::get_member(self : Archive, index : Int) -> Member?

    Return one immutable record, or None for an invalid index.

    Archive::member_bytes

    fn Archive::member_bytes(self : Archive, index : Int) -> Bytes?

    Return an owned copy of one record's payload, or None for an invalid index.

    Archive::member_count

    fn Archive::member_count(self : Archive) -> Int

    Number of physical records, including name and symbol tables.

    Archive::snapshot

    fn Archive::snapshot(self : Archive) -> ArchiveSnapshot

    Ordinary archives already contain every member and need no loader.

    Archive::symbol_indexes

    fn Archive::symbol_indexes(self : Archive) -> Array[SymbolIndex]

    Return copies so changing a returned array cannot alter validated references.

    Archive::to_json

    fn Archive::to_json(self : Archive) -> Json

    Schema version 1. Display names may be lossy; name_hex is the raw identity. Metadata numbers use decimal strings or null. Offsets and sizes are integers.

    Archive::to_repr

    ArchiveBuilder

    type ArchiveBuilder derive(
    Debug
    )

    An immutable editing plan. Operations return a new plan and preserve their input.

    ArchiveBuilder::append

    ArchiveBuilder::discover_symbols

    fn ArchiveBuilder::discover_symbols(self : ArchiveBuilder, max_symbols? : Int, max_symbol_name_bytes? : Int) -> Result[Array[WriteSymbol], WriteError]

    Discover fresh symbol targets after editing. Budgets are shared by all members. WR006 includes the failing member position and object error offset.

    ArchiveBuilder::from_members

    Validate a complete collection and copy its array once. Use this for bulk creation instead of repeatedly appending to an immutable plan.

    ArchiveBuilder::insert

    fn ArchiveBuilder::insert(self : ArchiveBuilder, index : Int, entry : WriteMember) -> Result[ArchiveBuilder, WriteError]

    Insert before a zero-based position. The final position appends an entry.

    ArchiveBuilder::member_count

    fn ArchiveBuilder::member_count(self : ArchiveBuilder) -> Int

    ArchiveBuilder::members

    Return an independent array; names and payloads are immutable byte strings.

    ArchiveBuilder::move_member

    fn ArchiveBuilder::move_member(self : ArchiveBuilder, from : Int, to : Int) -> Result[ArchiveBuilder, WriteError]

    Move a entry to its final zero-based position, preserving the order of others.

    ArchiveBuilder::new

    ArchiveBuilder::remove

    fn ArchiveBuilder::remove(self : ArchiveBuilder, index : Int) -> Result[ArchiveBuilder, WriteError]

    ArchiveBuilder::replace

    fn ArchiveBuilder::replace(self : ArchiveBuilder, index : Int, entry : WriteMember) -> Result[ArchiveBuilder, WriteError]

    ArchiveBuilder::write

    fn ArchiveBuilder::write(self : ArchiveBuilder, format? : WriteFormat, metadata? : WriteMetadata, symbols? : Array[WriteSymbol]?, index_little_endian? : Bool, max_bytes? : Int, max_members? : Int, max_name_bytes? : Int, max_symbols? : Int, max_symbol_name_bytes? : Int) -> Result[Bytes, WriteError]

    Write a regular archive after validating its complete layout and budgets. GNU uses a deduplicated long-name table; BSD uses embedded names. Symbol mappings refer to final ordinary-entry positions and retain their order. symbols=None omits the index, except Darwin always emits at least an empty index. BSD/Darwin indexes use the requested byte order; GNU is always big endian. Darwin aligns object data to eight bytes and includes trailing newline padding in the declared payload. Readers retain those bytes. Other formats preserve exact payload bytes. Deterministic Darwin timestamps distinguish repeated names. Limits count physical records, expanded names and final serialized bytes.

    ArchiveSnapshot

    type ArchiveSnapshot derive(
    Debug
    )

    An immutable comparison input with loaded member bytes. The original container bytes and physical record positions remain the source identity. A snapshot does not access external files after construction.

    ArchiveSnapshot::compare_logical

    fn ArchiveSnapshot::compare_logical(self : ArchiveSnapshot, other : ArchiveSnapshot) -> LogicalComparison

    Compare loaded contents, metadata, order and original symbol mappings. bytes_equal compares the original container bytes. For thin snapshots it can be true while content_equal is false because external contents have changed. Change and ambiguity indices refer to the original physical input records.

    ChangeKind

    pub(all) enum ChangeKind {
    Added
    Removed
    Modified
    } derive(Eq,
    Debug
    )

    ChangeKind::equal

    fn ChangeKind::equal(ChangeKind, ChangeKind) -> Bool

    ChangeKind::label

    fn ChangeKind::label(self : ChangeKind) -> String

    ChangeKind::not_equal

    fn ChangeKind::not_equal(x : ChangeKind, y : ChangeKind) -> Bool

    Comparison

    pub(all) struct Comparison {
    bytes_equal : Bool
    order_changed : Bool
    changes : Array[MemberChange]
    ambiguities : Array[MatchAmbiguity]
    } derive(Eq,
    Debug
    )

    bytes_equal includes every byte, even header spelling and alignment. order_changed compares only the relative order of matched records.

    Comparison::equal

    fn Comparison::equal(Comparison, Comparison) -> Bool

    Comparison::not_equal

    fn Comparison::not_equal(x : Comparison, y : Comparison) -> Bool

    Comparison::to_json

    fn Comparison::to_json(self : Comparison) -> Json

    LogicalComparison

    pub(all) struct LogicalComparison {
    bytes_equal : Bool
    content_equal : Bool
    metadata_changed : Bool
    order_changed : Bool
    symbols_equal : Bool
    symbol_index_presence_changed : Bool
    changes : Array[MemberChange]
    ambiguities : Array[MatchAmbiguity]
    } derive(Eq,
    Debug
    )

    Logical members exclude name/symbol table records. Payload bytes are exact; embedded Darwin padding is not guessed or removed. Symbol mappings preserve the order of targets for each symbol name.

    LogicalComparison::equal

    LogicalComparison::not_equal

    fn LogicalComparison::not_equal(x : LogicalComparison, y : LogicalComparison) -> Bool

    LogicalComparison::to_json

    MatchAmbiguity

    pub(all) struct MatchAmbiguity {
    name : Bytes
    member_kind : MemberKind
    before_indices : Array[Int]
    after_indices : Array[Int]
    } derive(Eq,
    Debug
    )

    Several member identities fit the observed data; pairing is deterministic.

    MatchAmbiguity::equal

    MatchAmbiguity::not_equal

    fn MatchAmbiguity::not_equal(x : MatchAmbiguity, y : MatchAmbiguity) -> Bool

    Member

    pub(all) struct Member {
    name : Bytes
    kind : MemberKind
    header_offset : Int
    data_offset : Int
    size : Int
    stored_size : Int
    metadata : Metadata
    } derive(Eq,
    Debug
    )

    A record in physical archive order. Names are bytes, not assumed to be UTF-8. size excludes a BSD embedded name; stored_size is the header's size field.

    Member::equal

    fn Member::equal(Member, Member) -> Bool

    Member::name_hex

    fn Member::name_hex(self : Member) -> String

    A lossless name suitable for JSON and comparison keys.

    Member::name_text

    fn Member::name_text(self : Member) -> String

    Lossy display only. Use name or name_hex for lossless identity.

    Member::not_equal

    fn Member::not_equal(x : Member, y : Member) -> Bool

    Member::to_repr

    MemberChange

    pub(all) struct MemberChange {
    change : ChangeKind
    name : Bytes
    member_kind : MemberKind
    occurrence : Int
    before_index : Int?
    after_index : Int?
    payload_changed : Bool
    metadata_changed : Bool
    } derive(Eq,
    Debug
    )

    Indices refer to physical records, including name and symbol tables. Occurrence is one-based among records with the same kind and raw name.

    MemberChange::equal

    MemberChange::not_equal

    fn MemberChange::not_equal(x : MemberChange, y : MemberChange) -> Bool

    MemberKind

    pub(all) enum MemberKind {
    Regular
    SymbolTable
    NameTable
    } derive(Eq,
    Debug
    )

    Structural roles of archive records.

    MemberKind::equal

    fn MemberKind::equal(MemberKind, MemberKind) -> Bool

    MemberKind::label

    fn MemberKind::label(self : MemberKind) -> String

    MemberKind::not_equal

    fn MemberKind::not_equal(x : MemberKind, y : MemberKind) -> Bool

    Metadata

    pub(all) struct Metadata {
    timestamp : Int64?
    uid : Int64?
    gid : Int64?
    mode : Int64?
    } derive(Eq,
    Debug
    )

    Blank numeric fields, used by special archive records, are represented by None.

    Metadata::equal

    fn Metadata::equal(Metadata, Metadata) -> Bool

    Metadata::not_equal

    fn Metadata::not_equal(x : Metadata, y : Metadata) -> Bool

    Metadata::to_repr

    NestedLimits

    pub(all) struct NestedLimits {
    max_bytes : Int
    max_members : Int
    max_name_bytes : Int
    max_symbols : Int
    max_symbol_name_bytes : Int
    max_depth : Int
    } derive(Eq,
    Debug
    )

    Aggregate limits for archives fetched while resolving GNU archive-offset references. They are separate from the selected external-payload byte limit.

    NestedLimits::equal

    NestedLimits::new

    fn NestedLimits::new(max_bytes? : Int, max_members? : Int, max_name_bytes? : Int, max_symbols? : Int, max_symbol_name_bytes? : Int, max_depth? : Int) -> NestedLimits

    NestedLimits::not_equal

    fn NestedLimits::not_equal(x : NestedLimits, y : NestedLimits) -> Bool

    ObjectError

    pub(all) struct ObjectError {
    code : String
    offset : Int
    message : String
    } derive(Eq,
    Debug
    )

    OB001: malformed object; OB002: unsupported object format or file kind; OB003: discovery budget exceeded. Offsets are relative to the object bytes.

    ObjectError::equal

    fn ObjectError::equal(ObjectError, ObjectError) -> Bool

    ObjectError::not_equal

    fn ObjectError::not_equal(x : ObjectError, y : ObjectError) -> Bool

    ParseError

    pub(all) struct ParseError {
    code : String
    offset : Int
    message : String
    } derive(Eq,
    Debug
    )

    A byte offset and stable code explain why parsing stopped.

    ParseError::equal

    fn ParseError::equal(ParseError, ParseError) -> Bool

    ParseError::not_equal

    fn ParseError::not_equal(x : ParseError, y : ParseError) -> Bool

    ParseError::to_json

    fn ParseError::to_json(self : ParseError) -> Json

    Symbol

    pub(all) struct Symbol {
    name : Bytes
    member_index : Int
    name_offset : Int
    offset_field_offset : Int
    } derive(Eq,
    Debug
    )

    A validated symbol-to-member reference. Offsets refer to the original input.

    Symbol::equal

    fn Symbol::equal(Symbol, Symbol) -> Bool

    Symbol::not_equal

    fn Symbol::not_equal(x : Symbol, y : Symbol) -> Bool

    Symbol::to_repr

    SymbolIndex

    pub(all) struct SymbolIndex {
    record_index : Int
    format : String
    symbols : Array[Symbol]
    } derive(Eq,
    Debug
    )

    Format is gnu32/gnu64 or bsd32/bsd64 with a -le, -be or -neutral suffix.

    SymbolIndex::equal

    fn SymbolIndex::equal(SymbolIndex, SymbolIndex) -> Bool

    SymbolIndex::not_equal

    fn SymbolIndex::not_equal(x : SymbolIndex, y : SymbolIndex) -> Bool

    ThinArchive

    type ThinArchive derive(
    Debug
    )

    GNU thin archive metadata and validated symbol references, without file I/O.

    ThinArchive::byte_length

    fn ThinArchive::byte_length(self : ThinArchive) -> Int

    ThinArchive::edit

    fn ThinArchive::edit(self : ThinArchive, drop_symbol_indexes? : Bool) -> Result[ThinArchiveBuilder, WriteError]

    Import external references without loading files. Rebuilt symbol mappings must be supplied after editing; dropping an existing index is explicit.

    ThinArchive::get_member

    fn ThinArchive::get_member(self : ThinArchive, index : Int) -> ThinMember?

    ThinArchive::inline_bytes

    fn ThinArchive::inline_bytes(self : ThinArchive, index : Int) -> Bytes?

    Only special records have inline bytes. Regular records and invalid indices return None; use load_member to obtain an external member's contents.

    ThinArchive::load_member

    fn ThinArchive::load_member(self : ThinArchive, index : Int, loader : (Bytes, Int) -> Result[Bytes, String], max_bytes? : Int, allow_parent_paths? : Bool, nested_limits? : NestedLimits) -> Result[Bytes, ParseError]

    Request an external member from a caller-supplied loader. The loader receives a portable relative path and the maximum expected byte count. It controls the base directory, symlink handling and actual I/O allocation. Declared size is checked before calling it; returned bytes must match that size and max_bytes.

    ThinArchive::materialize

    fn ThinArchive::materialize(self : ThinArchive, loader : (Bytes, Int) -> Result[Bytes, String], drop_symbol_indexes? : Bool, allow_parent_paths? : Bool, max_bytes? : Int, nested_limits? : NestedLimits) -> Result[ArchiveBuilder, ParseError]

    Load all external members into a regular-archive editing plan. max_bytes bounds total payload bytes. All paths and declared sizes are checked before the first loader call. Names, order and metadata are preserved. The loader owns base-directory resolution, symlink policy and I/O allocation.

    ThinArchive::member_count

    fn ThinArchive::member_count(self : ThinArchive) -> Int

    Count physical records, including inline symbol and name tables.

    ThinArchive::member_path

    fn ThinArchive::member_path(self : ThinArchive, index : Int, allow_parent_paths? : Bool) -> Result[Bytes, ParseError]

    Return a portable relative path, removing "." and repeated "/" components. Absolute, backslash, drive/stream and directory-ending paths fail with AR012. Parent components require allow_parent_paths=true and remain in the returned path so callers can resolve them relative to the archive directory.

    ThinArchive::member_sources

    fn ThinArchive::member_sources(self : ThinArchive, loader : (Bytes, Int) -> Result[Bytes, String], allow_parent_paths? : Bool, nested_limits? : NestedLimits) -> Result[Array[Bytes], ParseError]

    Return portable source paths reached by direct and archive-offset references. Archive containers are parsed and cached, while direct payload files are not opened. Useful for dependency manifests and output-alias protection.

    ThinArchive::resolve_member

    fn ThinArchive::resolve_member(self : ThinArchive, index : Int, loader : (Bytes, Int) -> Result[Bytes, String], max_bytes? : Int, allow_parent_paths? : Bool, nested_limits? : NestedLimits) -> Result[WriteMember, ParseError]

    Resolve one ordinary external member, retaining the outer thin metadata. Nested references return the referenced member's logical name and payload. A direct loader request carries the exact declared size; a container request carries the remaining archive-byte budget. The loader controls actual reads.

    ThinArchive::snapshot

    fn ThinArchive::snapshot(self : ThinArchive, loader : (Bytes, Int) -> Result[Bytes, String], max_bytes? : Int, allow_parent_paths? : Bool, nested_limits? : NestedLimits) -> Result[ArchiveSnapshot, ParseError]

    Load an immutable snapshot for logical comparison with another thin snapshot or an ordinary Archive.snapshot(). Index mappings are retained as parsed; this operation neither discovers symbols nor rewrites the input archive. max_bytes limits cumulative external payloads, separately from container size.

    ThinArchive::symbol_indexes

    fn ThinArchive::symbol_indexes(self : ThinArchive) -> Array[SymbolIndex]

    Return copies; symbol targets use the same physical indices as get_member.

    ThinArchive::to_json

    fn ThinArchive::to_json(self : ThinArchive) -> Json

    Thin member sizes describe external files; no inline data offset is exposed.

    ThinArchive::validate_members

    fn ThinArchive::validate_members(self : ThinArchive, loader : (Bytes, Int) -> Result[Bytes, String], max_bytes? : Int, allow_parent_paths? : Bool, nested_limits? : NestedLimits) -> Result[Int, ParseError]

    Validate every external member without retaining its contents. Returns the total loaded byte count. Repeated references each count toward max_bytes. Paths and cumulative declared sizes are checked before the first loader call.

    ThinArchiveBuilder

    type ThinArchiveBuilder derive(
    Debug
    )

    An immutable editing plan for GNU thin archives. Every position is an ordinary-member position, excluding the symbol and name tables.

    ThinArchiveBuilder::append

    ThinArchiveBuilder::from_members

    Validate and copy a collection once, preserving exact path bytes and order.

    ThinArchiveBuilder::insert

    ThinArchiveBuilder::member_count

    fn ThinArchiveBuilder::member_count(self : ThinArchiveBuilder) -> Int

    ThinArchiveBuilder::members

    ThinArchiveBuilder::move_member

    fn ThinArchiveBuilder::move_member(self : ThinArchiveBuilder, from : Int, to : Int) -> Result[ThinArchiveBuilder, WriteError]

    Move an entry to its final zero-based position.

    ThinArchiveBuilder::new

    ThinArchiveBuilder::remove

    ThinArchiveBuilder::replace

    ThinArchiveBuilder::write

    fn ThinArchiveBuilder::write(self : ThinArchiveBuilder, metadata? : WriteMetadata, symbols? : Array[WriteSymbol]?, symbol_width? : Int, max_bytes? : Int, max_members? : Int, max_name_bytes? : Int, max_symbols? : Int, max_symbol_name_bytes? : Int) -> Result[Bytes, WriteError]

    Write GNU thin bytes without reading or embedding any external file. All paths use a deduplicated GNU name table. symbols=None omits the index; Some([]) emits an empty index. symbol_width selects 32 or 64 bit GNU offsets. Symbol targets are final ordinary-member positions after editing. Byte limits cover serialized archive bytes, not the external payload sizes.

    ThinMember

    pub(all) struct ThinMember {
    name : Bytes
    kind : MemberKind
    header_offset : Int
    size : Int
    metadata : Metadata
    archive_member_offset : Int?
    } derive(Eq,
    Debug
    )

    A physical thin-archive record. Regular records refer to external files; their size is the declared external file size, not an inline payload length.

    ThinMember::equal

    fn ThinMember::equal(ThinMember, ThinMember) -> Bool

    ThinMember::name_hex

    fn ThinMember::name_hex(self : ThinMember) -> String

    ThinMember::name_text

    fn ThinMember::name_text(self : ThinMember) -> String

    ThinMember::not_equal

    fn ThinMember::not_equal(x : ThinMember, y : ThinMember) -> Bool

    ThinWriteMember

    pub(all) struct ThinWriteMember {
    name : Bytes
    size : Int
    metadata : Metadata
    archive_member_offset : Int?
    } derive(Eq,
    Debug
    )

    An external thin-archive member. The path is interpreted relative to the archive file; size describes the external file, which is never embedded.

    ThinWriteMember::equal

    ThinWriteMember::new

    fn ThinWriteMember::new(name : Bytes, size : Int, metadata? : Metadata, archive_member_offset? : Int?) -> ThinWriteMember

    ThinWriteMember::not_equal

    fn ThinWriteMember::not_equal(x : ThinWriteMember, y : ThinWriteMember) -> Bool

    WriteError

    pub(all) struct WriteError {
    code : String
    member_index : Int?
    message : String
    } derive(Eq,
    Debug
    )

    WR001: invalid entry/name/index; WR002: unrepresentable metadata; WR003: resource limit; WR004: existing index needs an explicit decision; WR005: invalid or unsupported symbol-index request.

    WriteError::equal

    fn WriteError::equal(WriteError, WriteError) -> Bool

    WriteError::not_equal

    fn WriteError::not_equal(x : WriteError, y : WriteError) -> Bool

    WriteFormat

    pub(all) enum WriteFormat {
    Gnu
    Gnu64
    Bsd
    Bsd64
    Darwin
    Darwin64
    Coff
    } derive(Eq,
    Debug
    )

    Archive layout and symbol-index width used when creating a regular archive.

    WriteFormat::equal

    fn WriteFormat::equal(WriteFormat, WriteFormat) -> Bool

    WriteFormat::not_equal

    fn WriteFormat::not_equal(x : WriteFormat, y : WriteFormat) -> Bool

    WriteMember

    pub(all) struct WriteMember {
    name : Bytes
    data : Bytes
    metadata : Metadata
    } derive(Eq,
    Debug
    )

    An ordinary entry to write. Names and payloads retain their exact bytes.

    WriteMember::equal

    fn WriteMember::equal(WriteMember, WriteMember) -> Bool

    WriteMember::new

    fn WriteMember::new(name : Bytes, data : Bytes, metadata? : Metadata) -> WriteMember

    WriteMember::not_equal

    fn WriteMember::not_equal(x : WriteMember, y : WriteMember) -> Bool

    WriteMetadata

    pub(all) enum WriteMetadata {
    Deterministic
    Preserve
    } derive(Eq,
    Debug
    )

    Deterministic writes use time/uid/gid zero and mode 0644. Darwin assigns increasing timestamps to repeated names, starting at zero for each name. Preserve writes the supplied metadata, including blank fields represented by None.

    WriteMetadata::equal

    WriteMetadata::not_equal

    fn WriteMetadata::not_equal(x : WriteMetadata, y : WriteMetadata) -> Bool

    WriteSymbol

    pub(all) struct WriteSymbol {
    name : Bytes
    member_index : Int
    } derive(Eq,
    Debug
    )

    A symbol target is a zero-based ordinary-entry index in the final builder. Supply mappings after all edits; mappings are never carried across edits.

    WriteSymbol::equal

    fn WriteSymbol::equal(WriteSymbol, WriteSymbol) -> Bool

    WriteSymbol::not_equal

    fn WriteSymbol::not_equal(x : WriteSymbol, y : WriteSymbol) -> Bool

    compare

    fn compare(before : Archive, after : Archive) -> Comparison

    Compare physical records by kind/name, matching identical payloads first.

    compare_logical

    fn compare_logical(before : Archive, after : Archive) -> LogicalComparison

    Compare regular-member bytes and validated symbol-to-member mappings across encodings. This does not assert executable or ABI equivalence.

    parse

    fn parse(input : Bytes, max_members? : Int, max_bytes? : Int, max_name_bytes? : Int, max_symbols? : Int, max_symbol_name_bytes? : Int) -> Result[Archive, ParseError]

    Read a regular ar archive. Limits include special records and the entire input. Input bytes are immutable. Errors carry offsets into that input.

    parse_thin

    fn parse_thin(input : Bytes, max_members? : Int, max_bytes? : Int, max_name_bytes? : Int, max_symbols? : Int, max_symbol_name_bytes? : Int) -> Result[ThinArchive, ParseError]

    Parse GNU thin metadata and GNU 32/64-bit symbol indexes without loading external files. Byte limits cover this input, not referenced external files. GNU archive-offset references are retained without I/O. BSD embedded names are not part of the GNU thin encoding and are rejected.

    symbols_from_object

    fn symbols_from_object(data : Bytes, max_symbols? : Int, max_symbol_name_bytes? : Int) -> Result[Array[Bytes], ObjectError]

    Discover archive-index names from relocatable ELF, Mach-O and COFF objects. ELF32/64 and Mach-O32/64 accept both byte orders. Defined external, weak, common and absolute symbols are included; undefined/local/debug symbols are omitted. Duplicate names within one object retain their first position. Standard little-endian COFF includes weak aliases and skips auxiliary records. Bigobj, short import objects and LLVM bitcode with an embedded version-3 symbol table are supported. Bitcode wrappers and module groups are decoded without constructing IR. Executable images and universal objects are rejected. GCC slim LTO requires explicit mappings or plugin symbol discovery. Ordinary data returns an empty array; recognized unsupported object formats and malformed supported objects return an error. max_symbols counts inspected symbol-table entries, including filtered entries. max_symbol_name_bytes counts expanded discovered names before deduplication.