diff --git a/.github/workflows/openkal-compat.yml b/.github/workflows/openkal-compat.yml new file mode 100644 index 00000000..322ef9ff --- /dev/null +++ b/.github/workflows/openkal-compat.yml @@ -0,0 +1,180 @@ +name: openkal-compat + +# Measures the members listed in tests/openkal/members.toml in an openkal graph +# and records the result the site labels packages by. See docs/openkal-compat.md. +# +# It does not gate pull requests that touch other descriptors: openkal is still +# moving, and a label is a statement about a measurement rather than a condition +# of merging. The regression comparison below becomes a required check only when +# the repository variable OPENKAL_RATCHET is `on`. + +on: + schedule: + - cron: "0 7 * * 0" + workflow_dispatch: + inputs: + members: + description: "Space-separated members to measure (empty = every listed member)" + required: false + default: "" + pull_request: + paths: + - "pkgs/**/*.lua" + - "tests/openkal/**" + - ".xpkgindex/openkal-compat.json" + - ".github/workflows/openkal-compat.yml" + +permissions: + contents: read + +env: + MCPP_VERSION: "2026.9.17.2" + XLINGS_NON_INTERACTIVE: "1" + +jobs: + measure: + name: measure (linux, windows through wine) + runs-on: ubuntu-24.04 + timeout-minutes: 240 + outputs: + members: ${{ steps.select.outputs.members }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # A pull request measures the members its change can affect; the schedule + # and a request with no members measure every listed member. + - name: Select the members + id: select + shell: bash + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + files=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD") + members=$(python3 tests/openkal/compat.py select $files) + else + members="${{ github.event.inputs.members }}" + [ -n "$members" ] || members=$(python3 tests/openkal/compat.py select tests/openkal/) + fi + echo "members=$members" >> "$GITHUB_OUTPUT" + echo "measuring: ${members:-nothing}" + + - name: Download mcpp + if: steps.select.outputs.members != '' + shell: bash + run: | + archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz" + curl -L -fsS -o "$archive" \ + "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/${archive}" + tar -xzf "$archive" + root="$PWD/mcpp-${MCPP_VERSION}-linux-x86_64" + echo "$root/bin" >> "$GITHUB_PATH" + echo "MCPP_HOME=$root" >> "$GITHUB_ENV" + + # The Windows cross headers of the host are installed deliberately. A + # graph that reached them would build differently with them present, so a + # result that is the same here and on a runner without them is evidence + # that the graph is closed. Wine runs the Windows members' tests. + - name: Host headers the graph must not reach, and a runner for Windows + if: steps.select.outputs.members != '' + shell: bash + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq mingw-w64 wine64 > /dev/null + ls /usr/x86_64-w64-mingw32/include/windows.h + command -v wine || sudo ln -s "$(command -v wine64)" /usr/local/bin/wine + wine --version + + - name: The pins agree with this workflow + if: steps.select.outputs.members != '' + shell: bash + run: | + pinned=$(sed -n 's/^mcpp *= *"\(.*\)"/\1/p' tests/openkal/pins.toml) + if [ "$pinned" != "$MCPP_VERSION" ]; then + echo "::error::tests/openkal/pins.toml names mcpp $pinned and this workflow installs $MCPP_VERSION" + exit 1 + fi + + - name: Measure + if: steps.select.outputs.members != '' + shell: bash + run: | + mcpp self config --mirror GLOBAL + args=() + for m in ${{ steps.select.outputs.members }}; do args+=(--member "$m"); done + python3 tests/openkal/compat.py run "${args[@]}" --out openkal-compat.json + + - uses: actions/upload-artifact@v4 + if: steps.select.outputs.members != '' + with: + name: openkal-compat + path: openkal-compat.json + + - name: No published label regressed + if: steps.select.outputs.members != '' + shell: bash + continue-on-error: ${{ vars.OPENKAL_RATCHET != 'on' }} + run: | + python3 tests/openkal/compat.py check \ + --results openkal-compat.json --baseline .xpkgindex/openkal-compat.json + + - name: Summary + if: always() && steps.select.outputs.members != '' + shell: bash + run: | + [ -f openkal-compat.json ] || exit 0 + python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" + import json + r = json.load(open("openkal-compat.json")) + print("| member | target | result | first diagnostic |") + print("| --- | --- | --- | --- |") + for m, e in sorted(r["members"].items()): + for t, rec in sorted(e["targets"].items()): + d = rec.get("diagnostic", "").replace("|", "\\|") + print(f"| {m} | {t} | {rec['status']} | {d} |") + PY + + # Only this job may write, and it does not run for a pull request. + propose: + name: propose the measurement + needs: measure + if: needs.measure.outputs.members != '' && github.event_name != 'pull_request' && github.event.inputs.members == '' + runs-on: ubuntu-24.04 + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: openkal-compat + # A scheduled or requested measurement that differs from the published file + # is proposed as a pull request rather than committed, so that a label + # changes only through review. + - name: Propose the new measurement + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + python3 - <<'PY' + import json + new = json.load(open("openkal-compat.json")) + old = json.load(open(".xpkgindex/openkal-compat.json")) + same = {k: v for k, v in new.items() if k != "measured"} == \ + {k: v for k, v in old.items() if k != "measured"} + open("same.txt", "w").write("1" if same else "0") + PY + if [ "$(cat same.txt)" = "1" ]; then echo "the measurement is unchanged"; exit 0; fi + branch="bot/openkal-compat-$(date +%Y%m%d)" + cp openkal-compat.json .xpkgindex/openkal-compat.json + git config user.name "mcpp-index-bot" + git config user.email "bot@mcpplibs.invalid" + git checkout -b "$branch" + git add .xpkgindex/openkal-compat.json + git commit -m "openkal compatibility: the measurement of $(date +%F)" + git push -f origin "$branch" + gh pr create --base main --head "$branch" \ + --title "openkal compatibility: the measurement of $(date +%F)" \ + --body "Measured by .github/workflows/openkal-compat.yml run ${{ github.run_id }}. See the run summary for every member and target." \ + || echo "a pull request for $branch already exists" + diff --git a/.gitignore b/.gitignore index 73f1f408..126226e1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ tests/examples/*/imgui.ini # plugin bytecode .xpkgindex/plugins/__pycache__/ +tests/openkal-work/ diff --git a/.xpkgindex/openkal-compat.json b/.xpkgindex/openkal-compat.json new file mode 100644 index 00000000..38439767 --- /dev/null +++ b/.xpkgindex/openkal-compat.json @@ -0,0 +1,48 @@ +{ + "excluded": { + "build-mcpp": "builds the build tool itself, a host program", + "cuda-curand": "a GPU vendor runtime distributed as binaries for the platform C runtime", + "cuda-driver": "a GPU vendor runtime distributed as binaries for the platform C runtime", + "grpc-codegen": "runs host code generators", + "protobuf-protoc": "runs a host code generator", + "sycl-runtime": "a vendor runtime distributed as binaries for the platform C runtime" + }, + "measured": "2026-09-17", + "members": { + "cmdline": { + "packages": [ + "mcpplibs.cmdline" + ], + "portable": true, + "targets": { + "x86_64-linux-gnu": { + "status": "runs" + }, + "x86_64-windows-gnu": { + "status": "runs" + } + } + }, + "gzip-hpp": { + "packages": [ + "compat.gzip-hpp" + ], + "portable": true, + "targets": { + "x86_64-linux-gnu": { + "status": "runs" + }, + "x86_64-windows-gnu": { + "diagnostic": "error: build failed", + "status": "fails" + } + } + } + }, + "pins": { + "mcpp": "2026.9.17.2", + "runtime": "0.9.7", + "toolchain": "llvm@22.1.8" + }, + "schema": 1 +} diff --git a/.xpkgindex/plugins/mcpp.py b/.xpkgindex/plugins/mcpp.py index 676f5529..fb227d6f 100644 --- a/.xpkgindex/plugins/mcpp.py +++ b/.xpkgindex/plugins/mcpp.py @@ -46,6 +46,28 @@ def _t(en: str, zh: str, hant: str) -> Dict[str, str]: ("external", _t("upstream mcpp.toml", "上游 mcpp.toml", "上游 mcpp.toml"), "neutral"), ] +# Whether a package's test project builds and runs on openkal, measured by +# tests/openkal/compat.py and recorded in .xpkgindex/openkal-compat.json. The +# label is a measurement, never a declaration: a descriptor carries no field for +# it. Ordered from the strongest statement to the weakest. +OPENKAL_LEVELS = [ + ("family", _t("openkal itself", "openkal 本身", "openkal 本身"), "module"), + ("runs", _t("runs on openkal", "在 openkal 上运行", "在 openkal 上執行"), "module"), + ("builds", _t("builds on openkal", "在 openkal 上构建", "在 openkal 上建置"), "header"), + ("fails", _t("fails on openkal", "在 openkal 上失败", "在 openkal 上失敗"), "neutral"), + ("n/a", _t("not applicable", "不适用", "不適用"), "neutral"), +] +_OPENKAL_RANK = {"fails": 0, "builds": 1, "runs": 2} +# The packages that make up openkal: the specification, its implementations, and +# the layers built directly on it. Named once, here. +OPENKAL_FAMILY = { + "mcpplibs.openkal", "mcpplibs.openkal-kit", "mcpplibs.openkal-linux", + "mcpplibs.openkal-macos", "mcpplibs.openkal-windows", "mcpplibs.openkal-uefi", + "mcpplibs.openkal-opensbi", "mcpplibs.openkal-emscripten", "mcpplibs.openkal-libc", + "mcpplibs.openkal-musl", "mcpplibs.openkal-llvm-runtime", + "mcpplibs.std-freestanding-alloc-kal", +} + _IMPORT_RE = re.compile(r"^\s*import\s+([A-Za-z_][\w.]*)\s*;", re.M) _INCLUDE_RE = re.compile(r"^\s*#include\s+([<\"][^>\"]+[>\"])", re.M) _TAG_RE = re.compile(r"/archive/refs/tags/(.+?)\.(?:tar\.gz|zip|tgz)$") @@ -275,6 +297,8 @@ def __init__(self) -> None: self.root = "" self.examples: Dict[str, List[Dict[str, str]]] = {} self.overrides: Dict[str, Any] = {} + self.openkal: Dict[str, Any] = {} + self.openkal_by_package: Dict[str, Dict[str, Any]] = {} # ------------------------------------------------------------ index -- def on_index(self, ctx) -> None: @@ -298,6 +322,7 @@ def on_index(self, ctx) -> None: self.overrides = curated.get("interfaces", curated) self._scan_examples(ctx) + self._load_openkal(ctx) if self.examples: ctx.meta.set("hero_stats", [ {"label": _t("with examples", "带示例", "附範例"), "value": len(self.examples)}, @@ -345,6 +370,38 @@ def _scan_examples(self, ctx) -> None: for pkg_id in ids: self.examples.setdefault(pkg_id, []).append(entry) + def _load_openkal(self, ctx) -> None: + """Index the openkal measurement by package. + + A test project covers every package it depends on, and a package covered + by several projects takes, per target, the best result any of them + recorded: a package that runs in one project and was only built in + another does run.""" + data = _read_json(ctx.path(".xpkgindex", "openkal-compat.json")) + if not data: + return + self.openkal = data + for member, entry in (data.get("members") or {}).items(): + for pkg_id in entry.get("packages") or []: + rec = self.openkal_by_package.setdefault( + pkg_id, {"targets": {}, "members": [], "portable": True}) + rec["members"].append(member) + rec["portable"] = rec["portable"] and bool(entry.get("portable", True)) + for target, result in (entry.get("targets") or {}).items(): + have = rec["targets"].get(target) + if have is None or _OPENKAL_RANK.get(result.get("status"), 0) > \ + _OPENKAL_RANK.get(have.get("status"), 0): + rec["targets"][target] = dict(result, member=member) + + def _openkal_level(self, slug: str) -> str: + if slug in OPENKAL_FAMILY: + return "family" + rec = self.openkal_by_package.get(slug) + if not rec or not rec["targets"]: + return "" + best = max(_OPENKAL_RANK.get(r.get("status"), 0) for r in rec["targets"].values()) + return {2: "runs", 1: "builds", 0: "fails"}[best] + # --------------------------------------------------------- identity -- def identity(self, raw: Dict[str, Any], path: str) -> Optional[Identity]: """mcpp resolves `namespace.name`, so the namespace IS the identity. @@ -405,6 +462,15 @@ def on_package(self, pkg, raw: Dict[str, Any]) -> None: if "CN" in mirrors: pkg.extensions.setdefault("_badges", []).append(_t("CN mirror", "国内镜像", "中國鏡像")) + level = self._openkal_level(pkg.identity.slug) + if level: + pkg.facets["openkal"] = level + ext["openkal"] = {"level": level, + **(self.openkal_by_package.get(pkg.identity.slug) or {})} + if level in ("runs", "builds", "family"): + label = {k: lbl for k, lbl, _ in OPENKAL_LEVELS}[level] + pkg.extensions.setdefault("_badges", []).append(label) + pkg.extensions["mcpp"] = ext # After the examples are attached: the surfaces depend on which # interface lines the examples can actually produce. @@ -571,9 +637,14 @@ def _manifest_urls(self, pkg, ext: Dict[str, Any]) -> List[str]: # ------------------------------------------------------------ facets -- def facets(self) -> List[Facet]: - return [Facet(key="surface", label=_t("how you use it", "怎么用", "怎麼用"), weight=10, values=[ - FacetValue(key=key, label=label, tone=tone) for key, label, tone in SURFACES - ])] + return [ + Facet(key="surface", label=_t("how you use it", "怎么用", "怎麼用"), weight=10, values=[ + FacetValue(key=key, label=label, tone=tone) for key, label, tone in SURFACES + ]), + Facet(key="openkal", label=_t("openkal", "openkal", "openkal"), weight=20, values=[ + FacetValue(key=key, label=label, tone=tone) for key, label, tone in OPENKAL_LEVELS + ]), + ] # -------------------------------------------------------------- row -- # Line 2 of every row answers one question — how do I consume this — and @@ -655,6 +726,8 @@ def detail_blocks(self, pkg) -> List[Block]: f"{example['path']} · 專案 tests/examples/{example['project']}"), })) + blocks.extend(self._openkal_blocks(pkg, ext)) + if ext.get("form") == "B": items = [] if ext.get("modules"): @@ -739,6 +812,42 @@ def detail_blocks(self, pkg) -> List[Block]: + "。本次建置沒有抓取它的內容 —— 執行一次索引的重新整理動作就能拉進來。")})) return blocks + def _openkal_blocks(self, pkg, ext: Dict[str, Any]) -> List[Block]: + info = ext.get("openkal") or {} + if info.get("level") in (None, "", "family"): + return [] + rows = [] + for target, rec in sorted((info.get("targets") or {}).items()): + rows.append([target, rec.get("status", ""), rec.get("member", ""), + rec.get("diagnostic", "")]) + pins = self.openkal.get("pins") or {} + caption = _t( + f"Measured {self.openkal.get('measured', '')} by tests/openkal/compat.py with " + f"openkal-llvm-runtime {pins.get('runtime', '')}, {pins.get('toolchain', '')}, " + f"mcpp {pins.get('mcpp', '')}. " + + ("The test project selects no platform dependency of its own." + if info.get("portable", True) else + "The test project selects platform dependencies of its own."), + f"{self.openkal.get('measured', '')} 由 tests/openkal/compat.py 测得,使用 " + f"openkal-llvm-runtime {pins.get('runtime', '')}、{pins.get('toolchain', '')}、" + f"mcpp {pins.get('mcpp', '')}。" + + ("测试项目没有自行选择平台依赖。" if info.get("portable", True) + else "测试项目自行选择了平台依赖。"), + f"{self.openkal.get('measured', '')} 由 tests/openkal/compat.py 測得,使用 " + f"openkal-llvm-runtime {pins.get('runtime', '')}、{pins.get('toolchain', '')}、" + f"mcpp {pins.get('mcpp', '')}。" + + ("測試專案沒有自行選擇平台相依。" if info.get("portable", True) + else "測試專案自行選擇了平台相依。")) + return [ + Block(kind="table", title=_t("openkal", "openkal", "openkal"), weight=25, + data={"head": [_t("target", "目标", "目標"), _t("result", "结果", "結果"), + _t("test project", "测试项目", "測試專案"), + _t("first diagnostic", "首条诊断", "首條診斷")], + "rows": rows}), + Block(kind="callout", title=_t("How this was measured", "测量方式", "測量方式"), + weight=26, data={"text": caption}), + ] + def _interface_lines(self, pkg, ext: Dict[str, Any]) -> List[Dict[str, str]]: """Every way this package can be consumed, most idiomatic first. diff --git a/README.md b/README.md index 394baeef..48d53b27 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,8 @@ Detailed documentation lives in [`docs/`](docs/), written for humans and agents why each descriptor is written the way it is. - [The CN mirror loop](docs/cn-mirror.md): `gtc` and gitcode operations, plus the fallback when you have no `mcpp-res` access. +- [openkal compatibility](docs/openkal-compat.md): what the `openkal` label on the site means, how it is measured, and + how a package is adapted to an openkal graph. - [Repository layout, schema and CI](docs/repository-and-schema.md): field cheat-sheet, selective-run mechanics and local lint. - The **authoritative** judge of a field is `mcpp xpkg parse` (exactly what CI runs: an unknown mcpp-segment field diff --git a/README.zh-CN.md b/README.zh-CN.md index f4797425..f73531e7 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -65,6 +65,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 - [库形态与描述符模板](docs/zh/package-types.md):各类形态的描述符模板与样例,以及最小工程的写法。 - [描述符示例总览(按形态)](docs/zh/descriptor-examples.md):索引里已有内容的完整目录,以及每个描述符为何这样写。 - [CN 镜像闭环](docs/zh/cn-mirror.md):`gtc` 与 gitcode 操作,以及无 `mcpp-res` 权限时的回退方案。 +- [openkal 兼容性](docs/zh/openkal-compat.md):站点上 `openkal` 标签的含义、测量方式,以及包如何适配 openkal 依赖图。 - [仓库结构与 schema 与 CI](docs/zh/repository-and-schema.md):字段速查、选跑机制与本地 lint。 - 字段的**权威判定**是 `mcpp xpkg parse`(CI 用的就是它:未知的 mcpp 段字段直接失败,而不是被静默忽略); 语义与约束见 mcpp 仓的 [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec)。 diff --git a/docs/README.md b/docs/README.md index 0c73eee1..4fe2aa78 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,7 @@ behind each step. | [descriptor-examples.md](descriptor-examples.md) | The complete catalog of descriptors in this index, grouped by shape — what each package compiles, and what it deliberately leaves out | | [cn-mirror.md](cn-mirror.md) | The GitCode `mcpp-res` CN mirror loop (`gtc` tooling, closed-loop verification, caveats), including the fallback when you lack write access to `mcpp-res` (plain-string upstream url) | | [repository-and-schema.md](repository-and-schema.md) | Repository layout, descriptor schema cheat-sheet, `validate.yml` CI behavior, reproducing lint locally, case index | +| [openkal-compat.md](openkal-compat.md) | What the `openkal` label means, how it is measured, and how a package that fails in an openkal graph is adapted | > The package field specification (the `mcpp = { … }` extension) is settled by `mcpp xpkg parse` — the very parser CI > uses; for semantics and constraints see [`docs/spec/`](https://github.com/mcpp-community/mcpp/tree/main/docs/spec) diff --git a/docs/openkal-compat.md b/docs/openkal-compat.md new file mode 100644 index 00000000..54dcd4d3 --- /dev/null +++ b/docs/openkal-compat.md @@ -0,0 +1,126 @@ +# openkal compatibility + +**Reader:** a package author or index maintainer who wants to know whether a +package works on openkal, or who is adapting one. + +**The question this document answers:** what the `openkal` label on the site +means, how it is measured, and how a descriptor adapts a package whose build +fails in an openkal graph. + +## 1. What is measured + +A build selects openkal when its graph contains `openkal-llvm-runtime` (C++) or +`openkal-musl` (C). The graph then supplies four layers, and each guarantees only +its own: + +| Layer | Supplied by | Guarantees | +| --- | --- | --- | +| `kernel-abi = openkal` | the specification and one implementation per target | every `kal_*` operation behaves the same on every platform | +| `c-abi = musl` | `openkal-musl` | a POSIX-shaped C environment; what it cannot provide it refuses and lists | +| `c++-abi = libc++` | `openkal-llvm-runtime` | a C++ runtime configured for that C library | +| build tool | `mcpp` | a layer supplied by the graph is supplied wholly by it; host headers are not searched | + +The label is a measurement of the package's test project in that graph, not a +declaration. A descriptor has no field for it. + +## 2. The label + +`tests/openkal/compat.py run` copies each member listed in +`tests/openkal/members.toml`, adds the runtime pinned in `tests/openkal/pins.toml`, +and builds it with the pinned toolchain for every pinned target. The result is +recorded per target: + +| Result | Meaning | +| --- | --- | +| `runs` | the member's tests passed; on a target other than the host they ran through the pinned runner (Wine for Windows) | +| `builds` | the member built, and its tests were not run or did not pass; the first diagnostic is kept | +| `fails` | the member did not build; the first diagnostic is kept | + +The results are written to `.xpkgindex/openkal-compat.json` together with the +pins and the date. The site gives every package a test project covers the best +result any covering project recorded for each target, and files the package +under the `openkal` facet by its best target. The packages of openkal itself are +filed as `openkal itself`. + +A member is also recorded as selecting platform dependencies of its own or not. +Selecting them is permitted: a package on openkal may use a platform's system +interfaces, provided they come from the dependency graph. The distinction is +shown and does not lower the label. + +`tests/openkal/members.toml` lists what is measured. `[excluded]` lists members +that cannot be built in any openkal graph, each with its reason; a member that +fails is measured and published, not excluded. + +## 3. When it runs + +`.github/workflows/openkal-compat.yml` runs weekly and on demand, measuring every +listed member. For a pull request it measures every member when the openkal +family or `tests/openkal` changes, and otherwise the members whose test projects +depend on a changed descriptor. It does not block a merge unless the comparison +below is enabled. + +It installs the Windows cross toolchain's host headers on purpose. A build that +reached the host's headers would change its result when they are present, so a +result that does not change is evidence that the graph was closed. + +The workflow also compares a new measurement with the published file +(`compat.py check`) and reports every member whose label for a target became +lower. The comparison becomes a required check for pull requests once the +repository variable `OPENKAL_RATCHET` is `on`; it is enabled after the weekly +measurement has been stable for two consecutive weeks. + +## 4. Adapting a package + +A package that fails in an openkal graph has met one of the layers. The rules +below decide where the adaptation goes. + +1. **Adapt on the layer that differs.** A missing header or C runtime function + (`io.h`, `_lseeki64`, `TargetConditionals.h`, `winsock2.h`) is a property of + the C library, and the adaptation is selected with `c-abi`: + + ```lua + target_cfg = { + ["cfg(all(windows, c-abi = \"musl\"))"] = { cflags = { ... } }, + }, + ``` + + A missing facility (`epoll`, signal handlers) is a property of openkal. Prefer + a feature of the package that selects another mechanism (for example a + `select`-based reactor); where a descriptor must select it, use + `cfg(all(kernel-abi = "openkal", c-abi = "musl"))`. Nothing in source should + test whether it is built on openkal. + +2. **Do not undefine a platform macro.** `_WIN32` and `__APPLE__` are defined by + the target triple and are true. Change the package's own condition instead, + in this order of preference: a configuration macro the package already + honours (`Z_HAVE_UNISTD_H`, `HAVE_*`); a generated configuration header + (`generated_files`); a patch. An exception is permitted only for a private + translation unit whose result reaches no public header, and the descriptor + states why beside it. + +3. **A public header is read one way.** A macro that changes what a public header + declares must reach the package's consumers as well as the package. If it + cannot, the member asserts the agreement (rule 4). + +4. **A changed configuration is checked across the boundary.** A member of a + package whose configuration differs in an openkal graph asserts a layout the + compiled package reports about itself against the one its consumer computes. + `tests/examples/zlib` compares `zlibCompileFlags()` with `sizeof(z_off_t)`. + +5. **One C runtime and one C++ runtime per image.** A package that depends on a + platform may call the platform's system interfaces across which only handles + and values pass. A static library compiled against the platform's C runtime, + or an object that runtime owns crossing the boundary (`FILE*`, memory released + by the other runtime, `errno`), is not supported, and such a package is not + measured on openkal. A callback that a platform library makes on a thread it + created has no C library state and must not rely on it. + +## 5. Reproducing a measurement + +```bash +python3 tests/openkal/compat.py run --member cjson --target x86_64-linux-gnu --out /tmp/r.json +python3 tests/openkal/compat.py check --results /tmp/r.json --baseline .xpkgindex/openkal-compat.json +``` + +The members are copied into `tests/openkal-work/`, which is not tracked, and the +directory is removed when the run ends. diff --git a/docs/repository-and-schema.md b/docs/repository-and-schema.md index 3ba8f7fe..10ec679c 100644 --- a/docs/repository-and-schema.md +++ b/docs/repository-and-schema.md @@ -59,9 +59,9 @@ goes in `namespace` (mcpp SPEC-001 §3.2, see the mcpp repository): ```lua -namespace = "compat", name = "zlib" -- ✅ -namespace = "mcpplibs.capi", name = "lua" -- ✅ multi-level namespace -namespace = "mcpplibs", name = "capi.lua" -- ❌ the short name still carries a dot +namespace = "compat", name = "zlib" -- +namespace = "mcpplibs.capi", name = "lua" -- multi-level namespace +namespace = "mcpplibs", name = "capi.lua" -- the short name still carries a dot ``` The last one is rejected rather than reinterpreted: the extra dot inside `name` describes a namespace **nobody ever @@ -251,6 +251,15 @@ says so. `--platform` chooses which column of `tests/member-timings.tsv` to read `\t\t` rows to. The exit status is non-zero if any member failed, and the timing table prints either way — a run worth diagnosing is exactly the one where the times matter. +## openkal compatibility (openkal-compat.yml) + +`tests/openkal/compat.py` measures the members listed in `tests/openkal/members.toml` in an openkal graph, and +`.xpkgindex/openkal-compat.json` records the result the site labels packages by. A descriptor has no field for it. +`.github/workflows/openkal-compat.yml` runs weekly, on demand, and for a pull request that changes a descriptor a listed +member depends on; it does not gate other pull requests. How to adapt a package that fails there (select on `c-abi`, do +not undefine a platform macro, keep a public header read one way, assert a changed configuration across the boundary) +is stated in [openkal compatibility](openkal-compat.md). + ## After the merge `publish-artifact.yml` republishes the mcpp-index artifact and moves the pointer automatically once the change lands diff --git a/docs/zh/openkal-compat.md b/docs/zh/openkal-compat.md new file mode 100644 index 00000000..a459f8b9 --- /dev/null +++ b/docs/zh/openkal-compat.md @@ -0,0 +1,75 @@ +# openkal 兼容性 + +[English](../openkal-compat.md) | **简体中文** + +**读者:** 想知道某个包能否在 openkal 上工作的包作者或索引维护者,以及正在适配包的人。 + +**本文回答的问题:** 站点上的 `openkal` 标签是什么意思、如何测量,以及在 openkal 依赖图中构建失败的包如何由描述符适配。 + +## 1. 测量的对象 + +依赖图中含有 `openkal-llvm-runtime`(C++)或 `openkal-musl`(C)时,构建即选择了 openkal。此时依赖图提供四层,每层只保证自己那一层: + +| 层 | 提供者 | 保证 | +| --- | --- | --- | +| `kernel-abi = openkal` | 规范,以及每个目标上的一个实现 | 每个 `kal_*` 操作在所有平台上行为一致 | +| `c-abi = musl` | `openkal-musl` | 一个 POSIX 形状的 C 环境;做不到的显式拒绝并列出 | +| `c++-abi = libc++` | `openkal-llvm-runtime` | 为该 C 库配置的 C++ 运行时 | +| 构建工具 | `mcpp` | 由依赖图提供的层完全由依赖图提供;不搜索宿主头文件 | + +标签是对包的测试项目在该依赖图中的测量,不是声明。描述符中没有对应字段。 + +## 2. 标签 + +`tests/openkal/compat.py run` 复制 `tests/openkal/members.toml` 列出的每个成员,加入 `tests/openkal/pins.toml` 固定的运行时,用固定的工具链为每个固定的目标构建。结果按目标记录: + +| 结果 | 含义 | +| --- | --- | +| `runs` | 成员的测试通过;非宿主目标经由固定的运行器执行(Windows 用 Wine) | +| `builds` | 成员构建成功,测试未运行或未通过;保留第一条诊断 | +| `fails` | 成员构建失败;保留第一条诊断 | + +结果连同固定版本与日期写入 `.xpkgindex/openkal-compat.json`。一个包若被多个测试项目覆盖,站点对每个目标取其中最好的结果,并按最好的目标将包归入 `openkal` 分面。openkal 自身的包归为 `openkal 本身`。 + +成员是否自行选择平台依赖也会记录。自行选择平台依赖是允许的:openkal 上的包可以使用平台的系统接口,只要这些依赖来自依赖图。这一区分会显示出来,但不降低标签。 + +`tests/openkal/members.toml` 列出测量对象。`[excluded]` 列出在任何 openkal 依赖图中都无法构建的成员,并逐条写明原因;失败的成员照常测量并公布,不列入排除。 + +## 3. 何时运行 + +`.github/workflows/openkal-compat.yml` 每周运行、可手动触发,测量全部列出的成员。对 PR,openkal 家族或 `tests/openkal` 变化时测量全部成员,否则测量依赖了被修改描述符的成员。除非启用下文的比较,它不阻止合并。 + +它有意安装宿主的 Windows 交叉头文件。若构建触及宿主头文件,结果会因其存在而改变;结果不变即说明依赖图是封闭的。 + +工作流还会把新的测量与已发布的文件比较(`compat.py check`),报告每个在某目标上标签降低的成员。仓库变量 `OPENKAL_RATCHET` 为 `on` 时,这一比较成为 PR 的必需检查;在每周测量连续两周稳定之后启用。 + +## 4. 适配一个包 + +在 openkal 依赖图中失败的包遇到的是其中某一层。以下规则决定适配放在哪里。 + +1. **按造成差异的那一层适配。** 缺少头文件或 C 运行时函数(`io.h`、`_lseeki64`、`TargetConditionals.h`、`winsock2.h`)是 C 库层的性质,用 `c-abi` 选择: + + ```lua + target_cfg = { + ["cfg(all(windows, c-abi = \"musl\"))"] = { cflags = { ... } }, + }, + ``` + + 缺少设施(`epoll`、信号处理器)是 openkal 的性质。优先使用包自身选择其他机制的特性(例如基于 `select` 的 reactor);描述符必须自动选择时,使用 `cfg(all(kernel-abi = "openkal", c-abi = "musl"))`。源码中不应判断自己是否构建在 openkal 上。 + +2. **不取消定义平台宏。** `_WIN32` 与 `__APPLE__` 由目标三元组定义,且为真。应改写包自己的判断条件,按以下优先顺序:包已支持的配置宏(`Z_HAVE_UNISTD_H`、`HAVE_*`);生成的配置头(`generated_files`);补丁。仅当结果不进入任何公共头时,才允许在私有翻译单元中例外,并由描述符在旁注明原因。 + +3. **公共头只有一种读法。** 改变公共头声明内容的宏必须同时作用于包与其消费者;做不到时,由测试成员断言两侧一致(规则 4)。 + +4. **跨越边界检查变化了的配置。** 在 openkal 依赖图中配置不同的包,其测试成员要用已编译的包自己报告的布局,对照消费者计算出的布局。`tests/examples/zlib` 以 `zlibCompileFlags()` 对照 `sizeof(z_off_t)`。 + +5. **每个镜像只有一个 C 运行时和一个 C++ 运行时。** 依赖平台的包可以调用只传递句柄与值的平台系统接口。按平台 C 运行时编译的静态库,以及由该运行时拥有的对象跨越边界(`FILE*`、由另一运行时释放的内存、`errno`),均不受支持,此类包不在 openkal 上测量。平台库在其自行创建的线程上发起的回调没有 C 库状态,不得依赖它。 + +## 5. 复现一次测量 + +```bash +python3 tests/openkal/compat.py run --member cjson --target x86_64-linux-gnu --out /tmp/r.json +python3 tests/openkal/compat.py check --results /tmp/r.json --baseline .xpkgindex/openkal-compat.json +``` + +成员被复制到未纳入版本管理的 `tests/openkal-work/`,运行结束后删除该目录。 diff --git a/docs/zh/repository-and-schema.md b/docs/zh/repository-and-schema.md index 4e3faee9..426bbd41 100644 --- a/docs/zh/repository-and-schema.md +++ b/docs/zh/repository-and-schema.md @@ -51,9 +51,9 @@ tools/compat-ffmpeg/ 等 compat 大包的描述符再生成流水线 身份是二元组 —— **`namespace` 是点分层级路径,`name` 是单一原子段**。层级一律放 `namespace`(mcpp SPEC-001 §3.2,见 mcpp 仓的 [`docs/spec/package-identity.md`](https://github.com/mcpp-community/mcpp/blob/main/docs/spec/package-identity.md)): ```lua -namespace = "compat", name = "zlib" -- ✅ -namespace = "mcpplibs.capi", name = "lua" -- ✅ 多级命名空间 -namespace = "mcpplibs", name = "capi.lua" -- ❌ 短名仍带点 +namespace = "compat", name = "zlib" -- +namespace = "mcpplibs.capi", name = "lua" -- 多级命名空间 +namespace = "mcpplibs", name = "capi.lua" -- 短名仍带点 ``` 最后一种被拒绝而非重新解读:`name` 里多出的点描述的是一个**没人声明过的命名空间**。mcpp 曾按最后一个点切分、静默造出 `(mcpplibs.capi, lua)`,0.0.106 起改为拒绝。 @@ -202,6 +202,13 @@ bash tests/run_members.sh --all --cache local # 绕过包构建缓 `MCPP` 指定所用二进制(缺省为 `PATH` 上的 `mcpp`);`MCPP_TIMINGS` 指定追加 `<秒>\t<成员>\t` 行的 文件。任一成员失败则退出码非零,而耗时表两种情况下均会打印 —— 值得诊断的运行恰恰就是耗时重要的那一次。 +## openkal 兼容性(openkal-compat.yml) + +`tests/openkal/compat.py` 在 openkal 依赖图中测量 `tests/openkal/members.toml` 列出的成员,结果写入 +`.xpkgindex/openkal-compat.json`,站点据此给包打标签;描述符中没有对应字段。`.github/workflows/openkal-compat.yml` +每周运行、可手动触发,并在 PR 修改了被列出成员所依赖的描述符时运行;它不阻塞其他 PR。包在 openkal 上失败时的适配规则 +(按 `c-abi` 选择、不取消定义平台宏、公共头只有一种读法、跨边界断言变化了的配置)见 [openkal 兼容性](openkal-compat.md)。 + ## 合并后 `publish-artifact.yml` 在合并至 `main` 后自动重新发布 mcpp-index artifact 并移动指针,无需发布新的 mcpp 版本。 diff --git a/mcpp.toml b/mcpp.toml index afc37cdf..8b091848 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -152,6 +152,7 @@ members = [ "tests/examples/tray", "tests/examples/xxhash", "tests/examples/yyjson", + "tests/examples/zlib", "tests/examples/magic_enum", "tests/examples/mimalloc", "tests/examples/harfbuzz", diff --git a/pkgs/c/compat.mbedtls.lua b/pkgs/c/compat.mbedtls.lua index 18d87a2a..55d79a12 100644 --- a/pkgs/c/compat.mbedtls.lua +++ b/pkgs/c/compat.mbedtls.lua @@ -59,5 +59,18 @@ package = { windows = { ldflags = { "-lbcrypt" }, }, + target_cfg = { + -- Windows with musl as the C library. mbedtls selects its entropy, + -- timing and socket code by asking whether the environment is + -- Windows (_WIN32) or Unix (__unix__); the triple answers Windows, + -- and the C library these units compile against is POSIX-shaped + -- (openkal-musl: getrandom, clock_gettime, BSD sockets). Both + -- flags are confined to mbedtls's own translation units, and no + -- public header of mbedtls tests either macro, so its consumers + -- read the same declarations. + ["cfg(all(windows, c-abi = \"musl\"))"] = { + cflags = { "-U_WIN32", "-D__unix__" }, + }, + }, }, } diff --git a/pkgs/c/compat.zlib.lua b/pkgs/c/compat.zlib.lua index 6392b061..3fe29b38 100644 --- a/pkgs/c/compat.zlib.lua +++ b/pkgs/c/compat.zlib.lua @@ -53,6 +53,21 @@ package = { macosx = { cflags = { "-include", "mcpp_zlib_config.h" }, }, + -- Windows with musl as the C library (an openkal graph). The triple + -- defines _WIN32, and zlib reads _WIN32 as "the Windows C runtime is + -- present" (, _lseeki64, _wopen), which is false here. The flag is + -- confined to zlib's own translation units and reaches no public + -- header in a way that changes a declaration: zconf.h computes z_off_t + -- and z_off64_t as `long long` on both sides, because Z_HAVE_UNISTD_H + -- is deliberately not defined (unistd.h is included directly instead). + -- tests/examples/zlib asserts that agreement with zlibCompileFlags(). + -- gzopen_w stays declared for a consumer and is not defined: a call to + -- it fails at the link rather than at run time. + target_cfg = { + ["cfg(all(windows, c-abi = \"musl\"))"] = { + cflags = { "-U_WIN32", "-include", "unistd.h" }, + }, + }, generated_files = { ["mcpp_generated/include/mcpp_zlib_config.h"] = "#ifndef MCPP_ZLIB_CONFIG_H\n#define MCPP_ZLIB_CONFIG_H\n#if !defined(_WIN32)\n#define Z_HAVE_UNISTD_H 1\n#endif\n#endif\n", }, diff --git a/pkgs/o/openkal-linux.lua b/pkgs/o/openkal-linux.lua index 88830e7c..ad50242c 100644 --- a/pkgs/o/openkal-linux.lua +++ b/pkgs/o/openkal-linux.lua @@ -19,6 +19,13 @@ package = { xpm = { linux = { + ["0.13.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-linux/archive/refs/tags/0.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-linux/releases/download/0.13.0/openkal-linux-0.13.0.tar.gz", + }, + sha256 = "c794b08721297faefcb26eda238e32b350e3b94bc863a5c43b75c223dba482b0", + }, ["0.12.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-linux/archive/refs/tags/0.12.0.tar.gz", @@ -105,6 +112,13 @@ package = { }, }, macosx = { + ["0.13.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-linux/archive/refs/tags/0.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-linux/releases/download/0.13.0/openkal-linux-0.13.0.tar.gz", + }, + sha256 = "c794b08721297faefcb26eda238e32b350e3b94bc863a5c43b75c223dba482b0", + }, ["0.12.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-linux/archive/refs/tags/0.12.0.tar.gz", @@ -191,6 +205,13 @@ package = { }, }, windows = { + ["0.13.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-linux/archive/refs/tags/0.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-linux/releases/download/0.13.0/openkal-linux-0.13.0.tar.gz", + }, + sha256 = "c794b08721297faefcb26eda238e32b350e3b94bc863a5c43b75c223dba482b0", + }, ["0.12.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-linux/archive/refs/tags/0.12.0.tar.gz", diff --git a/pkgs/o/openkal-llvm-runtime.lua b/pkgs/o/openkal-llvm-runtime.lua index e1727998..540532c1 100644 --- a/pkgs/o/openkal-llvm-runtime.lua +++ b/pkgs/o/openkal-llvm-runtime.lua @@ -26,6 +26,13 @@ package = { xpm = { linux = { + ["0.10.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-llvm-runtime/archive/refs/tags/0.10.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-llvm-runtime/releases/download/0.10.0/openkal-llvm-runtime-0.10.0.tar.gz", + }, + sha256 = "3fbb5dcee3c62c74a07e76a7486d5e76d49af7c9ee5806ae4ee0545dfd47dd0e", + }, ["0.9.7"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-llvm-runtime/archive/refs/tags/0.9.7.tar.gz", @@ -168,6 +175,13 @@ package = { }, }, macosx = { + ["0.10.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-llvm-runtime/archive/refs/tags/0.10.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-llvm-runtime/releases/download/0.10.0/openkal-llvm-runtime-0.10.0.tar.gz", + }, + sha256 = "3fbb5dcee3c62c74a07e76a7486d5e76d49af7c9ee5806ae4ee0545dfd47dd0e", + }, ["0.9.7"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-llvm-runtime/archive/refs/tags/0.9.7.tar.gz", @@ -310,6 +324,13 @@ package = { }, }, windows = { + ["0.10.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-llvm-runtime/archive/refs/tags/0.10.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-llvm-runtime/releases/download/0.10.0/openkal-llvm-runtime-0.10.0.tar.gz", + }, + sha256 = "3fbb5dcee3c62c74a07e76a7486d5e76d49af7c9ee5806ae4ee0545dfd47dd0e", + }, ["0.9.7"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-llvm-runtime/archive/refs/tags/0.9.7.tar.gz", diff --git a/pkgs/o/openkal-macos.lua b/pkgs/o/openkal-macos.lua index f7881321..0fece845 100644 --- a/pkgs/o/openkal-macos.lua +++ b/pkgs/o/openkal-macos.lua @@ -19,6 +19,13 @@ package = { xpm = { linux = { + ["0.10.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-macos/archive/refs/tags/0.10.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-macos/releases/download/0.10.0/openkal-macos-0.10.0.tar.gz", + }, + sha256 = "282d0aff75e803bfacc39af3f777933c557197f0238c9b7d2d0ea7b22c9bf439", + }, ["0.9.1"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-macos/archive/refs/tags/0.9.1.tar.gz", @@ -112,6 +119,13 @@ package = { }, }, macosx = { + ["0.10.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-macos/archive/refs/tags/0.10.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-macos/releases/download/0.10.0/openkal-macos-0.10.0.tar.gz", + }, + sha256 = "282d0aff75e803bfacc39af3f777933c557197f0238c9b7d2d0ea7b22c9bf439", + }, ["0.9.1"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-macos/archive/refs/tags/0.9.1.tar.gz", @@ -205,6 +219,13 @@ package = { }, }, windows = { + ["0.10.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-macos/archive/refs/tags/0.10.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-macos/releases/download/0.10.0/openkal-macos-0.10.0.tar.gz", + }, + sha256 = "282d0aff75e803bfacc39af3f777933c557197f0238c9b7d2d0ea7b22c9bf439", + }, ["0.9.1"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-macos/archive/refs/tags/0.9.1.tar.gz", diff --git a/pkgs/o/openkal-musl.lua b/pkgs/o/openkal-musl.lua index 528b5b29..84d0cb62 100644 --- a/pkgs/o/openkal-musl.lua +++ b/pkgs/o/openkal-musl.lua @@ -24,6 +24,13 @@ package = { xpm = { linux = { + ["0.14.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-musl/archive/refs/tags/0.14.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-musl/releases/download/0.14.0/openkal-musl-0.14.0.tar.gz", + }, + sha256 = "91e747f495e041846c48522a3a5a2061ce6fd8f81e4b4e60f54cfaec4358bd66", + }, ["0.13.5"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-musl/archive/refs/tags/0.13.5.tar.gz", @@ -173,6 +180,13 @@ package = { }, }, macosx = { + ["0.14.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-musl/archive/refs/tags/0.14.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-musl/releases/download/0.14.0/openkal-musl-0.14.0.tar.gz", + }, + sha256 = "91e747f495e041846c48522a3a5a2061ce6fd8f81e4b4e60f54cfaec4358bd66", + }, ["0.13.5"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-musl/archive/refs/tags/0.13.5.tar.gz", @@ -322,6 +336,13 @@ package = { }, }, windows = { + ["0.14.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-musl/archive/refs/tags/0.14.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-musl/releases/download/0.14.0/openkal-musl-0.14.0.tar.gz", + }, + sha256 = "91e747f495e041846c48522a3a5a2061ce6fd8f81e4b4e60f54cfaec4358bd66", + }, ["0.13.5"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-musl/archive/refs/tags/0.13.5.tar.gz", diff --git a/pkgs/o/openkal-opensbi.lua b/pkgs/o/openkal-opensbi.lua index cbf256fd..7f703d92 100644 --- a/pkgs/o/openkal-opensbi.lua +++ b/pkgs/o/openkal-opensbi.lua @@ -20,6 +20,13 @@ package = { xpm = { linux = { + ["0.7.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-opensbi/archive/refs/tags/0.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-opensbi/releases/download/0.7.0/openkal-opensbi-0.7.0.tar.gz", + }, + sha256 = "4c1fa90f51b377aa30ff053ac6b814ab2eded41d4ccdbb8cfc438fa50fbbfb7e", + }, ["0.6.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-opensbi/archive/refs/tags/0.6.0.tar.gz", @@ -92,6 +99,13 @@ package = { }, }, macosx = { + ["0.7.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-opensbi/archive/refs/tags/0.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-opensbi/releases/download/0.7.0/openkal-opensbi-0.7.0.tar.gz", + }, + sha256 = "4c1fa90f51b377aa30ff053ac6b814ab2eded41d4ccdbb8cfc438fa50fbbfb7e", + }, ["0.6.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-opensbi/archive/refs/tags/0.6.0.tar.gz", @@ -164,6 +178,13 @@ package = { }, }, windows = { + ["0.7.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-opensbi/archive/refs/tags/0.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-opensbi/releases/download/0.7.0/openkal-opensbi-0.7.0.tar.gz", + }, + sha256 = "4c1fa90f51b377aa30ff053ac6b814ab2eded41d4ccdbb8cfc438fa50fbbfb7e", + }, ["0.6.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-opensbi/archive/refs/tags/0.6.0.tar.gz", diff --git a/pkgs/o/openkal-uefi.lua b/pkgs/o/openkal-uefi.lua index 435e77ed..da179d94 100644 --- a/pkgs/o/openkal-uefi.lua +++ b/pkgs/o/openkal-uefi.lua @@ -20,6 +20,13 @@ package = { xpm = { linux = { + ["0.7.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-uefi/archive/refs/tags/0.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-uefi/releases/download/0.7.0/openkal-uefi-0.7.0.tar.gz", + }, + sha256 = "fa16ad19d13dae8509d90ec2fd3714503701c27f323233bdbd2ba32ca3653068", + }, ["0.6.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-uefi/archive/refs/tags/0.6.0.tar.gz", @@ -85,6 +92,13 @@ package = { }, }, macosx = { + ["0.7.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-uefi/archive/refs/tags/0.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-uefi/releases/download/0.7.0/openkal-uefi-0.7.0.tar.gz", + }, + sha256 = "fa16ad19d13dae8509d90ec2fd3714503701c27f323233bdbd2ba32ca3653068", + }, ["0.6.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-uefi/archive/refs/tags/0.6.0.tar.gz", @@ -150,6 +164,13 @@ package = { }, }, windows = { + ["0.7.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-uefi/archive/refs/tags/0.7.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-uefi/releases/download/0.7.0/openkal-uefi-0.7.0.tar.gz", + }, + sha256 = "fa16ad19d13dae8509d90ec2fd3714503701c27f323233bdbd2ba32ca3653068", + }, ["0.6.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-uefi/archive/refs/tags/0.6.0.tar.gz", diff --git a/pkgs/o/openkal-windows.lua b/pkgs/o/openkal-windows.lua index 9c824709..c3bced0d 100644 --- a/pkgs/o/openkal-windows.lua +++ b/pkgs/o/openkal-windows.lua @@ -21,6 +21,13 @@ package = { xpm = { linux = { + ["0.8.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-windows/archive/refs/tags/0.8.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-windows/releases/download/0.8.0/openkal-windows-0.8.0.tar.gz", + }, + sha256 = "82151a7896007c21428f5c4162d72d131a31c71a3d9bb607d5188eee9e424b37", + }, ["0.7.4"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-windows/archive/refs/tags/0.7.4.tar.gz", @@ -128,6 +135,13 @@ package = { }, }, macosx = { + ["0.8.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-windows/archive/refs/tags/0.8.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-windows/releases/download/0.8.0/openkal-windows-0.8.0.tar.gz", + }, + sha256 = "82151a7896007c21428f5c4162d72d131a31c71a3d9bb607d5188eee9e424b37", + }, ["0.7.4"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-windows/archive/refs/tags/0.7.4.tar.gz", @@ -235,6 +249,13 @@ package = { }, }, windows = { + ["0.8.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal-windows/archive/refs/tags/0.8.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal-windows/releases/download/0.8.0/openkal-windows-0.8.0.tar.gz", + }, + sha256 = "82151a7896007c21428f5c4162d72d131a31c71a3d9bb607d5188eee9e424b37", + }, ["0.7.4"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal-windows/archive/refs/tags/0.7.4.tar.gz", diff --git a/pkgs/o/openkal.lua b/pkgs/o/openkal.lua index 08137bcd..7e79944c 100644 --- a/pkgs/o/openkal.lua +++ b/pkgs/o/openkal.lua @@ -22,6 +22,13 @@ package = { xpm = { linux = { + ["0.13.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal/archive/refs/tags/0.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal/releases/download/0.13.0/openkal-0.13.0.tar.gz", + }, + sha256 = "d8ef2871553d9fa3d90df2637c7dbd8410140234019963f17652bfaacd5c4ecf", + }, ["0.12.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal/archive/refs/tags/0.12.0.tar.gz", @@ -94,6 +101,13 @@ package = { }, }, macosx = { + ["0.13.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal/archive/refs/tags/0.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal/releases/download/0.13.0/openkal-0.13.0.tar.gz", + }, + sha256 = "d8ef2871553d9fa3d90df2637c7dbd8410140234019963f17652bfaacd5c4ecf", + }, ["0.12.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal/archive/refs/tags/0.12.0.tar.gz", @@ -166,6 +180,13 @@ package = { }, }, windows = { + ["0.13.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/openkal/archive/refs/tags/0.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/openkal/releases/download/0.13.0/openkal-0.13.0.tar.gz", + }, + sha256 = "d8ef2871553d9fa3d90df2637c7dbd8410140234019963f17652bfaacd5c4ecf", + }, ["0.12.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/openkal/archive/refs/tags/0.12.0.tar.gz", diff --git a/pkgs/t/tinyhttps.lua b/pkgs/t/tinyhttps.lua index 485714e1..2d41b791 100644 --- a/pkgs/t/tinyhttps.lua +++ b/pkgs/t/tinyhttps.lua @@ -89,6 +89,13 @@ package = { }, sha256 = "acb4d494bae2030632f4d91e4e8420cee9aed4560e4d0a6876e85232d2ba67f9", }, + ["0.3.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/tinyhttps/archive/refs/tags/0.3.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/tinyhttps/releases/download/0.3.1/tinyhttps-0.3.1.tar.gz", + }, + sha256 = "ba998b6053d4d662b119da308b8274058f7d9f7075c02ae0e87373a350f49ea5", + }, }, macosx = { ["0.2.1"] = { @@ -168,6 +175,13 @@ package = { }, sha256 = "acb4d494bae2030632f4d91e4e8420cee9aed4560e4d0a6876e85232d2ba67f9", }, + ["0.3.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/tinyhttps/archive/refs/tags/0.3.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/tinyhttps/releases/download/0.3.1/tinyhttps-0.3.1.tar.gz", + }, + sha256 = "ba998b6053d4d662b119da308b8274058f7d9f7075c02ae0e87373a350f49ea5", + }, }, windows = { ["0.2.1"] = { @@ -247,6 +261,13 @@ package = { }, sha256 = "acb4d494bae2030632f4d91e4e8420cee9aed4560e4d0a6876e85232d2ba67f9", }, + ["0.3.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/tinyhttps/archive/refs/tags/0.3.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/tinyhttps/releases/download/0.3.1/tinyhttps-0.3.1.tar.gz", + }, + sha256 = "ba998b6053d4d662b119da308b8274058f7d9f7075c02ae0e87373a350f49ea5", + }, }, }, } diff --git a/tests/examples/zlib/mcpp.toml b/tests/examples/zlib/mcpp.toml new file mode 100644 index 00000000..ce329ca8 --- /dev/null +++ b/tests/examples/zlib/mcpp.toml @@ -0,0 +1,11 @@ +# compat.zlib test project. It asserts behaviour (a deflate round trip and a gz +# file written and read back) and the one agreement a configuration that differs +# per target can break silently: the width of z_off_t as the compiled library +# reports it and as this consumer computes it from zconf.h. +# `compat` redirect inherited from the workspace root. +[package] +name = "zlib-tests" +version = "0.1.0" + +[dependencies.compat] +zlib = "1.3.2" diff --git a/tests/examples/zlib/tests/zlib.cpp b/tests/examples/zlib/tests/zlib.cpp new file mode 100644 index 00000000..313210b1 --- /dev/null +++ b/tests/examples/zlib/tests/zlib.cpp @@ -0,0 +1,70 @@ +// compat.zlib: behaviour, and agreement between the library and its consumer. +#include + +#include +#include +#include + +namespace { + +int failures = 0; + +void check(bool held, const char* what) { + if (!held) { std::printf("FAIL: %s\n", what); ++failures; } +} + +// zlibCompileFlags() bits 6 and 7 state the size of z_off_t the library was +// compiled with: 0 is 2 bytes, 1 is 4, 2 is 8, 3 is another size. +unsigned long library_off_t_size() { + switch ((zlibCompileFlags() >> 6) & 3u) { + case 0: return 2; + case 1: return 4; + case 2: return 8; + default: return 0; + } +} + +} // namespace + +int main() { + check(library_off_t_size() == sizeof(z_off_t), + "the library and this consumer agree on the width of z_off_t"); + + const std::string text(4096, 'z'); + uLongf bound = compressBound(static_cast(text.size())); + std::string packed(bound, '\0'); + check(compress(reinterpret_cast(packed.data()), &bound, + reinterpret_cast(text.data()), + static_cast(text.size())) == Z_OK, + "a buffer is compressed"); + std::string unpacked(text.size(), '\0'); + uLongf size = static_cast(unpacked.size()); + check(uncompress(reinterpret_cast(unpacked.data()), &size, + reinterpret_cast(packed.data()), bound) == Z_OK + && size == text.size() && unpacked == text, + "and decompresses to what it was"); + + const char* name = "zlib-test.gz"; + gzFile out = gzopen(name, "wb"); + check(out != nullptr, "a gz file is opened for writing"); + if (out) { + check(gzwrite(out, text.data(), static_cast(text.size())) + == static_cast(text.size()), "the gz file is written"); + gzclose(out); + } + gzFile in = gzopen(name, "rb"); + check(in != nullptr, "the gz file is opened for reading"); + if (in) { + std::string back(text.size(), '\0'); + check(gzread(in, back.data(), static_cast(back.size())) + == static_cast(text.size()) && back == text, + "and reads back what was written"); + check(gzseek(in, 10, SEEK_SET) == 10 && gztell(in) == 10, + "a gz file seeks and reports its offset through z_off_t"); + gzclose(in); + } + std::remove(name); + + if (failures == 0) std::printf("compat.zlib: every observation held\n"); + return failures == 0 ? 0 : 1; +} diff --git a/tests/openkal/compat.py b/tests/openkal/compat.py new file mode 100644 index 00000000..29ddfa09 --- /dev/null +++ b/tests/openkal/compat.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +"""Measures whether the index's test projects build and run on openkal. + +The measurement is the whole of what the site reports: a package is labelled +by what its test project did in an openkal graph, not by what its descriptor +claims. See docs/openkal-compat.md. + + compat.py run [--member NAME ...] [--target TRIPLE ...] --out FILE + compat.py check --results FILE [--baseline FILE] [--members NAME ...] + compat.py select FILE ... + +`run` copies each selected member of tests/examples into tests/openkal-work, +adds the openkal C++ runtime named by pins.toml, and builds with the pinned +toolchain. A target equal to the host is tested (`mcpp test`); another target +is tested through its runner when pins.toml names one and the runner is on +PATH, and built otherwise. Each member is reported per target as one of + + runs the member's tests passed + builds the member built; its tests were not run on this host + fails the build or the tests failed; the first diagnostic is kept + +`select` reads changed file paths and prints the members to measure: every +listed member when the openkal family or this directory changed, otherwise the +members whose test projects depend on a changed descriptor. + +`check` compares a results file with a baseline and fails when a member that +the baseline records as `runs` or `builds` for a target is recorded lower. It +is the regression guard for labels that have been published. +""" +from __future__ import annotations + +import argparse +import datetime as _dt +import json +import os +import platform +import re +import shutil +import subprocess +import sys + +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover + tomllib = None + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(os.path.dirname(HERE)) +EXAMPLES = os.path.join(ROOT, "tests", "examples") +WORK = os.path.join(ROOT, "tests", "openkal-work") +RANK = {"fails": 0, "builds": 1, "runs": 2} + + +def load_toml(path: str) -> dict: + with open(path, "rb") as f: + return tomllib.load(f) + + +def host_triple() -> str: + machine = platform.machine().lower() + arch = {"amd64": "x86_64", "arm64": "aarch64"}.get(machine, machine) + system = platform.system().lower() + if system == "linux": + return f"{arch}-linux-gnu" + if system == "darwin": + return f"{arch}-apple-darwin" + if system == "windows": + return f"{arch}-windows-msvc" + return f"{arch}-{system}" + + +def packages_of(manifest: dict) -> list[str]: + """The index packages a member depends upon, as `namespace.name`.""" + found: list[str] = [] + + def take(table: dict) -> None: + for key, value in table.items(): + if isinstance(value, dict) and not ({"version", "path", "features"} & set(value)): + for name in value: + found.append(f"{key}.{name}") + else: + found.append(key if "." in key else f"mcpplibs.{key}") + + for section in ("dependencies", "dev-dependencies"): + if isinstance(manifest.get(section), dict): + take(manifest[section]) + for cfg in (manifest.get("target") or {}).values(): + if isinstance(cfg, dict): + for section in ("dependencies", "dev-dependencies"): + if isinstance(cfg.get(section), dict): + take(cfg[section]) + return sorted(set(found)) + + +def platform_bound(manifest: dict) -> bool: + """A member that selects dependencies per target brings platform code of its + own. The openkal implementation is selected by the runtime and is not counted.""" + for cfg in (manifest.get("target") or {}).values(): + if isinstance(cfg, dict) and cfg.get("dependencies"): + return True + return False + + +def prepare(member: str, pins: dict) -> str: + """Copies a member and states the openkal graph in its manifest. + + The copy sits at the same depth as tests/examples/, so a relative + `[indices]` path in the member resolves to the same checkout.""" + src = os.path.join(EXAMPLES, member) + dst = os.path.join(WORK, member) + shutil.rmtree(dst, ignore_errors=True) + # Build state is not copied, and a link is copied as a link: a member's + # `.mcpp` directory links into the package store. + shutil.copytree(src, dst, symlinks=True, ignore=shutil.ignore_patterns( + "target", ".mcpp", "compile_commands.json", "mcpp.lock")) + path = os.path.join(dst, "mcpp.toml") + text = open(path, encoding="utf-8").read() + runtime = f'openkal-llvm-runtime = "{pins["runtime"]}"\n' + if re.search(r"^\[dependencies\]\s*$", text, re.M): + text = re.sub(r"^\[dependencies\]\s*$", "[dependencies]\n" + runtime.rstrip("\n"), + text, count=1, flags=re.M) + else: + text = text.rstrip("\n") + "\n\n[dependencies]\n" + runtime + for triple, runner in (pins.get("runners") or {}).items(): + if f"[target.{triple}]" not in text and f"[target.'{triple}']" not in text: + text += f"\n[target.{triple}]\nrunner = {json.dumps(runner)}\n" + open(path, "w", encoding="utf-8").write(text) + return dst + + +def first_diagnostic(output: str) -> str: + # A compiler's own diagnostic names a file and a line, and is preferred to + # the build tool's summary of it. + for line in output.splitlines(): + if re.search(r":\d+(:\d+)?: (fatal error|error):", line): + return line.strip()[:300] + for line in output.splitlines(): + if re.search(r"\b(fatal error|error)\b[:\[]", line) and "test result" not in line: + return line.strip()[:300] + for line in output.splitlines(): + if "FAIL" in line: + return line.strip()[:300] + return output.strip().splitlines()[-1][:300] if output.strip() else "" + + +def measure(member: str, target: str, pins: dict) -> dict: + work = prepare(member, pins) + toolchain = pins["toolchain"] + native = target == host_triple() + runner = (pins.get("runners") or {}).get(target) + can_run = native or (runner and shutil.which(runner[0])) + if can_run: + cmd = ["mcpp", "test", "--toolchain", toolchain] + if not native: + cmd += ["--target", target] + else: + cmd = ["mcpp", "build", "--toolchain", toolchain, "--target", target] + proc = subprocess.run(cmd, cwd=work, capture_output=True, text=True, + timeout=pins.get("timeout", 3600)) + out = proc.stdout + proc.stderr + if proc.returncode == 0: + status = "runs" if can_run else "builds" + return {"status": status} + built = can_run and re.search(r"^\s*Running bin/", out, re.M) is not None + return {"status": "builds" if built else "fails", + "diagnostic": first_diagnostic(out)} + + +def cmd_run(args: argparse.Namespace) -> int: + pins = load_toml(os.path.join(HERE, "pins.toml")) + members_file = load_toml(os.path.join(HERE, "members.toml")) + members = args.member or list(members_file.get("members", {}).keys()) + targets = args.target or pins.get("targets", [host_triple()]) + results = { + "schema": 1, + "measured": _dt.date.today().isoformat(), + "pins": {k: v for k, v in pins.items() if k in ("runtime", "toolchain", "mcpp")}, + "members": {}, + "excluded": members_file.get("excluded", {}), + } + for member in members: + manifest = load_toml(os.path.join(EXAMPLES, member, "mcpp.toml")) + entry = {"packages": packages_of(manifest), + "portable": not platform_bound(manifest), + "targets": {}} + for target in targets: + print(f"== {member} on {target}", flush=True) + try: + entry["targets"][target] = measure(member, target, pins) + except subprocess.TimeoutExpired: + entry["targets"][target] = {"status": "fails", "diagnostic": "timed out"} + print(f" {entry['targets'][target]['status']}" + + (f": {entry['targets'][target].get('diagnostic', '')}" + if entry['targets'][target]['status'] == 'fails' else ""), flush=True) + results["members"][member] = entry + with open(args.out, "w", encoding="utf-8") as f: + json.dump(results, f, indent=2, sort_keys=True) + f.write("\n") + shutil.rmtree(WORK, ignore_errors=True) + return 0 + + +def cmd_check(args: argparse.Namespace) -> int: + current = json.load(open(args.results, encoding="utf-8")) + baseline = json.load(open(args.baseline, encoding="utf-8")) + regressions = [] + for member, entry in baseline.get("members", {}).items(): + if args.members and member not in args.members: + continue + now = current.get("members", {}).get(member) + for target, rec in entry.get("targets", {}).items(): + was = RANK.get(rec.get("status"), 0) + if was == 0: + continue + got = RANK.get(((now or {}).get("targets", {}).get(target) or {}).get("status"), 0) + if got < was: + regressions.append(f"{member} on {target}: {rec['status']} -> " + f"{((now or {}).get('targets', {}).get(target) or {}).get('status', 'absent')}") + for line in regressions: + print(f"regression: {line}") + if not regressions: + print("no published label regressed") + return 1 if regressions else 0 + + +FAMILY_PREFIXES = ("pkgs/o/openkal", "pkgs/s/std-freestanding-alloc-kal", "tests/openkal/") + + +def descriptor_id(path: str) -> str: + stem = os.path.basename(path)[: -len(".lua")] + if "." in stem: + return stem + try: + text = open(os.path.join(ROOT, path), encoding="utf-8").read() + m = re.search(r'namespace\s*=\s*"([^"]+)"', text) + return f"{m.group(1)}.{stem}" if m else f"mcpplibs.{stem}" + except OSError: + return f"mcpplibs.{stem}" + + +def cmd_select(args: argparse.Namespace) -> int: + listed = list(load_toml(os.path.join(HERE, "members.toml")).get("members", {}).keys()) + changed = [p.strip() for p in args.files if p.strip()] + if any(p.startswith(FAMILY_PREFIXES) for p in changed): + print(" ".join(listed)) + return 0 + ids = {descriptor_id(p) for p in changed if p.startswith("pkgs/") and p.endswith(".lua")} + chosen = [] + for member in listed: + manifest = load_toml(os.path.join(EXAMPLES, member, "mcpp.toml")) + if ids & set(packages_of(manifest)): + chosen.append(member) + print(" ".join(chosen)) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + sub = parser.add_subparsers(dest="command", required=True) + run = sub.add_parser("run") + run.add_argument("--member", action="append") + run.add_argument("--target", action="append") + run.add_argument("--out", required=True) + check = sub.add_parser("check") + check.add_argument("--results", required=True) + check.add_argument("--baseline", required=True) + check.add_argument("--members", nargs="*") + select = sub.add_parser("select") + select.add_argument("files", nargs="*") + args = parser.parse_args() + return {"run": cmd_run, "check": cmd_check, "select": cmd_select}[args.command](args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/openkal/members.toml b/tests/openkal/members.toml new file mode 100644 index 00000000..c2bba69b --- /dev/null +++ b/tests/openkal/members.toml @@ -0,0 +1,48 @@ +# The test projects measured on openkal, and the ones deliberately not measured. +# +# A member is listed here, rather than every member being measured, because a +# measurement takes minutes per target. The list grows by adding a line; a +# member's label on the site comes from its measurement and not from this file. + +[members] +cjson = "JSON parsing, C" +yyjson = "JSON parsing, C" +"nlohmann.json" = "JSON, header-only C++" +expat = "XML parsing, C" +pcre2 = "regular expressions, C" +re2 = "regular expressions, C++" +md4c = "Markdown parsing, C" +xxhash = "hashing, C" +brotli = "compression, C" +zlib = "compression, C; its configuration differs on Windows with musl" +gzip-hpp = "compression, C++ over compat.zlib" +archive = "archive formats, C (libarchive)" +"fmtlib.fmt" = "formatting, C++" +spdlog = "logging, C++, threads and files" +cli11 = "command lines, C++" +argparse = "command lines, C++" +cmdline = "command lines, C++ modules" +"magic_enum" = "reflection, header-only C++" +glm = "mathematics, header-only C++" +eigen = "linear algebra, header-only C++" +catch2 = "testing framework, C++" +doctest = "testing framework, C++" +sqlite3 = "database, C, file locks" +capi-lua = "interpreter, C" +mimalloc = "allocator, C, address space" +libpng = "image codec, C over zlib" +c-ares = "name resolution, C, sockets" +curl = "HTTP client, C, sockets" +tinyhttps = "HTTPS client, C++ modules, sockets" +cmp-module = "coroutines, C++ modules" + +# Members that are not measured, each with the reason. A member belongs here +# when it cannot be built in any openkal graph, not when it fails: a failure is +# measured and published. +[excluded] +build-mcpp = "builds the build tool itself, a host program" +grpc-codegen = "runs host code generators" +protobuf-protoc = "runs a host code generator" +cuda-curand = "a GPU vendor runtime distributed as binaries for the platform C runtime" +cuda-driver = "a GPU vendor runtime distributed as binaries for the platform C runtime" +sycl-runtime = "a vendor runtime distributed as binaries for the platform C runtime" diff --git a/tests/openkal/pins.toml b/tests/openkal/pins.toml new file mode 100644 index 00000000..f56f6e8c --- /dev/null +++ b/tests/openkal/pins.toml @@ -0,0 +1,16 @@ +# The openkal graph every member is measured in. A result is meaningful only +# together with these values, and the results file records them. +# +# `runtime` is the C++ runtime for openkal; it pins openkal-musl exactly, and +# openkal-musl selects the openkal implementation for each target, so this one +# version names the whole graph. +runtime = "0.9.7" +toolchain = "llvm@22.1.8" +mcpp = "2026.9.17.2" + +targets = ["x86_64-linux-gnu", "x86_64-windows-gnu"] + +# A target that is not the host is tested through its runner when the runner is +# on PATH, and built otherwise. +[runners] +x86_64-windows-gnu = ["wine"]