Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# mcpp-style-ref Agent Skills
# libxpkg Agent Skills

用于指导 Agent 在编写或审查 Modern/Module C++ 代码时遵循 mcpp-style-ref 规范的技能
用于指导 Agent 在 libxpkg 仓库中编写、审查和维护 C++23 模块、xpkg Lua 规范、测试 fixture 与构建配置

## 可用技能

| 技能 | 说明 |
|------|------|
| [libxpkg-best-practices](libxpkg-best-practices/SKILL.md) | libxpkg 项目实践:模块边界、Lua/xpkg runtime、测试 fixture、跨平台构建与 CI |
| [mcpp-style-ref](mcpp-style-ref/SKILL.md) | 面向 mcpp 项目的 Modern/Module C++ (C++23) 命名、模块化与实践规则 |

## 使用方式
Expand Down
38 changes: 32 additions & 6 deletions .agents/skills/libxpkg-best-practices/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ description: Use when working on libxpkg — adding modules, writing tests, modi

libxpkg 是 xpkg 规范的 C++23 参考实现,分为四个子模块。以下是开发过程中积累的关键模式和陷阱。

## xpkg V1 包脚本边界

xpkg 包作者的默认边界是:

- libxpkg 提供的 `xim.libxpkg.*` 模块。
- 标准 Lua 语法与标准库。
- xpkg V1 规范中的 `package` 元数据、`xpm` 平台矩阵和 lifecycle hooks。
- xpkg 运行时内置 API,例如 `import()`、`path.*`、扩展的 `os.*`/`io.*`/`string.*`、`is_host()`、`try { ... }`。

新增或审查包脚本时,优先判断它是否只依赖上述四类能力。不要把包脚本 API 描述成某个外部构建工具的 API;在规范、注释和 review 结论中使用“xpkg 内置 API”或“xpkg runtime API”。

规范入口:`docs/V1/xpackage-spec.md`。

## 模块架构

```
Expand Down Expand Up @@ -121,24 +134,29 @@ end)

## 5. Lua 钩子文件规范

xpkg 包 `.lua` 文件必须正确捕获 `import()` 的返回值
xpkg 包 `.lua` 文件应使用 `libxpkg + 标准 Lua + xpkg 规范 + xpkg 内置 API`。`import()` 可加载 `xim.libxpkg.*` 模块;推荐捕获返回值为局部变量,避免隐式全局造成测试和复用困难

```lua
-- ❌ 错误:import() 不注入全局,pkginfo/xvm 为 nil
-- 不推荐:依赖隐式全局
import("xim.libxpkg.pkginfo")
import("xim.libxpkg.xvm")

-- ✅ 正确:捕获为局部变量
-- 推荐:捕获为局部变量
local pkginfo = import("xim.libxpkg.pkginfo")
local xvm = import("xim.libxpkg.xvm")
```

包脚本允许使用:
- 标准 Lua:`string/table/math/io/os.getenv/pcall/error` 等。
- xpkg 内置 API:`path.join`、`os.isfile`、`os.isdir`、`os.tryrm`、`os.mv`、`io.readfile`、`io.writefile`、`is_host`、`try { ... }` 等。
- libxpkg 模块:`pkginfo/xvm/system/log/json/pkgmanager/utils/elfpatch/base64`。

**钩子函数模板:**
```lua
-- installed() — 检查是否已安装,返回版本字符串或 nil
function installed()
local dir = pkginfo.install_dir()
if dir and io.exists(dir .. "/.installed") then
if dir and os.isfile(dir .. "/.installed") then
return pkginfo.version()
end
return nil
Expand All @@ -147,9 +165,16 @@ end
-- install() — 安装逻辑
function install()
local dir = pkginfo.install_dir()
os.execute('mkdir -p "' .. dir .. '"')
os.mkdir(dir)
io.writefile(dir .. "/.installed", pkginfo.version())
if xvm then xvm.add(pkginfo.name(), pkginfo.version(), dir) end
return true
end

