Skip to content

feat(xpkg): exports schema + predicate-driven elfpatch#9

Merged
Sunrisepeak merged 2 commits intomainfrom
feat/exports-schema
May 2, 2026
Merged

feat(xpkg): exports schema + predicate-driven elfpatch#9
Sunrisepeak merged 2 commits intomainfrom
feat/exports-schema

Conversation

@Sunrisepeak
Copy link
Copy Markdown
Member

Summary

Adds xpm.<platform>.exports.runtime.{loader, libdirs, abi} schema field and rewrites elfpatch.lua to be predicate-driven post-install instead of relying on consumers to call elfpatch.auto({...}) in install hooks.

Detailed design + rationale: openxlings/xlings docs/plans/2026-05-02-elfpatch-exports-design.md (incoming PR).

Schema additions

xpm = {
    linux = {
        deps = { runtime = {...}, build = {...} },
        exports = {                                       -- new field
            runtime = {
                loader  = "lib64/ld-linux-x86-64.so.2",  -- libc family
                libdirs = { "lib64", "lib" },            -- non-default only
                abi     = "linux-x86_64-glibc",          -- multi-libc only
            },
        },
        ...
    },
}

All sub-fields optional. Providers only declare non-default behavior.

elfpatch.lua

New consumer-facing API:

  • elfpatch.set(opts) — override predicate-driven auto with explicit params
  • elfpatch.skip() — opt out of auto-patch entirely

Old elfpatch.auto({...}) retained as deprecation alias mapping to elfpatch.set({...}). Drop after 2026-11. Debug-level deprecation log on first call.

_apply() decision tree:

  1. user_skip → return
  2. user_override → use hook-given opts
  3. self_exports.loader set → patch with own loader (glibc self-patch)
  4. exactly 1 runtime-dep with exports.loader → use it
  5. ≥ 2 runtime-deps with exports.loader → fail-fast, require interp_from
  6. otherwise → no-op

Plus: e_machine cross-arch protection, static-binary skip, runtime_deps_list (not union) for RPATH closure → fixes 0.0.32 build_dep pollution.

_RUNTIME extensions

  • runtime_deps_list / build_deps_list (split, post-#249)
  • deps_exports lookup { [dep_spec] = { loader, libdirs, abi } }
  • self_exports

Test plan

  • LoaderTest: 11/11 pass (3 new fixtures: full / loader-only / no-exports)
  • After merge: tag v0.0.33; downstream registers in mcpplibs-index; xlings PR bumps add_requires

Cross-repo

This PR alone is non-breaking — no consumer reads the new field yet, deprecation alias preserves old hook API. Real consumer impact lands after:

  1. Merge + tag v0.0.33
  2. mcpplibs-index registers 0.0.33
  3. openxlings/xlings PR bumps add_requires (drafted)
  4. d2learn/xim-pkgindex glibc/binutils pilot (drafted)

Add `xpm.<platform>.exports.runtime.{loader, libdirs, abi}` schema field,
parsed by xpkg-loader and surfaced on PlatformMatrix as a per-platform
ExportsBlock. Schema is the only new metadata field; all sub-fields
optional (providers only declare non-default behavior).

Extend xpkg-executor's _RUNTIME injection with:
  * runtime_deps_list / build_deps_list  — split form (post-#249)
  * deps_exports                          — pre-resolved per-runtime-dep
                                            { loader, libdirs, abi }
  * self_exports                          — same shape, for the current pkg

Rewrite elfpatch.lua's auto path to be predicate-driven:
  1. user_skip      → return
  2. user_override  → use elfpatch.set({...}) opts
  3. self_exports   → patch with own loader (handles glibc self-patch)
  4. runtime deps   → unique loader-provider wins; ≥2 require interp_from
  5. otherwise      → no-op (consumer uses host libc / no ELF / static)

New API: M.set(opts) and M.skip(); old M.auto kept as deprecation alias
mapped onto M.set with debug log (silent in normal mode, only verbose
users see). M.apply_auto retained as a back-compat name for xlings's
apply_elfpatch_auto bridge — internally calls the new M._apply.

Also adds e_machine cross-arch protection and a static-binary skip path
in _collect_targets / _has_pt_interp helpers.

Tests: 11/11 LoaderTest pass (incl. 3 new fixtures for full / loader-only /
no-exports parsing edge cases).
Previous deprecation alias mapped legacy elfpatch.auto({...}) onto the
new elfpatch.set({...}) API, which broke pre-existing consumers that
relied on auto({enable=true}) defaulting to loader='subos' (system
detection). After the remap, set({}) with no interpreter became a
no-op in _apply, breaking 3 ExecutorTest cases.

Fix: legacy auto() now sets _RUNTIME.elfpatch_legacy_* flags only.
M.apply_auto routes between three paths:
  1. user_skip            → return
  2. user_override (set)  → new predicate-aware path
  3. legacy_auto (auto)   → old loader='subos' default path
  4. neither              → predicate-driven default

Both old and new APIs coexist for the half-year transition window
without one mangling the other's semantics. ExecutorTest 22/22 pass
again.
@Sunrisepeak Sunrisepeak merged commit 3079ff2 into main May 2, 2026
3 checks passed
@Sunrisepeak Sunrisepeak deleted the feat/exports-schema branch May 2, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant