Skip to content

feat(xpkg): publish 0.0.47 - #128

Merged
Sunrisepeak merged 1 commit into
mainfrom
feat/xpkg-0.0.47
Jul 26, 2026
Merged

feat(xpkg): publish 0.0.47#128
Sunrisepeak merged 1 commit into
mainfrom
feat/xpkg-0.0.47

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

libxpkg 0.0.47 —— 为 xlings 的 files 资产模型提供声明能力。

新增

src / dst type = "files" 的资产声明。两端都相对(src 相对 payload 根,dst 相对 subos 根)—— payload 跨 subos 共享且引用计数,绝对路径只对安装它的那个 subos 正确
args alias 分开的注入参数列表。此前唯一办法是拼进 alias 字符串,消费方按第一个空格切分,路径含空格即废
xvm.files{src,dst} 语法糖,从包名派生 target 名

上游:openxlings/libxpkg#31(4 个新测试,CI 绿)

产物

三个平台段各加一条,GLOBAL + CN 双 URL:

GLOBAL  github.com/openxlings/libxpkg/archive/refs/tags/0.0.47.tar.gz
CN      gitcode.com/mcpp-res/xpkg/releases/download/0.0.47/xpkg-0.0.47.tar.gz
sha256  fe879e8f52ea5a7f316ca54bca1fa393febb8e2a23a3de852b0c2be1918a0be9

两个 URL 的内容逐字节相同 —— 都下载下来比对过 sha256,与 0.0.46 的做法一致(0.0.46 的 CN/GLOBAL/索引记录三者 sha256 也完全相同)。

CN 产物已用 gtc 上传并回验:GET 200,133404 字节,sha256 匹配。(gitcode 的 HEAD 返回 401,校验必须用 GET。)

解包目录为 libxpkg-0.0.47/,符合 Form A 描述符对 <verdir>/libxpkg-<tag>/mcpp.toml 的要求。

Adds `src`/`dst` for `type = "files"` assets and an `args` list separate
from `alias`, both needed by xlings to place and switch files that are not
programs. See openxlings/libxpkg#31.

Artifact is the GitHub tag tarball; the CN entry is the same bytes under the
gitcode mirror, verified by downloading both and comparing:
fe879e8f52ea5a7f316ca54bca1fa393febb8e2a23a3de852b0c2be1918a0be9
@Sunrisepeak
Sunrisepeak merged commit 9bd385c into main Jul 26, 2026
5 checks passed
Sunrisepeak added a commit to openxlings/xlings that referenced this pull request Jul 26, 2026
**File assets.** A package that ships something which is neither a program
nor a library -- headers under their own directory, pkg-config files,
certificates -- had no way to say so. `includedir` could only mean "this
directory becomes sysroot include". So index recipes grew their own
file-placing helpers instead: seven of them, in three languages, with two
contradictory conflict policies, and four invisible to the tool that is meant
to switch and remove what they write.