-- config() — 注册命令/库/header 或写配置
function config()
local dir = pkginfo.install_dir()
if xvm then xvm.add(pkginfo.name(), { bindir = dir }) end
return true
end

-- uninstall() — 卸载逻辑
Expand All @@ -159,6 +184,7 @@ function uninstall()
if xvm and xvm.has(pkginfo.name()) then
xvm.remove(pkginfo.name())
end
return true
end
```

Expand Down
46 changes: 30 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ on:

jobs:
build:
name: build + test (linux x86_64, mcpp)
name: build + test (linux x86_64)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install xlings
env:
XLINGS_VERSION: 0.4.25
XLINGS_VERSION: 0.4.41
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
curl -fsSL -o "/tmp/${tarball}" \
Expand All @@ -23,24 +23,38 @@ jobs:
"/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install workspace tools (.xlings.json → mcpp 0.0.7)
run: xlings install -y
- name: Install workspace tools (.xlings.json)
run: |
xlings config --mirror GLOBAL
xlings update
xlings install -y

# Cache mcpp's self-bootstrapped sandbox (musl-gcc + binutils +
# glibc + ninja + patchelf, ~800 MB). Toolchain set is pinned by
# mcpp 0.0.7, so a fixed key suffices.
- name: Cache mcpp sandbox
uses: actions/cache@v4
with:
path: ~/.xlings/data/xpkgs/xim-x-mcpp/0.0.7/registry
key: mcpp-sandbox-${{ runner.os }}-mcpp0.0.7
- name: Verify workspace tools
run: |
mcpp --version
xmake --version
"$HOME/.xlings/data/xpkgs/xim-x-gcc/16.1.0/bin/g++" --version

- name: Build with mcpp
run: mcpp build

# mcpp 0.0.7 auto-prepends sandbox PATH (patchelf, ninja) for
# test binaries, so Linux elfpatch tests run without manual PATH
# setup. Only macOS-specific tests (need install_name_tool) are
# filtered — they can't run on a Linux runner.
# Only macOS-specific tests (need install_name_tool) are filtered —
# they cannot run on a Linux runner.
- name: Run tests
run: mcpp test -- --gtest_filter=-ExecutorTest.ApplyElfpatchAuto_*

- name: Build xpkg with xmake
run: |
XLINGS_BIN="$HOME/.xlings/subos/current/bin"
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
xmake f -c -m release \
--toolchain=gcc \
--cc="$XLINGS_BIN/gcc" \
--cxx="$XLINGS_BIN/g++" \
--ld="$XLINGS_BIN/g++" \
--sh="$XLINGS_BIN/g++" \
--ar=/usr/bin/ar \
--as=/usr/bin/as \
--policies=build.c++.modules.std \
-y
xmake build -y -j"$(nproc)" xpkg
4 changes: 3 additions & 1 deletion .xlings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"workspace": {
"mcpp": { "linux": "0.0.7" }
"xim:mcpp": { "linux": "0.0.30" },
"xim:xmake": { "linux": "3.0.7" },
"xim:gcc": { "linux": "16.1.0" }
}
}
202 changes: 202 additions & 0 deletions docs/V1/xpackage-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# XPackage Spec V1

This document defines the package-authoring surface supported by libxpkg for `spec = "1"` xpkg files.

## Authoring Boundary

An xpkg V1 file is a Lua file that must stay within four layers:

1. `libxpkg` runtime modules exposed through `import("xim.libxpkg.*")`.
2. Standard Lua syntax and standard libraries.
3. The xpkg V1 schema: `package` metadata, `xpm` platform matrix, resources, deps, exports, and lifecycle hooks.
4. xpkg built-in APIs documented in this file.

Package files should not depend on APIs outside this boundary. New package code should prefer portable Lua helpers and the `xim.libxpkg.*` modules over host-specific assumptions.

## File Shape

An xpkg V1 file has two parts:

```lua
package = {
spec = "1",
name = "hello",
description = "Example package",
type = "package",
xpm = {
linux = {
["latest"] = { ref = "1.0.0" },
["1.0.0"] = {
url = "https://example.com/hello-1.0.0-linux.tar.gz",
sha256 = "0123456789abcdef",
},
},
},
}