`type = "files"` with a `src`/`dst` pair says it, using the fields libxpkg
0.0.47 added (openxlings/libxpkg#31, published via mcpplibs/mcpp-index#128).
Both ends stay relative and the resolver enforces it: a payload is shared
between subos and reference-counted, so an absolute destination recorded
against one would be right for the subos that installed it and wrong for
every other. Destinations are also constrained to `usr/`, `etc/` and
`share/` -- `bin/` belongs to the shims, and anything walking upward leaves
the subos entirely. A recipe that trips either gets a refusal naming the
field, not a surprising placement.

Placement reuses the library path: same rename-based replacement, same
skip-when-already-correct, so `use` re-materializing on every invocation
costs a stat rather than a window with the file absent.

**Date-based release identity.** `x.y.z` is retired; this release is
`2026.7.27.0` -- year, month, day, same-day sequence, no leading zeros.

The scheme is safe for already-released clients, and that is verified rather
than assumed. `self update` expands to `install xlings@latest` plus `use
xlings latest`, and the latter sorts through `version_key_greater`, which
compares components numerically: [2026,7,27,0] against [0,4,68] decides on
the first one. A new test pins that against every 0.x a user might be on.

Writing that test found the reason leading zeros must not be used, which is
sharper than the tidiness argument the design doc gave. When every component
ties, the comparator falls back to a lexicographic tiebreak to keep the order
total -- `sort` requires that. So `2026.07.27.0` and `2026.7.27.0` are not one
key spelled two ways; they are two distinct keys, numerically equal and
ordered by string bytes. A database holding both would have two entries for
one version and `latest` would pick by accident. The test now pins that they
are distinguishable, which is exactly why only one spelling may be written.

Verified: 452 unit tests across 11 binaries; E2E-34 extended with a declared
file asset that must move with the release at every step; group switch,
multi-version removal, home-config lock, subos install and idempotent install
all still pass.
Sunrisepeak added a commit to openxlings/xlings that referenced this pull request Jul 26, 2026
…s mixing versions (#410)

* fix(xvm): make libraries follow the release they belong to

A library was already a first-class entry: registered with a payload path, a
source name and a destination name, carried through the binding group like
any other member, and materialized correctly at install. What it never had
was materialization on *switch*.

`plan_use_switch` decided library work by reading `VData::libdir`. Nothing in
the tree writes that field. Not the registration batch -- `RegistrationNode`
has no such member -- and not any other path; the only mention outside the
struct is the JSON reader. On a real installation it is absent from all 372
entries. So the planner emitted no library work at all and `xlings use` was a
silent no-op for libraries, while `install_libs()` sat at zero call sites.

What that produced, concretely: install openssl 3.1.5, then 3.2.0. The
install path overwrites the sysroot link, so the library is at 3.2.0. The
headers travel a different route entirely and stay at 3.1.5. `use` in either
direction moves neither and reports success both times. The user compiles
against one version and links against another, and nothing says so until it
fails at run time.

Every fact needed to place a library was already in the entry -- `path` +
`sourceName` + `destinationName`, exactly what the install path uses. The
planner was reading the wrong field. `xvm::library_placement` now resolves an
entry to `{source, name}` through accessors moved into xvm.types, so
registration and the planner cannot drift; `MemberSwitch` carries the
placement instead of a directory; and the two installer call sites that were
also gated on `libdir` go through the same resolver.

Placement replaces by rename rather than remove-then-link. Two versions of a
library share a soname, so a switch overwrites the same name, and `use`
re-materializes the active release on every invocation to repair a drifted
sysroot -- remove-then-link would open a window with the library absent on
every one of those calls. Only a genuinely different soname is unlinked.

Legacy state is covered: entries written before 0.4.70 have no per-version
`kind`, so the resolver falls back to the target-level `type`, which those
246 targets do have. Without that an upgrade would quietly stop switching
every library the user already had.

`install_libs` / `install_libdir` / `remove_libdir` are gone -- the first had
no callers, the other two were reachable only through the field that is never
written.

Verified: 6 new cases (XvmLibrarySwitch) + two existing switch-plan cases
reworked to cover a real library member; 441 tests, 11 binaries green.
Restoring the `libdir` read fails the new suite.

* fix(doctor): a release anchor is not a broken payload

A library-only package has no program of its own, so its recipe registers the
package name with no bindir purely to give the libraries something to bind
to -- `cairo.lua` writes `xvm.add(package.name)` and nothing else. With `type`
unset the C++ side defaults it to "program", so the entry claims to be an
executable that will never exist, and `self doctor` reports it as a broken
payload with a hint (`xlings install <pkg>@<ver>`) that cannot fix it because
nothing is wrong.

`xvm::is_binding_root` recognises the shape: some other entry names this one
as the root of its release. Both forms count -- the provider group written
since 0.4.70, and the legacy pairwise edge where a member records
`bindings[root][memberVersion] = rootVersion`. An upgraded installation is
full of the second, so recognising only the first would leave every existing
anchor misreported.

Reported, but as what it is, rather than suppressed. Staying silent would
hide the rarer case of a genuine program whose payload directory survived
while its executable did not: that entry is also a binding root, so it lands
on the same line and the user still sees it.

Measured against a real 372-entry installation: `self doctor` went from 9
broken payloads to 8 plus 2 release anchors, with `binutils@2.42` and
`cairo@1.18.0` reclassified. (Two further findings became visible only
because the panel is a fixed 24 lines and reclassifying freed the slots --
they were always there.)

Correcting a number I put in the design doc and in #408: I claimed 31 false
positives. That was my own script's count of program-typed entries with no
same-named executable in the payload root, not doctor's output. doctor
resolves through `resolve_executable`, which searches `bin/` as well, so most
of those 31 resolve fine and were never reported. The real figure is 2. Both
documents are corrected in follow-up commits.

Verified: 3 new cases (XvmBindingRoot) covering the provider-group form, the
legacy pairwise form, and a standalone program that must stay reportable.
444 tests, 11 binaries green.

* fix(xim): stop a non-active version from overwriting the active library

Two defects, both found by putting the real CLI in front of a package that
ships a program, a library and a header -- the shape of openssl, which is
where the original report came from.

**Libraries were being placed in a directory nothing reads.** `cmd_use`
resolved the sysroot library directory as `<subos>/usr/lib`, while the
install path has always used `<subos>/lib` (`Config::paths().libDir`). On a
real installation that is 94 entries against one. The two never disagreed in
practice only because the switch side emitted no library work at all --
`VData::libdir`, the field it keyed on, has no writer -- so making libraries
switch would have quietly started filling a second, unused directory.

**Installing a second version clobbered the first one's library.** The
`Library` filesystem effect had no `active` gate. `InstallHeaders` grew one in
0.4.70 for exactly this reason and `Library` was missed, so `xlings install
pkg@2.0.0` while 1.0.0 was active moved the library to 2.0.0 and left the
headers on 1.0.0. The sysroot then held two releases at once: compile against
one, link against the other, and nothing reports it until run time.

E2E-34 walks the whole sequence and asserts the library and the header agree
at every step:

  1. install v1                → both materialize at v1
  2. install v2                → v1 stays active, the sysroot does not move
  3. use v2                    → both move
  4. use v1                    → both move back
  4b. use from the library     → the program follows too
  5. remove, empty uninstall() → the whole release deregisters

Scenario 5 is the other thing this pins. Removal has been provider-scoped
since 0.4.70 -- with no `xvm.remove` ops the installer finds every entry
whose `bindingGroup.provider` matches and takes the release out -- but that
was read out of the code and never tested. The fixture's `uninstall()`
returns true and does nothing, so if the mechanism does not work the entries
survive and the scenario fails. It is the prerequisite for deleting the
hand-written teardown boilerplate every recipe currently carries.

Both defects were reproduced by the test before being fixed; scenario 1
failed on the wrong directory and scenario 2 on the missing gate.

Verified: E2E-34 green; xvm_group_switch, remove_multi_version,
subos_xpkg_install, home_config_lock all still pass; 444 unit tests, 11
binaries green.

* docs(release): record the library-switching fixes in the 0.4.70 notes

* test(xvm): build the expected library path with `path`, not a POSIX literal

The planner joins the payload directory and the source name with
`std::filesystem::path`, which yields native separators. Comparing the result
against "/pkg/openssl/3.1.5/lib64/libssl.so.3" therefore passes on POSIX and
can never pass on Windows, where the value is
"/pkg/openssl/3.1.5/lib64\\libssl.so.3".

Windows CI caught it. Five other assertions in the same suite use substring
matches and were unaffected. Build the expectation the same way the value is
built.

* docs(release): adopt the compound release identity

`(0.4.70) 2026.07.27.0` -- semantic version plus release date and same-day
sequence. The semantic version stays machine-readable: `self update`
compares it and both indexes key on it, so a date-only identity would leave
already-released clients unable to tell whether an upgrade exists.

* feat(xvm): declared file assets, and date-based release identity

**File assets.** A package that ships something which is neither a program
nor a library -- headers under their own directory, pkg-config files,
certificates -- had no way to say so. `includedir` could only mean "this
directory becomes sysroot include". So index recipes grew their own
file-placing helpers instead: seven of them, in three languages, with two
contradictory conflict policies, and four invisible to the tool that is meant
to switch and remove what they write.

`type = "files"` with a `src`/`dst` pair says it, using the fields libxpkg
0.0.47 added (openxlings/libxpkg#31, published via mcpplibs/mcpp-index#128).
Both ends stay relative and the resolver enforces it: a payload is shared
between subos and reference-counted, so an absolute destination recorded
against one would be right for the subos that installed it and wrong for
every other. Destinations are also constrained to `usr/`, `etc/` and
`share/` -- `bin/` belongs to the shims, and anything walking upward leaves
the subos entirely. A recipe that trips either gets a refusal naming the
field, not a surprising placement.

Placement reuses the library path: same rename-based replacement, same
skip-when-already-correct, so `use` re-materializing on every invocation
costs a stat rather than a window with the file absent.

**Date-based release identity.** `x.y.z` is retired; this release is
`2026.7.27.0` -- year, month, day, same-day sequence, no leading zeros.

The scheme is safe for already-released clients, and that is verified rather
than assumed. `self update` expands to `install xlings@latest` plus `use
xlings latest`, and the latter sorts through `version_key_greater`, which
compares components numerically: [2026,7,27,0] against [0,4,68] decides on
the first one. A new test pins that against every 0.x a user might be on.

Writing that test found the reason leading zeros must not be used, which is
sharper than the tidiness argument the design doc gave. When every component
ties, the comparator falls back to a lexicographic tiebreak to keep the order
total -- `sort` requires that. So `2026.07.27.0` and `2026.7.27.0` are not one
key spelled two ways; they are two distinct keys, numerically equal and
ordered by string bytes. A database holding both would have two entries for
one version and `latest` would pick by accident. The test now pins that they
are distinguishable, which is exactly why only one spelling may be written.

Verified: 452 unit tests across 11 binaries; E2E-34 extended with a declared
file asset that must move with the release at every step; group switch,
multi-version removal, home-config lock, subos install and idempotent install
all still pass.
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