local pkginfo = import("xim.libxpkg.pkginfo")
local xvm = import("xim.libxpkg.xvm")

function install()
os.tryrm(pkginfo.install_dir())
os.mv(pkginfo.install_file(), pkginfo.install_dir())
return true
end

function config()
xvm.add("hello", { bindir = pkginfo.install_dir() })
return true
end

function uninstall()
xvm.remove("hello")
return true
end
```

The `package` table is declarative metadata. Runtime side effects belong in hooks.

## Package Metadata

Required fields:

| Field | Type | Description |
|-------|------|-------------|
| `spec` | string | Must be `"1"` for V1 packages. |
| `name` | string | Package name. |
| `description` | string | Short package description. |
| `type` | string | One of `package`, `script`, `template`, or `config`. |
| `xpm` | table | Platform matrix. |

Common optional fields:

| Field | Type | Description |
|-------|------|-------------|
| `namespace` | string | Optional package namespace, for example `config`. |
| `status` | string | `dev`, `stable`, or `deprecated`. |
| `archs` | string array | Supported CPU architectures. |
| `categories`, `keywords`, `programs` | string array | Search and registration metadata. |
| `authors`, `maintainers`, `licenses` | string array | Ownership and licensing metadata. |
| `homepage`, `repo`, `docs` | string | Project links. |
| `xvm_enable` | boolean | Whether the package is expected to expose xvm registrations. |

## Platform Matrix

Each `xpm` key is a platform name such as `linux`, `windows`, `macosx`, `ubuntu`, or `debian`.

```lua
xpm = {
linux = {
deps = {
runtime = { "xim:zlib" },
build = { "xim:gcc" },
},
exports = {
runtime = {
loader = "lib64/ld-linux-x86-64.so.2",
libdirs = { "lib64", "lib" },
abi = "linux-x86_64-glibc",
},
},
["latest"] = { ref = "1.0.0" },
["1.0.0"] = {
url = "https://example.com/pkg-1.0.0.tar.gz",
sha256 = "0123456789abcdef",
mirrors = {
GLOBAL = "https://example.com/pkg-1.0.0.tar.gz",
CN = "https://mirror.example.cn/pkg-1.0.0.tar.gz",
},
},
},
ubuntu = { ref = "linux" },
}
```

Supported platform fields:

| Field | Description |
|-------|-------------|
| `["latest"] = { ref = "..." }` | Version alias. |
| `["version"] = { url = "...", sha256 = "..." }` | Download resource. |
| `["version"] = "XLINGS_RES"` | Resource supplied by the package index mirror. |
| `deps = { "pkg" }` | Legacy dependency list. Consumers treat it as both runtime and build deps. |
| `deps = { runtime = {...}, build = {...} }` | Split runtime/build dependencies. |
| `exports.runtime.loader` | Dynamic loader path relative to install dir. |
| `exports.runtime.libdirs` | Runtime library directories relative to install dir. |
| `exports.runtime.abi` | ABI tag used to disambiguate loader providers. |
| `ref = "platform"` | Platform inheritance. |

## Lifecycle Hooks

Hooks are optional unless the package type or behavior needs them:

| Hook | Purpose |
|------|---------|
| `installed()` | Return installed version string, or `nil`/`false` when absent. |
| `build()` | Build from source or transform downloaded content before install. |
| `install()` | Install files into `pkginfo.install_dir()`. |
| `config()` | Register commands, libraries, headers, or config side effects. |
| `uninstall()` | Remove registrations and package-owned files. |

For `type = "config"` packages, keep `install()` trivial when there is no payload:

```lua
function install()
return true
end
```

Put configuration writes, tool registration, and validation in `config()`.

## Importing libxpkg Modules

Use `import("xim.libxpkg.<module>")` for xpkg runtime modules. Prefer assigning the return value to a local variable:

```lua
local pkginfo = import("xim.libxpkg.pkginfo")
local log = import("xim.libxpkg.log")
```

Available modules include:

| Module | Main APIs |
|--------|-----------|
| `pkginfo` | `name()`, `version()`, `install_file()`, `install_dir([dep, version])`, `dep_install_dir(dep, version)`, `deps_list()`, `build_dep(name, version)`, `with_build_deps_on_path(names, fn)` |
| `xvm` | `add(name, opt)`, `remove(name, version)`, `has(name, version)`, `info(name, version)`, `setup(name, opt)`, `teardown(name, opt)` |
| `system` | `exec(cmd, opt)`, `rundir()`, `xpkgdir()`, `bindir()`, `xpkg_args()`, `subos_sysrootdir()`, `run_in_script(content, admin)`, `unix_api()` |
| `log` | `debug()`, `info()`, `warn()`, `error()`, `set_level()`, `get_level()` |
| `json` | `decode()`, `encode()`, `loadfile()`, `savefile()` |
| `pkgmanager` | `install(target)`, `remove(target)`, `uninstall(target)` |
| `utils` | `filepath_to_absolute()`, `try_download_and_check()`, `input_args_process()` |
| `elfpatch` | ELF and Mach-O relocation helpers for binary packages. |
| `base64` | `encode()`, `decode()` |

## Built-In APIs

xpkg V1 provides a small built-in runtime for package scripts:

| API | Description |
|-----|-------------|
| `import(path)` | Loads an xpkg module such as `xim.libxpkg.pkginfo`. |
| `os.isfile(path)`, `os.isdir(path)` | File/directory checks. |
| `os.tryrm(path)`, `os.mkdir(path)` | Best-effort removal and directory creation. |
| `os.mv(src, dst)`, `os.trymv(src, dst)`, `os.cp(src, dst)` | File/directory movement and copy helpers. |
| `os.dirs(pattern)` | Directory glob helper. |
| `os.host()` | Current runtime platform name. |
| `os.exec(cmd)`, `os.iorun(cmd)` | Command execution helpers. |
| `path.join(...)`, `path.filename(path)`, `path.directory(path)`, `path.is_absolute(path)` | Path helpers. |
| `io.readfile(path)`, `io.writefile(path, content)` | Whole-file IO helpers. |
| `string.split(s, sep, plain)`, `string.trim(s)`, `string.replace(s, old, new)` | String helpers. |
| `cprint(fmt, ...)`, `format(...)`, `raise(msg)`, `try { ... }` | Convenience helpers retained by the xpkg runtime. |
| `is_host(name)` | Shortcut for matching the current runtime platform. |

Standard Lua APIs such as `string`, `table`, `math`, `io.open`, `os.getenv`, `pcall`, and `error` are available and are part of the supported authoring surface.

## Authoring Rules

- Prefer `xim.libxpkg.*` modules for package lifecycle integration.
- Prefer standard Lua for local parsing, table manipulation, string processing, and error handling.
- Keep top-level code declarative; side effects should run inside lifecycle hooks.
- Do not write secrets to logs. Use `log.info()`/`log.warn()`/`log.error()` for status messages.
- Do not mutate user shell profiles directly. Register commands through `xvm` and package dependencies through `xpm`.
- For config packages, preserve existing user config files where possible and back them up before overwriting.
4 changes: 2 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ repo = "https://github.com/openxlings/libxpkg"
[targets.xpkg]
kind = "lib"

[dependencies]
"mcpplibs.capi.lua" = "0.0.3"
[dependencies."mcpplibs.capi"]
lua = "0.0.3"

[dev-dependencies]
gtest = "1.15.2"
Loading