From b71a550771ff8827d4d76495c0ef1d2cf8a97c0d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 7 Aug 2026 02:52:48 +0800 Subject: [PATCH 1/5] feat(site): mcpp plugin for the rebuilt package index site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Everything mcpp-specific about how this index is presented now lives in `.xpkgindex/plugins/mcpp.py` rather than being assumed by the generator. - identity: `namespace.name`, which is what `mcpp add` accepts — closes #170, and gives the three `imgui` packages three pages instead of one - classifies packages by how they are consumed (`import` / `#include` / tool), the axis a C++ user actually browses by; `categories` is set by no descriptor in this repo - reads the `mcpp = {}` block (both its table and string forms) and merges `mcpp.deps`, which the site never showed for the 21 packages that use it - Form A packages resolve their module name from the upstream manifest: the `export module` declaration in the lib target's interface unit is the authority (libxpkg's `[targets.xpkg]` exports `mcpplibs.xpkg`, not `xpkg`), falling back to the manifest description and then to this repo's own tests. Never derived from the package name — godot-cpp-m is named `godot-cpp-m` and imported as `godot_cpp` - links each package to the test project that demonstrates it, so package pages show code CI compiles rather than a snippet written for the website - `.xpkgindex/interfaces.json` carries the handful of lines no rule can reach (abseil, bzip2, xz …), each taken from this repo's own tests Upstream lookups are cached in `.xpkgindex/cache/` and committed, so a normal deploy touches no network; `refresh-site-cache.yml` refreshes them on demand. --- .github/workflows/refresh-site-cache.yml | 54 + .gitignore | 3 + .xpkgindex.json | 140 +- .xpkgindex/cache/github.json | 1744 ++++++++++++++++++++++ .xpkgindex/interfaces.json | 27 + .xpkgindex/plugins/mcpp.py | 899 +++++++++++ 6 files changed, 2855 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/refresh-site-cache.yml create mode 100644 .xpkgindex/cache/github.json create mode 100644 .xpkgindex/interfaces.json create mode 100644 .xpkgindex/plugins/mcpp.py diff --git a/.github/workflows/refresh-site-cache.yml b/.github/workflows/refresh-site-cache.yml new file mode 100644 index 00000000..5fcb6b62 --- /dev/null +++ b/.github/workflows/refresh-site-cache.yml @@ -0,0 +1,54 @@ +name: refresh-site-cache + +# Manual refresh of the data the site cannot read from `pkgs/` alone: +# upstream repo metadata (stars, avatars, contributor logins) and, for Form A +# packages, the upstream `mcpp.toml` that actually holds their build +# information. +# +# Deliberately manual. The normal `deploy-site` build reads the committed +# cache and touches no network, so a rendered page never depends on GitHub +# being reachable at deploy time; this job is how new data gets in. +# +# Run it after adding a package or bumping one to a new upstream tag — the +# cache is keyed by URL and a manifest URL contains the release tag, so a +# bumped package fetches fresh while everything else is reused. + +on: + workflow_dispatch: + inputs: + reason: + description: "Why (shows up in the commit message)" + required: false + default: "manual refresh" + +permissions: + contents: write + +jobs: + refresh: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install xpkgindex + run: pip install git+https://github.com/openxlings/xpkgindex.git + + - name: Refresh cache + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: xpkgindex generate . --output /tmp/site --refresh + + - name: Commit if anything changed + run: | + if git diff --quiet -- .xpkgindex/cache; then + echo "cache unchanged" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add .xpkgindex/cache + git commit -m "chore(site): refresh upstream cache (${{ inputs.reason }})" + git push diff --git a/.gitignore b/.gitignore index 9a59b728..73f1f408 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ tests/examples/*/imgui.ini # 本地 git worktree(逐包验证时开的),不属于索引内容 .worktrees/ + +# plugin bytecode +.xpkgindex/plugins/__pycache__/ diff --git a/.xpkgindex.json b/.xpkgindex.json index b01d941d..dd5fe9d4 100644 --- a/.xpkgindex.json +++ b/.xpkgindex.json @@ -2,31 +2,147 @@ "site": { "title": "mcpp Package Index", "description": "Modular C++23 packages for the mcpp build tool", - "logo": "mcpp Package Index" + "logo": "mcpp index" }, "links": { - "github": "https://github.com/mcpp-community/mcpp-index", + "github": "https://github.com/mcpplibs/mcpp-index", "custom": [ - {"label": "mcpp", "url": "https://github.com/mcpp-community/mcpp"}, - {"label": "xpkg V1 spec", "url": "https://github.com/d2learn/xim-pkgindex/blob/main/docs/V1/xpackage-spec.md"}, - {"label": "mcpp ext", "url": "https://github.com/mcpp-community/mcpp/blob/main/docs/04-schema-xpkg-extension.md"} + { + "label": "mcpp", + "url": "https://github.com/mcpp-community/mcpp" + }, + { + "label": "xlings", + "url": "https://github.com/openxlings/xlings" + }, + { + "label": "xpkg V1 spec", + "url": "https://github.com/openxlings/xim-pkgindex/blob/main/docs/V1/xpackage-spec.md" + }, + { + "label": "mcpp ext", + "url": "https://github.com/mcpp-community/mcpp/blob/main/docs/04-schema-xpkg-extension.md" + } ] }, "about": { "project_name": "mcpp", "project_url": "https://github.com/mcpp-community/mcpp", "description": "Modern C++23 build & package management tool — modules-first, xlings-driven, single-binary install", - "maintainers": ["mcpp-community"], + "maintainers": [ + "mcpp-community" + ], "license": "Apache-2.0" }, "theme": { - "primary_color": "#00d4ff", - "style": "dark" + "accent": "#5b46d6", + "style": "auto", + "tones": { + "module": "#5b46d6", + "header": "#52606d", + "tool": "#b8690f" + }, + "dark": { + "accent": "#9b8bfa", + "tones": { + "module": "#9b8bfa", + "header": "#a7b6c7", + "tool": "#e0a260" + } + } }, - "install_command_template": "mcpp add {name}@{version}", "pkgs_dir": "pkgs", - "install_commands": { - "unix": "curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash", - "windows": "iwr https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh -useb | bash" + "growth": { + "total_label": "all packages", + "series": [ + { + "label": "import", + "facet": "surface", + "value": "module", + "tone": "module" + }, + { + "label": "mcpplibs", + "facet": "namespace", + "value": "mcpplibs" + } + ] + }, + "base_url": "https://mcpplibs.github.io/mcpp-index", + "languages": [ + "en", + "zh", + "zh-Hant" + ], + "install_command_template": "mcpp add {ref}@{version}", + "install": { + "primary": { + "label": "Install mcpp", + "command": "xlings install mcpp -y" + }, + "summary": "Don't have xlings yet?", + "os": [ + { + "id": "unix", + "os": "Linux / macOS", + "command": "curl -fsSL https://d2learn.org/xlings-install.sh | bash" + }, + { + "id": "windows", + "os": "Windows · PowerShell", + "command": "irm https://d2learn.org/xlings-install.ps1.txt | iex" + } + ] + }, + "plugins": [ + ".xpkgindex/plugins/mcpp.py" + ], + "guides": { + "nav_label": "Contribute", + "entries": [ + { + "slug": "contributing", + "title": "Adding a package", + "path": "docs/README.md", + "translations": { + "zh": "docs/zh/README.md" + } + }, + { + "slug": "package-types", + "title": "The four library shapes", + "path": "docs/package-types.md", + "translations": { + "zh": "docs/zh/package-types.md" + } + }, + { + "slug": "cn-mirror", + "title": "CN mirror loop", + "path": "docs/cn-mirror.md", + "translations": { + "zh": "docs/zh/cn-mirror.md" + } + }, + { + "slug": "repository-and-schema", + "title": "Repository & schema", + "path": "docs/repository-and-schema.md" + } + ] + }, + "ecosystem": { + "owners": [ + "mcpplibs", + "mcpp-community", + "openxlings", + "d2learn", + "Sunrisepeak" + ], + "repos": [ + "mcpp-community/mcpp", + "openxlings/xlings", + "mcpplibs/mcpp-index" + ] } } diff --git a/.xpkgindex/cache/github.json b/.xpkgindex/cache/github.json new file mode 100644 index 00000000..c7cdc1f3 --- /dev/null +++ b/.xpkgindex/cache/github.json @@ -0,0 +1,1744 @@ +{ + "entries": { + "https://api.github.com/repos/Aimol-l/Tensorvia-cpu": { + "at": 1786040367, + "body": { + "description": "多后端张量库Tensorvia的CPU 后端,移植到纯 C++23 模块化设计,使用mcpp构建。", + "homepage": "", + "language": "C++", + "license": "MIT", + "owner": "Aimol-l", + "owner_avatar": "https://avatars.githubusercontent.com/u/56304358?v=4", + "owner_url": "https://github.com/Aimol-l", + "slug": "Aimol-l/Tensorvia-cpu", + "stars": 3, + "topics": [] + } + }, + "https://api.github.com/repos/ArthurSonzogni/FTXUI": { + "at": 1786040374, + "body": { + "description": ":computer: C++ Functional Terminal User Interface. :heart:", + "homepage": "https://arthursonzogni.github.io/FTXUI/", + "language": "C++", + "license": "MIT", + "owner": "ArthurSonzogni", + "owner_avatar": "https://avatars.githubusercontent.com/u/4759106?v=4", + "owner_url": "https://github.com/ArthurSonzogni", + "slug": "ArthurSonzogni/FTXUI", + "stars": 10516, + "topics": [ + "arthursonzogni", + "ascii", + "ascii-art", + "cpp", + "curse", + "simple", + "terminal", + "terminal-based", + "tui", + "ui", + "user-interface", + "xterm" + ] + } + }, + "https://api.github.com/repos/CLIUtils/CLI11": { + "at": 1786040372, + "body": { + "description": "CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.", + "homepage": "https://cliutils.github.io/CLI11/", + "language": "C++", + "license": "NOASSERTION", + "owner": "CLIUtils", + "owner_avatar": "https://avatars.githubusercontent.com/u/26603456?v=4", + "owner_url": "https://github.com/CLIUtils", + "slug": "CLIUtils/CLI11", + "stars": 4402, + "topics": [ + "cli", + "cli-parser", + "cpp11", + "no-dependencies" + ] + } + }, + "https://api.github.com/repos/DaveGamble/cJSON": { + "at": 1786040371, + "body": { + "description": "Ultralightweight JSON parser in ANSI C", + "homepage": "", + "language": "C", + "license": "MIT", + "owner": "DaveGamble", + "owner_avatar": "https://avatars.githubusercontent.com/u/118247?v=4", + "owner_url": "https://github.com/DaveGamble", + "slug": "DaveGamble/cJSON", + "stars": 12903, + "topics": [] + } + }, + "https://api.github.com/repos/KhronosGroup/OpenGL-Registry": { + "at": 1786040377, + "body": { + "description": "OpenGL, OpenGL ES, and OpenGL ES-SC API and Extension Registry", + "homepage": "", + "language": "C", + "license": "", + "owner": "KhronosGroup", + "owner_avatar": "https://avatars.githubusercontent.com/u/1608701?v=4", + "owner_url": "https://github.com/KhronosGroup", + "slug": "KhronosGroup/OpenGL-Registry", + "stars": 850, + "topics": [] + } + }, + "https://api.github.com/repos/Mbed-TLS/mbedtls": { + "at": 1786040381, + "body": { + "description": "An open source, portable, easy to use, readable and flexible TLS library, and reference implementation of the PSA Cryptography API. Releases are on a varying cadence, typically around 3 - 6 months between releases.", + "homepage": "https://www.trustedfirmware.org/projects/mbed-tls/", + "language": "C", + "license": "NOASSERTION", + "owner": "Mbed-TLS", + "owner_avatar": "https://avatars.githubusercontent.com/u/97226525?v=4", + "owner_url": "https://github.com/Mbed-TLS", + "slug": "Mbed-TLS/mbedtls", + "stars": 6858, + "topics": [ + "crypto", + "cryptography-library", + "psa", + "ssl", + "tls" + ] + } + }, + "https://api.github.com/repos/NVIDIA/libglvnd": { + "at": 1786040376, + "body": { + "description": "The GL Vendor-Neutral Dispatch library", + "homepage": "", + "language": "C", + "license": "", + "owner": "NVIDIA", + "owner_avatar": "https://avatars.githubusercontent.com/u/1728152?v=4", + "owner_url": "https://github.com/NVIDIA", + "slug": "NVIDIA/libglvnd", + "stars": 538, + "topics": [] + } + }, + "https://api.github.com/repos/Neargye/magic_enum": { + "at": 1786040395, + "body": { + "description": "Static reflection for enums (to string, from string, iteration) for modern C++, work with any enum type without any macro or boilerplate code", + "homepage": "", + "language": "C++", + "license": "MIT", + "owner": "Neargye", + "owner_avatar": "https://avatars.githubusercontent.com/u/7997966?v=4", + "owner_url": "https://github.com/Neargye", + "slug": "Neargye/magic_enum", + "stars": 6161, + "topics": [ + "c-plus-plus", + "c-plus-plus-17", + "cplusplus", + "cplusplus-17", + "cpp", + "cpp17", + "enum", + "enum-to-string", + "header-only", + "metaprogramming", + "no-dependencies", + "reflection", + "serialization", + "single-file", + "string-to-enum" + ] + } + }, + "https://api.github.com/repos/OpenMathLib/OpenBLAS": { + "at": 1786040383, + "body": { + "description": "OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version. ", + "homepage": "http://www.openblas.net", + "language": "C", + "license": "BSD-3-Clause", + "owner": "OpenMathLib", + "owner_avatar": "https://avatars.githubusercontent.com/u/9943298?v=4", + "owner_url": "https://github.com/OpenMathLib", + "slug": "OpenMathLib/OpenBLAS", + "stars": 7546, + "topics": [ + "blas", + "lapack", + "lapacke" + ] + } + }, + "https://api.github.com/repos/Sunrisepeak/opencv-m": { + "at": 1786040393, + "body": { + "description": "", + "homepage": "", + "language": "C", + "license": "Apache-2.0", + "owner": "Sunrisepeak", + "owner_avatar": "https://avatars.githubusercontent.com/u/38786181?v=4", + "owner_url": "https://github.com/Sunrisepeak", + "slug": "Sunrisepeak/opencv-m", + "stars": 1, + "topics": [] + } + }, + "https://api.github.com/repos/abseil/abseil-cpp": { + "at": 1786040369, + "body": { + "description": "Abseil Common Libraries (C++)", + "homepage": "https://abseil.io", + "language": "C++", + "license": "Apache-2.0", + "owner": "abseil", + "owner_avatar": "https://avatars.githubusercontent.com/u/26718316?v=4", + "owner_url": "https://github.com/abseil", + "slug": "abseil/abseil-cpp", + "stars": 18055, + "topics": [] + } + }, + "https://api.github.com/repos/boost-ext/ut": { + "at": 1786040367, + "body": { + "description": "C++20 μ(micro)/Unit Testing framework", + "homepage": "https://boost-ext.github.io/ut/", + "language": "C++", + "license": "BSL-1.0", + "owner": "boost-ext", + "owner_avatar": "https://avatars.githubusercontent.com/u/16814575?v=4", + "owner_url": "https://github.com/boost-ext", + "slug": "boost-ext/ut", + "stars": 1435, + "topics": [ + "bdd", + "cpp20", + "tdd", + "testing" + ] + } + }, + "https://api.github.com/repos/c-ares/c-ares": { + "at": 1786040370, + "body": { + "description": "A C library for asynchronous DNS requests", + "homepage": "https://c-ares.org/", + "language": "C", + "license": "MIT", + "owner": "c-ares", + "owner_avatar": "https://avatars.githubusercontent.com/u/16745161?v=4", + "owner_url": "https://github.com/c-ares", + "slug": "c-ares/c-ares", + "stars": 2175, + "topics": [ + "async", + "c", + "dns", + "dns-queries", + "library", + "resolver" + ] + } + }, + "https://api.github.com/repos/catchorg/Catch2": { + "at": 1786040371, + "body": { + "description": "A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)", + "homepage": "https://discord.gg/4CWS9zD", + "language": "C++", + "license": "BSL-1.0", + "owner": "catchorg", + "owner_avatar": "https://avatars.githubusercontent.com/u/33321405?v=4", + "owner_url": "https://github.com/catchorg", + "slug": "catchorg/Catch2", + "stars": 21419, + "topics": [ + "bdd", + "cpp", + "cpp14", + "framework", + "no-dependencies", + "tdd", + "test-framework", + "testing" + ] + } + }, + "https://api.github.com/repos/chriskohlhoff/asio": { + "at": 1786040368, + "body": { + "description": "Asio C++ Library", + "homepage": "http://think-async.com/Asio", + "language": "C++", + "license": "NOASSERTION", + "owner": "chriskohlhoff", + "owner_avatar": "https://avatars.githubusercontent.com/u/462538?v=4", + "owner_url": "https://github.com/chriskohlhoff", + "slug": "chriskohlhoff/asio", + "stars": 6153, + "topics": [] + } + }, + "https://api.github.com/repos/curl/curl": { + "at": 1786040373, + "body": { + "description": "A command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features", + "homepage": "https://curl.se/", + "language": "C", + "license": "NOASSERTION", + "owner": "curl", + "owner_avatar": "https://avatars.githubusercontent.com/u/16928085?v=4", + "owner_url": "https://github.com/curl", + "slug": "curl/curl", + "stars": 42542, + "topics": [ + "c", + "client", + "curl", + "ftp", + "gopher", + "hacktoberfest", + "http", + "https", + "imaps", + "ldap", + "libcurl", + "library", + "mqtt", + "pop3", + "scp", + "sftp", + "transfer-data", + "transferring-data", + "user-agent", + "websocket" + ] + } + }, + "https://api.github.com/repos/facebook/zstd": { + "at": 1786040390, + "body": { + "description": "Zstandard - Fast real-time compression algorithm", + "homepage": "http://www.zstd.net", + "language": "C", + "license": "NOASSERTION", + "owner": "facebook", + "owner_avatar": "https://avatars.githubusercontent.com/u/69631?v=4", + "owner_url": "https://github.com/facebook", + "slug": "facebook/zstd", + "stars": 27523, + "topics": [] + } + }, + "https://api.github.com/repos/fmtlib/fmt": { + "at": 1786040391, + "body": { + "description": "A modern formatting library", + "homepage": "https://fmt.dev", + "language": "C++", + "license": "MIT", + "owner": "fmtlib", + "owner_avatar": "https://avatars.githubusercontent.com/u/7280830?v=4", + "owner_url": "https://github.com/fmtlib", + "slug": "fmtlib/fmt", + "stars": 23710, + "topics": [ + "c-plus-plus", + "chrono", + "cpp", + "cross-platform", + "floating-point", + "formatting", + "multiplatform", + "output", + "performance", + "printf", + "ranges", + "unicode" + ] + } + }, + "https://api.github.com/repos/gabime/spdlog": { + "at": 1786040386, + "body": { + "description": "Fast C++ logging library.", + "homepage": "", + "language": "C++", + "license": "NOASSERTION", + "owner": "gabime", + "owner_avatar": "https://avatars.githubusercontent.com/u/6052198?v=4", + "owner_url": "https://github.com/gabime", + "slug": "gabime/spdlog", + "stars": 29443, + "topics": [ + "cpp", + "cpp11", + "header-only", + "logging", + "spdlog" + ] + } + }, + "https://api.github.com/repos/glfw/glfw": { + "at": 1786040375, + "body": { + "description": "A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input", + "homepage": "https://www.glfw.org", + "language": "C", + "license": "Zlib", + "owner": "glfw", + "owner_avatar": "https://avatars.githubusercontent.com/u/3905364?v=4", + "owner_url": "https://github.com/glfw", + "slug": "glfw/glfw", + "stars": 15235, + "topics": [ + "c", + "linux", + "macos", + "opengl", + "opengl-es", + "unix", + "vulkan", + "windows" + ] + } + }, + "https://api.github.com/repos/godotengine/godot-cpp": { + "at": 1786040378, + "body": { + "description": "C++ bindings for the Godot script API", + "homepage": "https://docs.godotengine.org/en/latest/tutorials/scripting/cpp/", + "language": "C++", + "license": "MIT", + "owner": "godotengine", + "owner_avatar": "https://avatars.githubusercontent.com/u/6318500?v=4", + "owner_url": "https://github.com/godotengine", + "slug": "godotengine/godot-cpp", + "stars": 2628, + "topics": [] + } + }, + "https://api.github.com/repos/google/googletest": { + "at": 1786040379, + "body": { + "description": "GoogleTest - Google Testing and Mocking Framework", + "homepage": "https://google.github.io/googletest/", + "language": "C++", + "license": "BSD-3-Clause", + "owner": "google", + "owner_avatar": "https://avatars.githubusercontent.com/u/1342004?v=4", + "owner_url": "https://github.com/google", + "slug": "google/googletest", + "stars": 38905, + "topics": [] + } + }, + "https://api.github.com/repos/ibireme/yyjson": { + "at": 1786040388, + "body": { + "description": "The fastest JSON library in C", + "homepage": "https://ibireme.github.io/yyjson/doc/doxygen/html/", + "language": "C", + "license": "MIT", + "owner": "ibireme", + "owner_avatar": "https://avatars.githubusercontent.com/u/839283?v=4", + "owner_url": "https://github.com/ibireme", + "slug": "ibireme/yyjson", + "stars": 3829, + "topics": [ + "ansi-c", + "c", + "c89", + "cross-platform", + "deserialization", + "json", + "json5", + "portable", + "serialization" + ] + } + }, + "https://api.github.com/repos/libigl/libigl-glad": { + "at": 1786040375, + "body": { + "description": "", + "homepage": "", + "language": "C", + "license": "", + "owner": "libigl", + "owner_avatar": "https://avatars.githubusercontent.com/u/6785468?v=4", + "owner_url": "https://github.com/libigl", + "slug": "libigl/libigl-glad", + "stars": 0, + "topics": [] + } + }, + "https://api.github.com/repos/libsdl-org/SDL": { + "at": 1786040385, + "body": { + "description": "Simple DirectMedia Layer", + "homepage": "https://libsdl.org", + "language": "C", + "license": "Zlib", + "owner": "libsdl-org", + "owner_avatar": "https://avatars.githubusercontent.com/u/77683370?v=4", + "owner_url": "https://github.com/libsdl-org", + "slug": "libsdl-org/SDL", + "stars": 16285, + "topics": [ + "sdl", + "sdl2", + "sdl3" + ] + } + }, + "https://api.github.com/repos/lz4/lz4": { + "at": 1786040381, + "body": { + "description": "Extremely Fast Compression algorithm", + "homepage": "http://www.lz4.org", + "language": "C", + "license": "NOASSERTION", + "owner": "lz4", + "owner_avatar": "https://avatars.githubusercontent.com/u/11667224?v=4", + "owner_url": "https://github.com/lz4", + "slug": "lz4/lz4", + "stars": 11992, + "topics": [ + "c", + "compression", + "lz4" + ] + } + }, + "https://api.github.com/repos/machinezone/IXWebSocket": { + "at": 1786040387, + "body": { + "description": "websocket and http client and server library, with TLS support and very few dependencies", + "homepage": "", + "language": "C++", + "license": "BSD-3-Clause", + "owner": "machinezone", + "owner_avatar": "https://avatars.githubusercontent.com/u/6372053?v=4", + "owner_url": "https://github.com/machinezone", + "slug": "machinezone/IXWebSocket", + "stars": 783, + "topics": [ + "cpp", + "http", + "http-server", + "websocket-client", + "websocket-server", + "websockets" + ] + } + }, + "https://api.github.com/repos/madler/zlib": { + "at": 1786040389, + "body": { + "description": "A massively spiffy yet delicately unobtrusive compression library.", + "homepage": "http://zlib.net/", + "language": "C", + "license": "NOASSERTION", + "owner": "madler", + "owner_avatar": "https://avatars.githubusercontent.com/u/240717?v=4", + "owner_url": "https://github.com/madler", + "slug": "madler/zlib", + "stars": 7002, + "topics": [] + } + }, + "https://api.github.com/repos/marzer/tomlplusplus": { + "at": 1786040392, + "body": { + "description": "Header-only TOML config file parser and serializer for C++17.", + "homepage": "https://marzer.github.io/tomlplusplus/", + "language": "C++", + "license": "MIT", + "owner": "marzer", + "owner_avatar": "https://avatars.githubusercontent.com/u/3958019?v=4", + "owner_url": "https://github.com/marzer", + "slug": "marzer/tomlplusplus", + "stars": 2119, + "topics": [ + "c-plus-plus-17", + "c-plus-plus-20", + "configuration-files", + "cplusplus-17", + "cplusplus-20", + "cpp", + "cpp17", + "cpp20", + "header-only", + "json", + "no-dependencies", + "parser", + "serializer", + "single-file", + "toml", + "toml-parser", + "toml-parsing", + "tomlplusplus", + "unicode", + "utf-8" + ] + } + }, + "https://api.github.com/repos/mcpp-community/mcpp": { + "at": 1786040399, + "body": { + "description": "A modern C++ module-first build tool — written in pure C++23 modules, fully self-hosted / 一个 现代C++ 模块化构建工具 — 纯 C++23 模块编写(已实现自举) ", + "homepage": "", + "language": "C++", + "license": "Apache-2.0", + "owner": "mcpp-community", + "owner_avatar": "https://avatars.githubusercontent.com/u/247994312?v=4", + "owner_url": "https://github.com/mcpp-community", + "slug": "mcpp-community/mcpp", + "stars": 91, + "topics": [ + "build-tool", + "cpp", + "cpp23", + "mcpp", + "toolchain" + ] + } + }, + "https://api.github.com/repos/mcpp-community/mcpp/contributors?per_page=30": { + "at": 1786040400, + "body": [ + { + "avatar": "https://avatars.githubusercontent.com/u/38786181?v=4", + "contributions": 434, + "login": "Sunrisepeak", + "url": "https://github.com/Sunrisepeak" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/248744407?v=4", + "contributions": 30, + "login": "speak-agent", + "url": "https://github.com/speak-agent" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/194236111?v=4", + "contributions": 4, + "login": "ZheFeng7110", + "url": "https://github.com/ZheFeng7110" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/96378453?v=4", + "contributions": 2, + "login": "wellwei", + "url": "https://github.com/wellwei" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/86313530?v=4", + "contributions": 1, + "login": "xv1rcn", + "url": "https://github.com/xv1rcn" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/67222274?v=4", + "contributions": 1, + "login": "HalfAnElephant", + "url": "https://github.com/HalfAnElephant" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/119092375?v=4", + "contributions": 1, + "login": "yizhinailong", + "url": "https://github.com/yizhinailong" + } + ] + }, + "https://api.github.com/repos/mcpp-community/templates": { + "at": 1786040393, + "body": { + "description": "Minimal C++23 modular hello library — first package indexed in mcpp-index", + "homepage": "", + "language": "C++", + "license": "Apache-2.0", + "owner": "mcpp-community", + "owner_avatar": "https://avatars.githubusercontent.com/u/247994312?v=4", + "owner_url": "https://github.com/mcpp-community", + "slug": "mcpp-community/templates", + "stars": 0, + "topics": [] + } + }, + "https://api.github.com/repos/mcpplibs/ffmpeg-m": { + "at": 1786040390, + "body": { + "description": "", + "homepage": "", + "language": "C++", + "license": "MIT", + "owner": "mcpplibs", + "owner_avatar": "https://avatars.githubusercontent.com/u/247994090?v=4", + "owner_url": "https://github.com/mcpplibs", + "slug": "mcpplibs/ffmpeg-m", + "stars": 0, + "topics": [] + } + }, + "https://api.github.com/repos/mcpplibs/mcpp-index": { + "at": 1786040402, + "body": { + "description": "mcpp package index registry — modular C++23 packages for mcpp build tool", + "homepage": "https://mcpplibs.github.io/mcpp-index/", + "language": "Lua", + "license": "NOASSERTION", + "owner": "mcpplibs", + "owner_avatar": "https://avatars.githubusercontent.com/u/247994090?v=4", + "owner_url": "https://github.com/mcpplibs", + "slug": "mcpplibs/mcpp-index", + "stars": 3, + "topics": [] + } + }, + "https://api.github.com/repos/mcpplibs/mcpp-index/commits?per_page=100&page=1": { + "at": 1786040397, + "body": { + "Cloud_Yun|1770669041@qq.com": "yspbwx2010", + "FarnaHerry|108510510+FarnaHerry@users.noreply.github.com": "FarnaHerry", + "SPeak|speakshen@163.com": "Sunrisepeak", + "Yichen Pang|58691509+1115040131@users.noreply.github.com": "1115040131", + "Zhe Feng|zhefeng7110@outlook.com": "ZheFeng7110", + "sunrisepeak|speakshen@163.com": "Sunrisepeak", + "tz12323|2760686250@qq.com": "tz12323", + "wellwei|96378453+wellwei@users.noreply.github.com": "wellwei" + } + }, + "https://api.github.com/repos/mcpplibs/mcpp-index/commits?per_page=100&page=2": { + "at": 1786040398, + "body": { + "SPeak|speakshen@163.com": "Sunrisepeak", + "sunrisepeak|speakshen@163.com": "Sunrisepeak", + "sunrisepeak|x.d2learn.org@gmail.com": "speak-agent", + "子菲鱼|56304358+Aimol-l@users.noreply.github.com": "Aimol-l" + } + }, + "https://api.github.com/repos/mcpplibs/mcpp-index/commits?per_page=100&page=3": { + "at": 1786040399, + "body": {} + }, + "https://api.github.com/repos/mcpplibs/mcpp-index/contributors?per_page=30": { + "at": 1786040403, + "body": [ + { + "avatar": "https://avatars.githubusercontent.com/u/38786181?v=4", + "contributions": 144, + "login": "Sunrisepeak", + "url": "https://github.com/Sunrisepeak" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/108510510?v=4", + "contributions": 5, + "login": "FarnaHerry", + "url": "https://github.com/FarnaHerry" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/194236111?v=4", + "contributions": 5, + "login": "ZheFeng7110", + "url": "https://github.com/ZheFeng7110" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/96378453?v=4", + "contributions": 5, + "login": "wellwei", + "url": "https://github.com/wellwei" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/248744407?v=4", + "contributions": 3, + "login": "speak-agent", + "url": "https://github.com/speak-agent" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/58691509?v=4", + "contributions": 2, + "login": "1115040131", + "url": "https://github.com/1115040131" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/120697095?v=4", + "contributions": 1, + "login": "yspbwx2010", + "url": "https://github.com/yspbwx2010" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/190197873?v=4", + "contributions": 1, + "login": "tz12323", + "url": "https://github.com/tz12323" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/56304358?v=4", + "contributions": 1, + "login": "Aimol-l", + "url": "https://github.com/Aimol-l" + } + ] + }, + "https://api.github.com/repos/mity/md4c": { + "at": 1786040382, + "body": { + "description": "C Markdown parser. Fast. SAX-like interface. Compliant to CommonMark specification.", + "homepage": "", + "language": "C", + "license": "MIT", + "owner": "mity", + "owner_avatar": "https://avatars.githubusercontent.com/u/873269?v=4", + "owner_url": "https://github.com/mity", + "slug": "mity/md4c", + "stars": 1413, + "topics": [ + "c", + "commonmark", + "markdown", + "markdown-parser", + "mit-license", + "parser" + ] + } + }, + "https://api.github.com/repos/nlohmann/json": { + "at": 1786040395, + "body": { + "description": "JSON for Modern C++", + "homepage": "https://json.nlohmann.me", + "language": "C++", + "license": "MIT", + "owner": "nlohmann", + "owner_avatar": "https://avatars.githubusercontent.com/u/159488?v=4", + "owner_url": "https://github.com/nlohmann", + "slug": "nlohmann/json", + "stars": 50309, + "topics": [ + "bson", + "cbor", + "header-only", + "json", + "json-diff", + "json-merge-patch", + "json-parser", + "json-patch", + "json-pointer", + "json-serialization", + "messagepack", + "msgpack", + "rfc-6901", + "rfc-6902", + "rfc-7049", + "rfc-7159", + "rfc-8259", + "stl-containers", + "ubjson" + ] + } + }, + "https://api.github.com/repos/ocornut/imgui": { + "at": 1786040379, + "body": { + "description": "Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies", + "homepage": "", + "language": "C++", + "license": "MIT", + "owner": "ocornut", + "owner_avatar": "https://avatars.githubusercontent.com/u/8225057?v=4", + "owner_url": "https://github.com/ocornut", + "slug": "ocornut/imgui", + "stars": 75531, + "topics": [ + "api", + "cplusplus", + "framework", + "game-development", + "game-engine", + "gamedev", + "gui", + "imgui", + "immediate-gui", + "library", + "multi-platform", + "native", + "toolkit", + "tools", + "ui" + ] + } + }, + "https://api.github.com/repos/openssl/openssl": { + "at": 1786040383, + "body": { + "description": "General purpose TLS and crypto library", + "homepage": "https://openssl-library.org/", + "language": "C", + "license": "Apache-2.0", + "owner": "openssl", + "owner_avatar": "https://avatars.githubusercontent.com/u/3279138?v=4", + "owner_url": "https://github.com/openssl", + "slug": "openssl/openssl", + "stars": 30568, + "topics": [ + "cryptography", + "decryption", + "encryption", + "openssl", + "ssl", + "tls" + ] + } + }, + "https://api.github.com/repos/openxlings/libxpkg": { + "at": 1786040394, + "body": { + "description": "xpkg 规范 的 C++23 标准库实现", + "homepage": "https://github.com/d2learn/xlings", + "language": "C++", + "license": "Apache-2.0", + "owner": "openxlings", + "owner_avatar": "https://avatars.githubusercontent.com/u/266796665?v=4", + "owner_url": "https://github.com/openxlings", + "slug": "openxlings/libxpkg", + "stars": 2, + "topics": [] + } + }, + "https://api.github.com/repos/openxlings/xlings": { + "at": 1786040401, + "body": { + "description": "Universal package infrastructure with OS-like SubOS isolation - Multi-version · Rootless · Decentralized Index · Agent-ready.", + "homepage": "https://openxlings.github.io", + "language": "C++", + "license": "Apache-2.0", + "owner": "openxlings", + "owner_avatar": "https://avatars.githubusercontent.com/u/266796665?v=4", + "owner_url": "https://github.com/openxlings", + "slug": "openxlings/xlings", + "stars": 599, + "topics": [ + "ai", + "auto-configuration", + "auto-detect", + "d2x", + "demos", + "installer", + "llm-application", + "package-manager", + "project-management", + "tools", + "version-manager", + "xlings" + ] + } + }, + "https://api.github.com/repos/openxlings/xlings/contributors?per_page=30": { + "at": 1786040402, + "body": [ + { + "avatar": "https://avatars.githubusercontent.com/u/38786181?v=4", + "contributions": 619, + "login": "Sunrisepeak", + "url": "https://github.com/Sunrisepeak" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/248744407?v=4", + "contributions": 25, + "login": "speak-agent", + "url": "https://github.com/speak-agent" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/64307394?v=4", + "contributions": 6, + "login": "MoYingJi", + "url": "https://github.com/MoYingJi" + }, + { + "avatar": "https://avatars.githubusercontent.com/in/1143301?v=4", + "contributions": 1, + "login": "Copilot", + "url": "https://github.com/apps/copilot-swe-agent" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/33739170?v=4", + "contributions": 1, + "login": "zzxzzk115", + "url": "https://github.com/zzxzzk115" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/194236111?v=4", + "contributions": 1, + "login": "ZheFeng7110", + "url": "https://github.com/ZheFeng7110" + } + ] + }, + "https://api.github.com/repos/pnggroup/libpng": { + "at": 1786040380, + "body": { + "description": "LIBPNG: Portable Network Graphics support, official libpng repository", + "homepage": "http://libpng.sf.net", + "language": "C", + "license": "NOASSERTION", + "owner": "pnggroup", + "owner_avatar": "https://avatars.githubusercontent.com/u/48779779?v=4", + "owner_url": "https://github.com/pnggroup", + "slug": "pnggroup/libpng", + "stars": 1637, + "topics": [] + } + }, + "https://api.github.com/repos/protocolbuffers/protobuf": { + "at": 1786040384, + "body": { + "description": "Protocol Buffers - Google's data interchange format", + "homepage": "http://protobuf.dev", + "language": "C++", + "license": "NOASSERTION", + "owner": "protocolbuffers", + "owner_avatar": "https://avatars.githubusercontent.com/u/26310541?v=4", + "owner_url": "https://github.com/protocolbuffers", + "slug": "protocolbuffers/protobuf", + "stars": 71693, + "topics": [ + "marshalling", + "protobuf", + "protobuf-runtime", + "protoc", + "protocol-buffers", + "protocol-compiler", + "rpc", + "serialization" + ] + } + }, + "https://api.github.com/repos/sudoevolve/EUI-NEO": { + "at": 1786040373, + "body": { + "description": "EUI-NEO is a cross-platform, high-performance, low-overhead C++17 GPUI framework", + "homepage": "", + "language": "C", + "license": "Apache-2.0", + "owner": "sudoevolve", + "owner_avatar": "https://avatars.githubusercontent.com/u/147291183?v=4", + "owner_url": "https://github.com/sudoevolve", + "slug": "sudoevolve/EUI-NEO", + "stars": 1663, + "topics": [] + } + }, + "https://api.github.com/repos/tukaani-project/xz": { + "at": 1786040388, + "body": { + "description": "XZ Utils", + "homepage": "https://tukaani.org/xz/", + "language": "C", + "license": "NOASSERTION", + "owner": "tukaani-project", + "owner_avatar": "https://avatars.githubusercontent.com/u/116083088?v=4", + "owner_url": "https://github.com/tukaani-project", + "slug": "tukaani-project/xz", + "stars": 1629, + "topics": [ + "c", + "cli", + "compression", + "library" + ] + } + }, + "https://api.github.com/repos/zserge/tray": { + "at": 1786040386, + "body": { + "description": "Cross-platform, super tiny C99 implementation of a system tray icon with a popup menu.", + "homepage": "", + "language": "C", + "license": "MIT", + "owner": "zserge", + "owner_avatar": "https://avatars.githubusercontent.com/u/1911760?v=4", + "owner_url": "https://github.com/zserge", + "slug": "zserge/tray", + "stars": 576, + "topics": [ + "appindicator", + "cross-platform", + "gtk", + "tray-icon", + "tray-menu" + ] + } + }, + "https://raw.githubusercontent.com/Aimol-l/Tensorvia-cpu/v0.1.1/mcpp.toml": { + "at": 1786040356, + "body": { + "deps": [], + "description": "CPU backend of Tensorvia, ported to C++23 modules", + "language": "c++23", + "license": "MIT", + "modules": [ + "tensorvia" + ], + "modules_path": "src/tensorvia.cppm", + "modules_source": "export", + "name": "tensorvia-cpu", + "namespace": "", + "platforms": [ + "linux", + "macos", + "windows" + ], + "sources": [ + "src/tensorvia.cppm" + ], + "targets": { + "tensorvia-cpu": { + "kind": "lib" + } + }, + "version": "0.1.1" + } + }, + "https://raw.githubusercontent.com/Aimol-l/Tensorvia-cpu/v0.1.1/src/tensorvia.cppm": { + "at": 1786040356, + "body": "tensorvia" + }, + "https://raw.githubusercontent.com/Sunrisepeak/opencv-m/v0.0.10/mcpp.toml": { + "at": 1786040363, + "body": { + "deps": [ + "compat" + ], + "description": "OpenCV 5.0.0 vendored + C++23 module layer — import opencv.cv; (API and habits unchanged, just no headers). Single repo: full source build carried by this manifest, frozen per-OS config snapshots in gen/, no compat.opencv dependency.", + "language": "", + "license": "MIT", + "modules": [ + "opencv.cv" + ], + "modules_source": "description", + "name": "opencv", + "namespace": "", + "platforms": [], + "sources": [ + "src/opencv.cppm", + "src/cv.cppm", + "src/core.cppm", + "src/imgproc.cppm", + "src/imgcodecs.cppm", + "src/videoio.cppm", + "src/highgui.cppm", + "src/flann.cppm", + "src/geometry.cppm" + ], + "targets": { + "opencv": { + "kind": "lib" + } + }, + "version": "0.0.10" + } + }, + "https://raw.githubusercontent.com/Sunrisepeak/opencv-m/v5.0.0/mcpp.toml": { + "at": 1786040366, + "body": { + "deps": [ + "compat" + ], + "description": "OpenCV 5.0.0 vendored + C++23 module layer — import opencv.cv; (API and habits unchanged, just no headers). Single repo: full source build carried by this manifest, frozen per-OS config snapshots in gen/, no compat.opencv dependency.", + "language": "", + "license": "MIT", + "modules": [ + "opencv.cv" + ], + "modules_source": "description", + "name": "opencv", + "namespace": "opencv", + "platforms": [], + "sources": [ + "src/opencv.cppm", + "src/cv.cppm", + "src/core.cppm", + "src/imgproc.cppm", + "src/imgcodecs.cppm", + "src/videoio.cppm", + "src/highgui.cppm", + "src/flann.cppm", + "src/geometry.cppm" + ], + "targets": { + "opencv": { + "kind": "lib" + } + }, + "version": "5.0.0" + } + }, + "https://raw.githubusercontent.com/mcpp-community/templates/v0.0.1/mcpp.toml": { + "at": 1786040363, + "body": { + "deps": [], + "description": "Minimal C++23 modular hello library (mcpp port of mcpplibs/templates)", + "language": "", + "license": "Apache-2.0", + "modules": [ + "mcpplibs.templates" + ], + "modules_path": "src/templates.cppm", + "modules_source": "export", + "name": "mcpplibs.templates", + "namespace": "", + "platforms": [], + "sources": [], + "targets": { + "templates": { + "kind": "lib" + } + }, + "version": "0.0.1" + } + }, + "https://raw.githubusercontent.com/mcpp-community/templates/v0.0.1/src/templates.cppm": { + "at": 1786040363, + "body": "mcpplibs.templates" + }, + "https://raw.githubusercontent.com/mcpplibs/ffmpeg-m/v0.0.3/mcpp.toml": { + "at": 1786040360, + "body": { + "deps": [ + "compat" + ], + "description": "FFmpeg C++ modules for mcpp (import ffmpeg.av;) on top of the compat.ffmpeg source build", + "language": "", + "license": "MIT", + "modules": [ + "ffmpeg.av" + ], + "modules_source": "description", + "name": "ffmpeg", + "namespace": "", + "platforms": [ + "linux", + "macos", + "windows" + ], + "sources": [ + "src/**/*.cppm" + ], + "targets": { + "ffmpeg": { + "kind": "lib" + } + }, + "version": "0.0.3" + } + }, + "https://raw.githubusercontent.com/mcpplibs/ffmpeg-m/v8.1.2/mcpp.toml": { + "at": 1786040356, + "body": { + "deps": [ + "compat" + ], + "description": "FFmpeg 8.1.2 C++ modules for mcpp (import ffmpeg.av;) on top of the compat.ffmpeg source build", + "language": "", + "license": "MIT", + "modules": [ + "ffmpeg.av" + ], + "modules_source": "description", + "name": "ffmpeg", + "namespace": "ffmpeg", + "platforms": [ + "linux", + "macos", + "windows" + ], + "sources": [ + "src/**/*.cppm" + ], + "targets": { + "ffmpeg": { + "kind": "lib" + } + }, + "version": "8.1.2" + } + }, + "https://raw.githubusercontent.com/mcpplibs/godot-cpp-m/v10.0.0-rc1/mcpp.toml": { + "at": 1786040358, + "body": { + "deps": [ + "compat" + ], + "description": "godot-cpp as a C++23 module (import godot_cpp;) on top of the compat.godot-cpp source build", + "language": "", + "license": "MIT", + "modules": [ + "godot_cpp" + ], + "modules_source": "description", + "name": "godot-cpp-m", + "namespace": "", + "platforms": [ + "linux", + "macos", + "windows" + ], + "sources": [ + "src/**/*.cppm" + ], + "targets": { + "godot-cpp-m": { + "kind": "lib" + } + }, + "version": "10.0.0-rc1" + } + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/mcpp.toml": { + "at": 1786040358, + "body": { + "deps": [ + "compat" + ], + "description": "gRPC 1.83.0 for mcpp — upstream C++ source vendored and built by this manifest, with abseil / protobuf / re2 / c-ares / OpenSSL taken from mcpp-index instead of re-vendored.", + "language": "c++23", + "license": "Apache-2.0", + "modules": [ + "grpc" + ], + "modules_path": "src/grpc.cppm", + "modules_source": "export", + "name": "grpc", + "namespace": "", + "platforms": [ + "linux", + "macos" + ], + "sources": [ + "src/grpc.cppm", + "third_party/grpc-1.83.0/src/core/config/config_vars.cc", + "third_party/grpc-1.83.0/src/core/config/config_vars_non_generated.cc", + "third_party/grpc-1.83.0/src/core/config/load_config.cc", + "third_party/grpc-1.83.0/src/core/lib/event_engine/thread_local.cc", + "third_party/grpc-1.83.0/src/core/util/alloc.cc", + "third_party/grpc-1.83.0/src/core/util/crash.cc", + "third_party/grpc-1.83.0/src/core/util/examine_stack.cc", + "third_party/grpc-1.83.0/src/core/util/fork.cc", + "third_party/grpc-1.83.0/src/core/util/gpr_time.cc", + "third_party/grpc-1.83.0/src/core/util/host_port.cc", + "third_party/grpc-1.83.0/src/core/util/iphone/cpu.cc", + "third_party/grpc-1.83.0/src/core/util/linux/cpu.cc", + "third_party/grpc-1.83.0/src/core/util/linux/env.cc", + "third_party/grpc-1.83.0/src/core/util/log.cc", + "third_party/grpc-1.83.0/src/core/util/mpscq.cc", + "third_party/grpc-1.83.0/src/core/util/msys/tmpfile.cc", + "third_party/grpc-1.83.0/src/core/util/posix/cpu.cc", + "third_party/grpc-1.83.0/src/core/util/posix/env.cc", + "third_party/grpc-1.83.0/src/core/util/posix/stat.cc" + ], + "targets": { + "grpc": { + "kind": "lib" + } + }, + "version": "1.83.0" + } + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/plugin/mcpp.toml": { + "at": 1786040359, + "body": { + "deps": [ + "compat" + ], + "description": "grpc_cpp_plugin — the gRPC C++ code generator, buildable as an mcpp host tool", + "language": "c++23", + "license": "Apache-2.0", + "modules": [], + "modules_source": "", + "name": "grpc-plugin", + "namespace": "", + "platforms": [ + "linux", + "macos", + "windows" + ], + "sources": [ + "src/compiler/cpp_generator.cc", + "src/compiler/proto_parser_helper.cc" + ], + "targets": { + "grpc_cpp_plugin": { + "kind": "bin" + } + }, + "version": "1.83.0" + } + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/plugin/src/grpc-plugin.cppm": { + "at": 1786040359, + "body": null + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/plugin/src/grpc_cpp_plugin.cppm": { + "at": 1786040359, + "body": null + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/rules/mcpp.toml": { + "at": 1786040360, + "body": { + "deps": [], + "description": "protoc + grpc_cpp_plugin codegen as an importable mcpp build rule", + "language": "c++23", + "license": "Apache-2.0", + "modules": [ + "grpcgen" + ], + "modules_path": "src/grpcgen.cppm", + "modules_source": "export", + "name": "grpcgen", + "namespace": "", + "platforms": [ + "linux", + "macos", + "windows" + ], + "sources": [], + "targets": { + "grpcgen": { + "kind": "lib" + } + }, + "version": "1.83.0" + } + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/rules/src/grpcgen.cppm": { + "at": 1786040360, + "body": "grpcgen" + }, + "https://raw.githubusercontent.com/mcpplibs/grpc-m/v1.83.0-3/src/grpc.cppm": { + "at": 1786040358, + "body": "grpc" + }, + "https://raw.githubusercontent.com/mcpplibs/imgui-m/0.0.6/mcpp.toml": { + "at": 1786040361, + "body": { + "deps": [ + "compat" + ], + "description": "Dear ImGui core and GLFW/OpenGL3 backend modules for mcpp", + "language": "", + "license": "MIT", + "modules": [ + "imgui.core" + ], + "modules_path": "src/core.cppm", + "modules_source": "export", + "name": "imgui", + "namespace": "", + "platforms": [], + "sources": [ + "src/core.cppm", + "src/core.cppm", + "src/app.cppm", + "src/backends/backend.cppm", + "src/backends/platform_glfw.cppm", + "src/backends/renderer_opengl3.cppm", + "src/backends/glfw_opengl3.cppm", + "src/backends/headless.cppm", + "src/backends/glfw_impl.cpp", + "src/backends/opengl3_impl.cpp" + ], + "targets": { + "imgui": { + "kind": "lib" + } + }, + "version": "0.0.6" + } + }, + "https://raw.githubusercontent.com/mcpplibs/imgui-m/0.0.6/src/core.cppm": { + "at": 1786040361, + "body": "imgui.core" + }, + "https://raw.githubusercontent.com/mcpplibs/imgui-m/1.92.8/mcpp.toml": { + "at": 1786040365, + "body": { + "deps": [ + "compat" + ], + "description": "Dear ImGui 1.92.8 core and GLFW/OpenGL3 backend modules for mcpp", + "language": "", + "license": "MIT", + "modules": [ + "imgui.core" + ], + "modules_path": "src/core.cppm", + "modules_source": "export", + "name": "imgui", + "namespace": "ocornut", + "platforms": [], + "sources": [ + "src/core.cppm", + "src/core.cppm", + "src/backends/backend.cppm", + "src/backends/platform_glfw.cppm", + "src/backends/renderer_opengl3.cppm", + "src/backends/glfw_opengl3.cppm", + "src/backends/headless.cppm", + "src/backends/glfw_impl.cpp", + "src/backends/opengl3_impl.cpp" + ], + "targets": { + "imgui": { + "kind": "lib" + } + }, + "version": "1.92.8" + } + }, + "https://raw.githubusercontent.com/mcpplibs/imgui-m/1.92.8/src/core.cppm": { + "at": 1786040365, + "body": "imgui.core" + }, + "https://raw.githubusercontent.com/mcpplibs/llama.cpp-m/b10069/mcpp.toml": { + "at": 1786040357, + "body": { + "deps": [], + "description": "C++23 module package for llama.cpp b10069", + "language": "c++23", + "license": "MIT", + "modules": [ + "llamacpp" + ], + "modules_path": "src/llamacpp.cppm", + "modules_source": "export", + "name": "llamacpp", + "namespace": "ggml-org", + "platforms": [], + "sources": [ + "third_party/llama.cpp/ggml/src/ggml.c", + "generated/ggml_cpp.cpp", + "third_party/llama.cpp/ggml/src/ggml-alloc.c", + "third_party/llama.cpp/ggml/src/ggml-backend.cpp", + "third_party/llama.cpp/ggml/src/ggml-backend-meta.cpp", + "third_party/llama.cpp/ggml/src/ggml-opt.cpp", + "third_party/llama.cpp/ggml/src/ggml-threading.cpp", + "third_party/llama.cpp/ggml/src/ggml-quants.c", + "third_party/llama.cpp/ggml/src/gguf.cpp", + "third_party/llama.cpp/ggml/src/ggml-backend-dl.cpp", + "third_party/llama.cpp/ggml/src/ggml-backend-reg.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c", + "generated/ggml-cpu_cpp.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/binary-ops.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/hbm.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/ops.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/quants.c", + "third_party/llama.cpp/ggml/src/ggml-cpu/repack.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/traits.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/unary-ops.cpp" + ], + "targets": { + "llama": { + "kind": "lib" + } + }, + "version": "b10069" + } + }, + "https://raw.githubusercontent.com/mcpplibs/llama.cpp-m/b10069/src/llama.cppm": { + "at": 1786040357, + "body": null + }, + "https://raw.githubusercontent.com/mcpplibs/llama.cpp-m/b10069/src/llamacpp.cppm": { + "at": 1786040357, + "body": "llamacpp" + }, + "https://raw.githubusercontent.com/mcpplibs/llama.cpp-m/v0.1.0/mcpp.toml": { + "at": 1786040361, + "body": { + "deps": [], + "description": "C++23 module package for llama.cpp b10069", + "language": "c++23", + "license": "MIT", + "modules": [ + "llamacpp" + ], + "modules_path": "src/llamacpp.cppm", + "modules_source": "export", + "name": "llamacpp", + "namespace": "", + "platforms": [], + "sources": [ + "third_party/llama.cpp/ggml/src/ggml.c", + "generated/ggml_cpp.cpp", + "third_party/llama.cpp/ggml/src/ggml-alloc.c", + "third_party/llama.cpp/ggml/src/ggml-backend.cpp", + "third_party/llama.cpp/ggml/src/ggml-backend-meta.cpp", + "third_party/llama.cpp/ggml/src/ggml-opt.cpp", + "third_party/llama.cpp/ggml/src/ggml-threading.cpp", + "third_party/llama.cpp/ggml/src/ggml-quants.c", + "third_party/llama.cpp/ggml/src/gguf.cpp", + "third_party/llama.cpp/ggml/src/ggml-backend-dl.cpp", + "third_party/llama.cpp/ggml/src/ggml-backend-reg.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c", + "generated/ggml-cpu_cpp.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/binary-ops.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/hbm.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/ops.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/quants.c", + "third_party/llama.cpp/ggml/src/ggml-cpu/repack.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/traits.cpp", + "third_party/llama.cpp/ggml/src/ggml-cpu/unary-ops.cpp" + ], + "targets": { + "llama": { + "kind": "lib" + } + }, + "version": "0.1.0" + } + }, + "https://raw.githubusercontent.com/mcpplibs/llama.cpp-m/v0.1.0/src/llama.cppm": { + "at": 1786040362, + "body": null + }, + "https://raw.githubusercontent.com/mcpplibs/llama.cpp-m/v0.1.0/src/llamacpp.cppm": { + "at": 1786040362, + "body": "llamacpp" + }, + "https://raw.githubusercontent.com/mcpplibs/llmapi/0.2.8/mcpp.toml": { + "at": 1786040362, + "body": { + "deps": [ + "mcpplibs" + ], + "description": "Modern C++ LLM API client with openai-compatible support", + "language": "", + "license": "Apache-2.0", + "modules": [ + "mcpplibs.llmapi" + ], + "modules_path": "src/llmapi.cppm", + "modules_source": "export", + "name": "llmapi", + "namespace": "mcpplibs", + "platforms": [], + "sources": [], + "targets": { + "llmapi": { + "kind": "lib" + } + }, + "version": "0.2.8" + } + }, + "https://raw.githubusercontent.com/mcpplibs/llmapi/0.2.8/src/llmapi.cppm": { + "at": 1786040363, + "body": "mcpplibs.llmapi" + }, + "https://raw.githubusercontent.com/mcpplibs/lua/0.0.3/mcpp.toml": { + "at": 1786040359, + "body": { + "deps": [ + "lua" + ], + "description": "C++23 module wrapping the Lua 5.4 C API — `import mcpplibs.capi.lua;`", + "language": "", + "license": "Apache-2.0", + "modules": [ + "mcpplibs.capi.lua" + ], + "modules_source": "description", + "name": "mcpplibs.capi.lua", + "namespace": "", + "platforms": [], + "sources": [ + "src/capi/lua.cppm" + ], + "targets": { + "capi-lua": { + "kind": "lib" + }, + "mcpplibs.capi.lua": { + "kind": "lib" + } + }, + "version": "0.0.3" + } + }, + "https://raw.githubusercontent.com/mcpplibs/tinyhttps/0.2.9/mcpp.toml": { + "at": 1786040364, + "body": { + "deps": [ + "compat" + ], + "description": "Minimal C++23 HTTP/HTTPS client with SSE streaming support", + "language": "", + "license": "Apache-2.0", + "modules": [ + "mcpplibs.tinyhttps" + ], + "modules_path": "src/tinyhttps.cppm", + "modules_source": "export", + "name": "tinyhttps", + "namespace": "mcpplibs", + "platforms": [], + "sources": [], + "targets": { + "tinyhttps": { + "kind": "lib" + } + }, + "version": "0.2.9" + } + }, + "https://raw.githubusercontent.com/mcpplibs/tinyhttps/0.2.9/src/tinyhttps.cppm": { + "at": 1786040364, + "body": "mcpplibs.tinyhttps" + }, + "https://raw.githubusercontent.com/openxlings/libxpkg/0.0.53/mcpp.toml": { + "at": 1786040364, + "body": { + "deps": [ + "mcpplibs.capi.lua" + ], + "description": "C++23 reference implementation of the xpkg V2 spec (multi-arch)", + "language": "", + "license": "Apache-2.0", + "modules": [ + "mcpplibs.xpkg" + ], + "modules_path": "src/xpkg.cppm", + "modules_source": "export", + "name": "xpkg", + "namespace": "mcpplibs", + "platforms": [], + "sources": [], + "targets": { + "xpkg": { + "kind": "lib" + } + }, + "version": "0.0.53" + } + }, + "https://raw.githubusercontent.com/openxlings/libxpkg/0.0.53/src/xpkg.cppm": { + "at": 1786040365, + "body": "mcpplibs.xpkg" + } + }, + "fetched_at": 1786040403, + "schema": 1 +} \ No newline at end of file diff --git a/.xpkgindex/interfaces.json b/.xpkgindex/interfaces.json new file mode 100644 index 00000000..8efae442 --- /dev/null +++ b/.xpkgindex/interfaces.json @@ -0,0 +1,27 @@ +{ + "_note": [ + "Hand-curated consumption lines for packages where no rule can reach the", + "answer: the header name simply is not derivable from the package name", + "(abseil -> absl/…, bzip2 -> bzlib.h, xz -> lzma.h), and the test project", + "that uses them covers several packages at once so the file cannot be", + "attributed automatically either.", + "", + "Every line below was taken from this repository's own tests/examples,", + "i.e. from code CI compiles — not written from memory. Keys are package", + "ids; a value may be one line or a list of lines. Curated entries win over", + "every derivation, so keep them correct.", + "", + "Packages deliberately absent: the X11/XCB/Vulkan/GLX protocol and runtime", + "packages are transitive dependencies that no example includes directly,", + "so they show no consumption line rather than an invented one." + ], + + "interfaces": { + "compat.abseil": "#include \"absl/strings/str_cat.h\"", + "compat.bzip2": "#include ", + "compat.xz": "#include ", + "compat.ffmpeg": "#include ", + "compat.freetype": "#include ", + "compat.openblas": "#include " + } +} diff --git a/.xpkgindex/plugins/mcpp.py b/.xpkgindex/plugins/mcpp.py new file mode 100644 index 00000000..889211ae --- /dev/null +++ b/.xpkgindex/plugins/mcpp.py @@ -0,0 +1,899 @@ +"""mcpp ecosystem plugin for xpkgindex. + +Everything mcpp-specific about this index lives here: how a package is named, +how it is consumed (`import` / `#include` / a tool binary), what the `mcpp = {}` +extension block means, and which test project demonstrates it. + +The core framework knows none of that — see +`.agents/docs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md` §5. +""" + +from __future__ import annotations + +import glob +import json +import os +import re +from typing import Any, Dict, List, Optional + +from xpkgindex.models import Block, Facet, FacetValue, Identity, RowSpec +from xpkgindex.plugins import Plugin + +try: # py3.11+ + import tomllib +except ModuleNotFoundError: # pragma: no cover + tomllib = None + +# How a package is consumed. This is the axis a C++ user actually browses by, +# and unlike `categories` (which no descriptor in this repo sets) it is +# derivable from data that is already there. +SURFACES = [ + ("module", "import", "module"), + ("header", "#include", "header"), + ("tool", "tool", "tool"), + ("external", "upstream mcpp.toml", "neutral"), +] + +_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)$") +# Release assets carry the tag in the path instead — `tensorvia-cpu` publishes +# that way, and matching only archive URLs left it with no upstream data. +_RELEASE_TAG_RE = re.compile(r"/releases/download/([^/]+)/") +_IMPORT_IN_TEXT = re.compile(r"\bimport\s+([A-Za-z_][\w.]*)\s*;") +_EXPORT_MODULE_RE = re.compile(r"^\s*export\s+module\s+([A-Za-z_][\w.]*)\s*;", re.M) + + +def _project_export_module(text: str) -> str: + """The module a `.cppm` interface unit exports — the authoritative name.""" + m = _EXPORT_MODULE_RE.search(text) + return m.group(1) if m else "" + +# mcpp resolves a bare package name in this namespace (issue #170's +# "tried: (mcpplibs, json)"). +DEFAULT_NAMESPACE = "mcpplibs" + + +def _parse_repo(url: str): + """-> (host, owner, name) for a repository URL.""" + parts = [p for p in (url or "").split("//")[-1].split("/") if p] + if len(parts) < 3: + return ("", "", "") + name = parts[2][:-4] if parts[2].endswith(".git") else parts[2] + return (parts[0], parts[1], name) + + +def _project_manifest(text: str) -> Dict[str, Any]: + """Keep only the manifest fields the site renders. + + The cache is committed and reviewed in PRs, so it stores this projection + rather than whole upstream files. + """ + data: Dict[str, Any] = {} + if tomllib is not None: + try: + data = tomllib.loads(text) + except Exception: # noqa: BLE001 - upstream may ship anything + data = {} + if not data: + data = _manifest_fallback(text) + + pkg = data.get("package") or {} + targets = data.get("targets") or {} + build = data.get("build") or {} + description = str(pkg.get("description") or "") + + # `modules` must be a list — `[modules]` is a *table* in some manifests and + # picking up its keys produced `import sources;`. + declared = pkg.get("modules") or data.get("modules") or [] + modules = [str(m) for m in declared] if isinstance(declared, list) else [] + source = "declared" if modules else "" + + # Neither `[package] name` nor the target name is the module name: + # godot-cpp-m is named `godot-cpp-m` in both places and imported as + # `godot_cpp`. The manifests that know say so in their own description, so + # quote that instead of deriving a name that would be wrong. + if not modules: + m = _IMPORT_IN_TEXT.search(description) + if m: + modules = [m.group(1)] + source = "description" + + sources = build.get("sources") or [] + sources = [str(s) for s in sources] if isinstance(sources, list) else [] + + # `[lib] path = "src/x.cppm"` is the other way a manifest names its + # library — tensorvia-cpu uses it instead of `[targets.x]`, and reading + # only one shape left that package with no upstream data at all. + lib = data.get("lib") or {} + lib_path = str(lib.get("path") or "") + if lib_path: + sources.insert(0, lib_path) + targets = dict(targets) + targets.setdefault(str(pkg.get("name") or "lib"), {"kind": "lib"}) + + # The cache stores facts from the manifest, not conclusions drawn from + # them: `_is_modular` runs at build time, so its rules can change without + # a network round trip to re-derive them for every package. + return { + "modules": modules, + "modules_source": source, + "sources": sources[:20], + "name": str(pkg.get("name") or ""), + "namespace": str(pkg.get("namespace") or ""), + "targets": {k: {"kind": str((v or {}).get("kind", ""))} + for k, v in targets.items() if isinstance(v, dict)}, + "language": str(pkg.get("language") or pkg.get("standard") or ""), + "version": str(pkg.get("version") or ""), + "description": description, + "license": str(pkg.get("license") or ""), + "platforms": [str(p) for p in (pkg.get("platforms") or []) + if isinstance(pkg.get("platforms"), list)], + "deps": [str(k) for k in (data.get("dependencies") or {}).keys()], + } + + +def _collect_dependencies(data: Any) -> Dict[str, Any]: + """Every `dependencies` table in a manifest, at any nesting depth. + + Merged rather than replaced: a package may appear only under one + platform's gate, and for "which test demonstrates this package" the gate + does not matter. + """ + out: Dict[str, Any] = {} + + def walk(node: Any) -> None: + if not isinstance(node, dict): + return + for key, value in node.items(): + if key in ("dependencies", "dev-dependencies") and isinstance(value, dict): + for dep_key, dep_val in value.items(): + if isinstance(dep_val, dict) and isinstance(out.get(dep_key), dict): + out[dep_key].update(dep_val) + else: + out[dep_key] = dep_val + elif isinstance(value, dict): + walk(value) + + walk(data) + return out + + +def _is_modular(manifest: Dict[str, Any]) -> bool: + """Is this upstream package consumed with `import`? + + Form A means the upstream itself is an mcpp project, and mcpp is + modules-first: a `lib`/`shared` target there is a module library even when + the manifest never spells the module name out. The C-source packages that + are only ever `#include`d live in this index as Form B `compat.*` entries + instead, so they do not reach this function. + """ + if manifest.get("modules"): + return True + if any(".cppm" in s for s in manifest.get("sources") or []): + return True + if "import " in (manifest.get("description") or ""): + return True + kinds = {(t or {}).get("kind", "") for t in (manifest.get("targets") or {}).values()} + return bool(kinds & {"lib", "shared"}) + + +def _manifest_fallback(text: str) -> Dict[str, Any]: + """Enough of a TOML reader for `[package]` and `[targets.x]` on 3.9/3.10.""" + out: Dict[str, Any] = {"package": {}, "targets": {}, "dependencies": {}} + section = "" + for line in text.splitlines(): + line = line.split("#", 1)[0].strip() + if line.startswith("[") and line.endswith("]"): + section = line[1:-1] + if section.startswith("targets."): + out["targets"][section.split(".", 1)[1]] = {} + continue + if "=" not in line: + continue + key, _, value = line.partition("=") + key, value = key.strip().strip('"'), value.strip().strip('"') + if section == "package": + out["package"][key] = value + elif section.startswith("targets."): + out["targets"][section.split(".", 1)[1]][key] = value + elif section == "dependencies": + out["dependencies"][key] = value + return out + + +def _read_json(path: str) -> Dict[str, Any]: + if not os.path.isfile(path): + return {} + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + except Exception: # noqa: BLE001 - a broken override file + return {} # must not take the whole site down + return data if isinstance(data, dict) else {} + + +def _line_kind(code: str): + """-> (label, tone) inferred from the shape of the line itself.""" + if code.startswith("import "): + return "import", "module" + if code.startswith("#include"): + return "#include", "header" + return "tool", "tool" + + +def _read_toml(path: str) -> Dict[str, Any]: + if not os.path.isfile(path): + return {} + if tomllib is not None: + try: + with open(path, "rb") as f: + return tomllib.load(f) + except Exception: # noqa: BLE001 + return {} + return _read_toml_fallback(path) + + +def _read_toml_fallback(path: str) -> Dict[str, Any]: + """Minimal `[dependencies.]` reader for interpreters without tomllib.""" + out: Dict[str, Any] = {"dependencies": {}} + section = None + with open(path, "r", encoding="utf-8", errors="replace") as f: + for line in f: + line = line.strip() + if line.startswith("[") and line.endswith("]"): + section = line[1:-1] + continue + if not section or "=" not in line or section.split(".")[0] != "dependencies": + continue + key = line.split("=", 1)[0].strip().strip('"') + parts = section.split(".", 1) + if len(parts) == 2: + out["dependencies"].setdefault(parts[1], {})[key] = "" + else: + out["dependencies"][key] = "" + return out + + +class McppPlugin(Plugin): + api_version = 1 + name = "mcpp" + + def __init__(self) -> None: + self.root = "" + self.examples: Dict[str, List[Dict[str, str]]] = {} + self.overrides: Dict[str, Any] = {} + + # ------------------------------------------------------------ index -- + def on_index(self, ctx) -> None: + self.root = ctx.root + + # A descriptor without `namespace` resolves under mcpp's default one — + # that is what "tried: (mcpplibs, json)" in issue #170 refers to. + ctx.meta.set("default_namespace", "mcpplibs") + + index_toml = _read_toml(ctx.path("index.toml")).get("index", {}) + for key in ("spec", "min_mcpp", "latest_mcpp"): + if index_toml.get(key): + ctx.meta.set(key, index_toml[key]) + + # Hand-curated interface lines, for the packages where no rule can + # reach the answer: abseil's header is `absl/…`, bzip2's is `bzlib.h`, + # xz's is `lzma.h` — none of them derivable from the package name. + # Checked in beside the descriptors, so it is reviewed like data. + curated = _read_json(ctx.path(".xpkgindex", "interfaces.json")) + # Allow the file to carry a `_note` alongside the data. + self.overrides = curated.get("interfaces", curated) + + self._scan_examples(ctx) + if self.examples: + ctx.meta.set("hero_stats", [ + {"label": "with examples", "value": len(self.examples)}, + ]) + + def _scan_examples(self, ctx) -> None: + """Map packages to the test projects that consume them. + + This repo is also an mcpp workspace whose members are per-library test + projects, so every association points at code that CI actually builds + and runs — the detail page can show real usage instead of a snippet + written for the website. + """ + for toml_path in sorted(glob.glob(os.path.join(self.root, "tests", "examples", + "*", "mcpp.toml"))): + data = _read_toml(toml_path) + # Dependencies are often platform-gated — + # `[target.'cfg(linux)'.dependencies.ocornut]` — so a top-level + # lookup missed most of the module packages entirely, and with + # them the real `import` lines their tests demonstrate. + deps = _collect_dependencies(data) + project = os.path.basename(os.path.dirname(toml_path)) + sources = sorted(glob.glob(os.path.join(os.path.dirname(toml_path), + "tests", "*.cpp"))) + if not sources: + continue + for key, value in deps.items(): + names = list(value.keys()) if isinstance(value, dict) else [key] + prefix = f"{key}." if isinstance(value, dict) else "" + for n in names: + # A bare dependency name resolves in the index's default + # namespace, so `tinyhttps = "…"` in a test project is the + # package this index publishes as `mcpplibs.tinyhttps`. + ids = [f"{prefix}{n}"] if prefix else [n, f"{DEFAULT_NAMESPACE}.{n}"] + entry = { + "project": project, + "path": os.path.relpath(sources[0], self.root).replace(os.sep, "/"), + # A project can hold several test files (archive has + # one per codec); all of them are candidates when + # looking for the line that belongs to this package. + "paths": [os.path.relpath(s, self.root).replace(os.sep, "/") + for s in sources], + "count": str(len(sources)), + } + for pkg_id in ids: + self.examples.setdefault(pkg_id, []).append(entry) + + # --------------------------------------------------------- identity -- + def identity(self, raw: Dict[str, Any], path: str) -> Optional[Identity]: + """mcpp resolves `namespace.name`, so the namespace IS the identity. + + Dropping it produced `mcpp add json@3.12.0` (issue #170) and collapsed + three different `imgui` packages onto one page. + """ + name = str(raw.get("name") or "") + if not name: + name = os.path.basename(path)[: -len(".lua")] + return Identity.joined(str(raw.get("namespace") or ""), name, sep=".") + + # ---------------------------------------------------------- package -- + def on_package(self, pkg, raw: Dict[str, Any]) -> None: + mcpp = raw.get("mcpp") + ext: Dict[str, Any] = {} + + if isinstance(mcpp, str): + # Form A: the upstream archive carries its own mcpp.toml at this glob. + ext = {"form": "A", "manifest": mcpp} + elif isinstance(mcpp, dict): + ext = { + "form": "B", + "modules": mcpp.get("modules") or [], + "targets": mcpp.get("targets") or {}, + "language": mcpp.get("language", ""), + "import_std": mcpp.get("import_std"), + "c_standard": mcpp.get("c_standard", ""), + "include_dirs": mcpp.get("include_dirs") or [], + "sources": mcpp.get("sources") or [], + "generated_files": list((mcpp.get("generated_files") or {}).keys()), + "features": mcpp.get("features") or {}, + "cflags": mcpp.get("cflags") or [], + "deps": mcpp.get("deps") or {}, + } + # Dependencies live in `mcpp.deps` for 21 packages here; the core + # reader only sees `xpm..deps`, so merge them in. + for dep_ns, dep_val in (ext["deps"] or {}).items(): + names = list(dep_val.keys()) if isinstance(dep_val, dict) else [dep_ns] + prefix = f"{dep_ns}." if isinstance(dep_val, dict) else "" + for n in names: + ref = f"{prefix}{n}" if prefix else n + if ref not in pkg.deps: + pkg.deps.append(ref) + else: + ext = {"form": "A", "manifest": ""} + + # A package can be demonstrated by several test projects (grpc has one + # for the module surface and one for codegen); keep them all, because + # only one of them contains the line that shows how to consume it. + examples = self.examples.get(pkg.identity.slug) or [] + if examples: + ext["examples"] = examples + ext["example"] = examples[0] + pkg.extensions.setdefault("_badges", []).append("✓ example") + + mirrors = {m for v in pkg.versions for m in v.mirrors} + if "CN" in mirrors: + pkg.extensions.setdefault("_badges", []).append("CN mirror") + + pkg.extensions["mcpp"] = ext + # After the examples are attached: the surfaces depend on which + # interface lines the examples can actually produce. + self._recompute(pkg, ext) + + def _surfaces(self, ext: Dict[str, Any], lines: List[Dict[str, str]]) -> List[str]: + """A package can be consumed in more than one way — but only ways it + actually offers. + + `include_dirs` is not by itself a public header surface: on + `boost-ext.ut` it exists "so the wrapper's `#include` resolves", an + internal build detail, while `nlohmann.json` documents that + `#include` stays available to users. The two are indistinguishable in + the field, so for a module package the header surface has to be + *demonstrated* — a real include line from its own test project or the + curated table. A package with no module keeps the header surface from + `include_dirs`, which is the only thing it can be. + """ + out: List[str] = [] + modular = bool(ext.get("modules") or ext.get("modular")) + if modular: + out.append("module") + + shows_include = any(l["label"] == "#include" for l in lines) + header_declared = bool(ext.get("include_dirs") + or (ext.get("form") == "B" and ext.get("sources"))) + if shows_include or (not modular and header_declared): + out.append("header") + + kinds = {(t or {}).get("kind", "") for t in (ext.get("targets") or {}).values() + if isinstance(t, dict)} + if "bin" in kinds or "binary" in kinds: + out.append("tool") + return out or ["external"] + + def _recompute(self, pkg, ext: Dict[str, Any]) -> None: + """Interface lines and surfaces are derived together — the surfaces + depend on which lines could actually be produced.""" + lines = self._interface_lines(pkg, ext) + ext["interface_lines"] = lines + surfaces = self._surfaces(ext, lines) + ext["surface"] = surfaces[0] + ext["surfaces"] = surfaces + pkg.facets["surface"] = " ".join(surfaces) + + # ------------------------------------------------------------ remote -- + def enrich_remote(self, packages, http) -> None: + """Fill in Form A packages from the upstream repo's own `mcpp.toml`. + + A Form A descriptor deliberately carries no build information — the + upstream archive ships its manifest — so without this the site could + only say "see upstream" for 18 packages, and every one of them showed + up as `upstream mcpp.toml` rather than as the importable module it is. + + Only the manifest file is fetched (a few hundred bytes), never the + tarball, and the URL contains the release tag, so a cached entry is + reused until the package's version changes. `--refresh` re-fetches on + demand. + """ + for pkg in packages: + ext = pkg.extensions.get("mcpp") or {} + if ext.get("form") != "A": + continue + manifest, url = None, "" + for candidate in self._manifest_urls(pkg, ext): + manifest = http.get_text(candidate, _project_manifest) + if manifest: + url = candidate + break + if not manifest: + continue + + ext["upstream"] = manifest + ext["manifest_url"] = url + + # A lib target's module interface unit is the authority: mcpp + # defaults the unit to `src/.cppm`, and the `export module` + # inside it overrides the target name — libxpkg's `[targets.xpkg]` + # exports `mcpplibs.xpkg`, not `xpkg`. Reading the declaration + # beats every heuristic, and it is one small file per package. + if not manifest.get("modules"): + found, source_path = self._module_from_interface_unit( + http, manifest, url) + if found: + manifest["modules"] = [found] + manifest["modules_source"] = "export" + manifest["modules_path"] = source_path + + if manifest.get("modules"): + ext["modules"] = manifest["modules"] + if manifest.get("targets"): + ext["targets"] = manifest["targets"] + if manifest.get("language"): + ext["language"] = manifest["language"] + # Modular without a known name: the package IS importable, so it + # belongs in the `import` facet; the row shows the shape and the + # detail page says the manifest never names it. + ext["modular"] = _is_modular(manifest) + for dep in manifest.get("deps") or []: + if dep not in pkg.deps: + pkg.deps.append(dep) + + self._recompute(pkg, ext) + + def _module_from_interface_unit(self, http, manifest: Dict[str, Any], + manifest_url: str): + """Fetch the lib target's `.cppm` and read its `export module`. + + Candidates, in order of authority: a concrete `.cppm` the manifest + names, then mcpp's documented default `src/.cppm`, then the + package name. A 404 is cached like any other answer, so a miss costs + one request per package and never repeats. + """ + base = manifest_url.rsplit("/", 1)[0] + candidates: List[str] = [] + for s in manifest.get("sources") or []: + if s.endswith(".cppm") and "*" not in s: + candidates.append(s) + for target in manifest.get("targets") or {}: + candidates.append(f"src/{target}.cppm") + if manifest.get("name"): + candidates.append(f"src/{manifest['name']}.cppm") + + seen = set() + for path in candidates: + if path in seen: + continue + seen.add(path) + module = http.get_text(f"{base}/{path}", _project_export_module) + if module: + return module, path + return "", "" + + def _manifest_urls(self, pkg, ext: Dict[str, Any]) -> List[str]: + """Candidate raw URLs of the upstream manifest, most reliable first. + + The tag is read out of the descriptor's own download URL rather than + guessed from the version, because the two differ (`v1.2.3` vs + `1.2.3`). Both publishing shapes are handled — tag archives and + release assets — and the bare/`v`-prefixed version are kept as a last + resort, where a miss is a cached 404 rather than wrong data. + """ + host, owner, name = _parse_repo(pkg.repo) + if host != "github.com" or not (owner and name): + return [] + + tags: List[str] = [] + for version in pkg.versions: + for url in version.urls.values(): + if f"/{owner}/{name}/" not in url: + continue + for pattern in (_TAG_RE, _RELEASE_TAG_RE): + m = pattern.search(url) + if m and m.group(1) not in tags: + tags.append(m.group(1)) + if pkg.latest: + for guess in (f"v{pkg.latest}", pkg.latest): + if guess not in tags: + tags.append(guess) + + path = (ext.get("manifest") or "mcpp.toml").lstrip("*/") + return [f"https://raw.githubusercontent.com/{owner}/{name}/{tag}/{path}" + for tag in tags] + + # ------------------------------------------------------------ facets -- + def facets(self) -> List[Facet]: + return [Facet(key="surface", label="how you use it", weight=10, values=[ + FacetValue(key=key, label=label, tone=tone) for key, label, tone in SURFACES + ])] + + # -------------------------------------------------------------- row -- + # Line 2 of every row answers one question — how do I consume this — and + # line 3 answers another — how do I add it. Packages whose descriptor + # never names a module or header still get line 2, as a muted placeholder + # showing the shape, so the three lines keep the same meaning in every + # row instead of shifting depending on what data happens to exist. + ROW_BY_SURFACE = { + "module": ("import", "module", "import …;"), + "header": ("#include", "header", "#include <…>"), + "tool": ("tool", "tool", "$ …"), + "external": ("import", "module", "import …;"), + } + + def row(self, pkg) -> RowSpec: + ext = pkg.extensions.get("mcpp", {}) + surfaces = ext.get("surfaces") or [ext.get("surface", "")] + iface = pkg.interface + code = iface.data.get("code", "") if iface else "" + + # The pill follows the line actually shown. Taking it from the first + # surface instead put `tool` beside an `#include` on packages that + # ship both a library and a binary. + if iface: + lead, tone = iface.data.get("label", ""), iface.data.get("tone", "neutral") + primary = {"import": "module", "#include": "header"}.get(lead, "tool") + placeholder = "" + else: + primary = surfaces[0] + lead, tone, placeholder = self.ROW_BY_SURFACE.get(primary, ("", "neutral", "")) + + # A package consumable more than one way says so in the row, so the + # listing does not imply the shown line is the only option. + badges = list(pkg.extensions.get("_badges", [])) + for other in reversed(surfaces): + if other == primary: + continue + label = self.ROW_BY_SURFACE.get(other, ("", "", ""))[0] + if label and label not in badges: + badges.insert(0, label) + + return RowSpec( + tone=tone, + lead=lead, + code=code or placeholder, + code_muted=not code, + note="This descriptor does not name the module or header — " + "open the package for its build details.", + badges=badges, + ) + + # ------------------------------------------------------------ blocks -- + def detail_blocks(self, pkg) -> List[Block]: + ext = pkg.extensions.get("mcpp", {}) + blocks: List[Block] = [] + + iface = self._interface(pkg, ext) + if iface: + blocks.append(iface) + + example = ext.get("example") + if example: + code = self._example_code(example["path"]) + if code: + blocks.append(Block( + kind="code", title="Usage", weight=20, + data={ + "code": code, + "caption": "From this repository's own test project — " + "built and run by CI, not written for the website.", + "source": f"{example['path']} · project " + f"tests/examples/{example['project']}", + })) + + if ext.get("form") == "B": + items = [] + if ext.get("modules"): + items.append({"key": "modules", "value": ", ".join(ext["modules"]), "mono": True}) + targets = ", ".join( + f"{n} ({(t or {}).get('kind', '?')})" for n, t in (ext.get("targets") or {}).items() + if isinstance(t, dict)) + if targets: + items.append({"key": "targets", "value": targets, "mono": True}) + if ext.get("language"): + items.append({"key": "language", "value": ext["language"], "mono": True}) + if ext.get("c_standard"): + items.append({"key": "C standard", "value": ext["c_standard"], "mono": True}) + if ext.get("import_std") is not None: + items.append({"key": "import std", "value": "yes" if ext["import_std"] else "no"}) + if ext.get("include_dirs"): + items.append({"key": "include dirs", + "value": ", ".join(ext["include_dirs"]), "mono": True}) + if items: + blocks.append(Block(kind="kv", title="Build", data={"items": items}, weight=30)) + + feats = ext.get("features") or {} + if feats: + rows = [] + for fname, fdata in feats.items(): + detail = "" + if isinstance(fdata, dict): + detail = ", ".join(f"{k}" for k in fdata.keys()) + rows.append([fname, detail]) + blocks.append(Block(kind="table", title="Features", weight=40, + data={"head": ["feature", "declares"], "rows": rows})) + + if ext.get("sources"): + blocks.append(Block(kind="list", title=f"Sources ({len(ext['sources'])})", + collapsed=True, weight=50, + data={"items": ext["sources"]})) + if ext.get("generated_files"): + blocks.append(Block(kind="list", title="Generated files", collapsed=True, + weight=60, data={"items": ext["generated_files"]})) + else: + upstream = ext.get("upstream") or {} + if upstream: + items = [] + if upstream.get("modules"): + items.append({"key": "modules", "value": ", ".join(upstream["modules"]), + "mono": True}) + targets = ", ".join(f"{n} ({t.get('kind', '?')})" + for n, t in (upstream.get("targets") or {}).items()) + if targets: + items.append({"key": "targets", "value": targets, "mono": True}) + if upstream.get("language"): + items.append({"key": "language", "value": upstream["language"], "mono": True}) + if upstream.get("version"): + items.append({"key": "manifest version", "value": upstream["version"], + "mono": True}) + if upstream.get("deps"): + items.append({"key": "dependencies", + "value": ", ".join(upstream["deps"]), "mono": True}) + items.append({"key": "source", "value": ext.get("manifest_url", ""), "mono": True}) + blocks.append(Block(kind="kv", title="Build · upstream manifest", + data={"items": items}, weight=30)) + else: + blocks.append(Block( + kind="callout", title="Build", weight=30, + data={"text": "Form A package: the upstream archive ships its own " + "mcpp.toml" + (f" at {ext['manifest']}" if ext.get("manifest") else "") + + ". Its contents have not been fetched into this build — " + "run the index's refresh action to pull them in."})) + return blocks + + def _interface_lines(self, pkg, ext: Dict[str, Any]) -> List[Dict[str, str]]: + """Every way this package can be consumed, most idiomatic first. + + A module package that also exposes its headers offers both, and the + detail page should say so instead of picking one and hiding the other. + """ + # A curated entry wins over every derivation: it exists precisely + # because the derivations cannot reach the answer. + manual = self.overrides.get(pkg.identity.slug) + if manual: + codes = manual if isinstance(manual, list) else [manual] + out = [] + for code in codes: + label, tone = _line_kind(str(code)) + out.append({"code": str(code), "label": label, "tone": tone, + "note": "Curated in .xpkgindex/interfaces.json."}) + return out + + lines: List[Dict[str, str]] = [] + modules = ext.get("modules") or [] + note = "" + source = (ext.get("upstream") or {}).get("modules_source") + if modules and source == "description": + note = "Module name taken from the upstream manifest's own description." + elif modules and source == "export": + path = (ext.get("upstream") or {}).get("modules_path", "") + note = f"Module name read from the upstream `export module` declaration in {path}." + + # A Form A manifest often never names its module, but a test project + # that consumes the package writes the import for real — and CI + # compiles it. That is a better source than any derivation. + if not modules: + hit = self._scan_examples_for(ext, pkg.identity.name, self._import_line) + if hit: + modules = [hit] + note = ("Module name taken from this index's own test project " + "for the package.") + + if modules: + lines.append({"code": f"import {modules[0]};", "label": "import", + "tone": "module", "note": note}) + + include = self._scan_examples_for(ext, pkg.identity.name, self._include_line, + repoint=not modules) + if include: + lines.append({"code": include, "label": "#include", "tone": "header", "note": ""}) + + if not lines: + programs = [n for n, t in (ext.get("targets") or {}).items() + if isinstance(t, dict) and t.get("kind") in ("bin", "binary")] + if programs: + lines.append({"code": f"$ {programs[0]}", "label": "tool", + "tone": "tool", "note": ""}) + return lines + + def _scan_examples_for(self, ext: Dict[str, Any], pkg_name: str, extract, + repoint: bool = True) -> str: + """Run `extract` over every test file of every associated project. + + When one matches, the Usage block is repointed at that exact file, so + the snippet shown is the one that demonstrates this package rather + than whichever project sorted first. + """ + for example in ext.get("examples") or ([ext["example"]] if ext.get("example") else []): + for candidate in example.get("paths") or [example.get("path", "")]: + if not candidate: + continue + found = extract(candidate, pkg_name) + if found: + if repoint: + ext["example"] = dict(example, path=candidate) + return found + return "" + + def _include_line(self, rel: str, pkg_name: str) -> str: + line = self._first_interface_line(rel, pkg_name, allow_fallback=False) + return line if line.startswith("#include") else "" + + def _import_line(self, rel: str, pkg_name: str) -> str: + """The module this package's own test imports, if it names one.""" + line = self._first_interface_line(rel, pkg_name, allow_fallback=False) + if line.startswith("import "): + return line[len("import "):].rstrip(";") + return "" + + def _interface(self, pkg, ext: Dict[str, Any]) -> Optional[Block]: + """How this package is consumed — the primary line plus any others. + + Only ever taken from real data: a declared (or upstream-manifest) + module name, or an include from the package's own test project. Never + invented — a wrong `#include` on a package page is worse than none. + """ + lines = ext.get("interface_lines") + if lines is None: + lines = self._interface_lines(pkg, ext) + if not lines: + # No usable line, but the example may still point at the file that + # demonstrates this package; keep the association for the Usage block. + example = ext.get("example") + if example: + for candidate in example.get("paths") or [example["path"]]: + if self._first_interface_line(candidate, pkg.identity.name, + allow_fallback=False): + example["path"] = candidate + break + return None + + primary, rest = lines[0], lines[1:] + return Block(kind="code", weight=10, data={ + "role": "interface", + "code": primary["code"], "tone": primary["tone"], + "label": primary["label"], "note": primary["note"], + "alt": rest, + }) + + # ----------------------------------------------------------- helpers -- + def _example_code(self, rel: str, max_lines: int = 22) -> str: + path = os.path.join(self.root, rel) + if not os.path.isfile(path): + return "" + with open(path, "r", encoding="utf-8", errors="replace") as f: + lines = f.read().splitlines() + if len(lines) > max_lines: + lines = lines[:max_lines] + ["// …"] + return "\n".join(lines).strip() + + def _first_interface_line(self, rel: str, pkg_name: str = "", + allow_fallback: bool = True) -> str: + """Pick the line that belongs to *this* package. + + One example project often covers several packages — tests/examples/ + archive exercises zlib, bzip2, lz4, xz and zstd together — so taking + the first include would put `#include ` on the zlib page. + Prefer a line whose identifier matches the package name, and only + fall back to the first one when nothing matches. + """ + path = os.path.join(self.root, rel) + if not os.path.isfile(path): + return "" + with open(path, "r", encoding="utf-8", errors="replace") as f: + text = f.read() + + def norm(s: str) -> str: + return s.lower().replace("-", "").replace("_", "") + + key = norm(pkg_name or "") + modules = [m for m in _IMPORT_RE.findall(text) if m != "std"] + includes = _INCLUDE_RE.findall(text) + + def stem_of(inc: str) -> str: + return os.path.splitext(os.path.basename(inc.strip('<>"')))[0] + + # Two passes, exact before fuzzy and across ALL candidates each time. + # Doing it candidate-by-candidate would let a fuzzy hit win before the + # exact one is even considered: `bzlib` ends with `zlib`, so zlib's + # page got `#include `. + def pick(test) -> str: + if not key: + return "" + for mod in modules: + if test(norm(mod.split(".")[-1])) or test(norm(mod)): + return f"import {mod};" + for inc in includes: + path = inc.strip('<>"') + # Both the file stem and the leading directory: Eigen ships + # ``, where the package name is the directory. + parts = [stem_of(inc)] + path.split("/")[:-1] + if any(test(norm(p)) for p in parts if p): + return f"#include {inc}" + return "" + + found = pick(lambda c: c == key) + if found: + return found + # Boundary match, for `libarchive` → `archive.h` and friends. + found = pick(lambda c: c.startswith(key) or key.startswith(c) + or c.endswith(key) or key.endswith(c)) + if found: + return found + + # No name match. Trust the example only when it is unambiguous — a + # shared project (archive covers zlib, bzip2, lz4, xz, zstd) would + # otherwise attribute one package's header to another. `xz` ships + # `lzma.h`, so it legitimately ends up with no interface line rather + # than a wrong one. + if allow_fallback: + if len(modules) == 1 and not includes: + return f"import {modules[0]};" + if len(includes) == 1 and not modules: + return f"#include {includes[0]}" + return "" From 5ddd85a2257763d49ac65ea9aad87b832064fa3d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 7 Aug 2026 03:07:26 +0800 Subject: [PATCH 2/5] docs: add a quick start, and fold the docs into one Docs section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six commands from nothing to a project that imports a library from this index: install mcpp, create and run a project, `mcpp add nlohmann.json`, import it, run again — then links onward, to the packaging guides here and to mcpp's own docs. Every command is taken from mcpp's README and this repo's own test projects rather than written from memory, and the page is the site's Docs landing (and the homepage card), so the flow a newcomer sees is the one CI exercises. --- .xpkgindex.json | 26 ++++++++-- docs/quick-start.md | 109 +++++++++++++++++++++++++++++++++++++++++ docs/zh/quick-start.md | 101 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 docs/quick-start.md create mode 100644 docs/zh/quick-start.md diff --git a/.xpkgindex.json b/.xpkgindex.json index dd5fe9d4..33043517 100644 --- a/.xpkgindex.json +++ b/.xpkgindex.json @@ -97,9 +97,18 @@ "plugins": [ ".xpkgindex/plugins/mcpp.py" ], - "guides": { - "nav_label": "Contribute", + "docs": { + "nav_label": "Docs", + "landing": "quick-start", "entries": [ + { + "slug": "quick-start", + "title": "Quick start", + "path": "docs/quick-start.md", + "translations": { + "zh": "docs/zh/quick-start.md" + } + }, { "slug": "contributing", "title": "Adding a package", @@ -129,7 +138,18 @@ "title": "Repository & schema", "path": "docs/repository-and-schema.md" } - ] + ], + "cta": { + "eyebrow": "New here?", + "title": "Quick start", + "description": "Install mcpp, create a project, add a library from this index, run it.", + "lines": [ + "mcpp new hello", + "mcpp add nlohmann.json@3.12.0", + "mcpp run" + ], + "action": "Read the guide" + } }, "ecosystem": { "owners": [ diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 00000000..be984601 --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,109 @@ +# Quick start + +**English** | [简体中文](zh/quick-start.md) + +From nothing to a C++23 project that imports a library from this index. Six +commands, no build-system configuration. + +## 1. Install mcpp + +```bash +xlings install mcpp -y +``` + +
+Don't have xlings yet? + +```bash +# Linux / macOS +curl -fsSL https://d2learn.org/xlings-install.sh | bash +``` + +```powershell +# Windows · PowerShell +irm https://d2learn.org/xlings-install.ps1.txt | iex +``` + +
+ +## 2. Create and run a project + +```bash +mcpp new hello +cd hello +mcpp run +``` + +The first build fetches the toolchain, so it takes a while; later builds are +incremental. What the scaffold gives you: + +``` +hello/ +├── mcpp.toml ← project manifest +└── src/ + └── main.cpp ← `import std;` works out of the box +``` + +## 3. Add a package from this index + +```bash +mcpp add nlohmann.json@3.12.0 +``` + +The name is the one shown on every package page — `namespace.name`. The +command writes the dependency into your `mcpp.toml`: + +```toml +[dependencies.nlohmann] +json = "3.12.0" +``` + +## 4. Use it + +`nlohmann.json` is exposed as a C++23 module, so there is no header to find +and no include path to configure: + +```cpp +// src/main.cpp +import std; +import nlohmann.json; + +int main() { + nlohmann::json j = { {"answer", 42}, {"list", {1, 2, 3}} }; + std::println("{}", j.dump()); +} +``` + +Every package page tells you which line to write: `import x;` for module +packages, `#include ` for the `compat.*` ports, and it is taken from this +repository's own tests — code CI compiles. + +## 5. Run it again + +```bash +mcpp run +``` + +That is the whole loop: `mcpp add` → `import` → `mcpp run`. + +## Where to go next + +**Using packages** + +- [Package types](package-types.md) — the four library shapes in this index and + what each one means for you as a consumer +- [CN mirror](cn-mirror.md) — how packages resolve from the GitCode mirror in + mainland China + +**Contributing a package** + +- [Adding a package](README.md) — the end-to-end procedure +- [Repository & schema](repository-and-schema.md) — descriptor fields, CI + behaviour, how to reproduce lint locally + +**mcpp itself** + +- [mcpp README](https://github.com/mcpp-community/mcpp) — build system, + toolchain management, the full command set +- [mcpp.toml guide](https://github.com/mcpp-community/mcpp/blob/main/docs/05-mcpp-toml.md) + — version constraints, git references, local paths diff --git a/docs/zh/quick-start.md b/docs/zh/quick-start.md new file mode 100644 index 00000000..e45afe34 --- /dev/null +++ b/docs/zh/quick-start.md @@ -0,0 +1,101 @@ +# 快速开始 + +[English](../quick-start.md) | **简体中文** + +从零到一个 import 本索引库的 C++23 项目。六条命令,不用配任何构建系统。 + +## 1. 安装 mcpp + +```bash +xlings install mcpp -y +``` + +
+还没有 xlings? + +```bash +# Linux / macOS +curl -fsSL https://d2learn.org/xlings-install.sh | bash +``` + +```powershell +# Windows · PowerShell +irm https://d2learn.org/xlings-install.ps1.txt | iex +``` + +
+ +## 2. 创建并运行项目 + +```bash +mcpp new hello +cd hello +mcpp run +``` + +首次构建会拉取工具链,耗时较长;之后都是增量构建。脚手架生成的结构: + +``` +hello/ +├── mcpp.toml ← 项目清单 +└── src/ + └── main.cpp ← `import std;` 开箱可用 +``` + +## 3. 从本索引添加一个库 + +```bash +mcpp add nlohmann.json@3.12.0 +``` + +这个名字就是每个包页面上显示的那个 —— `namespace.name`。命令会把依赖写进 +`mcpp.toml`: + +```toml +[dependencies.nlohmann] +json = "3.12.0" +``` + +## 4. 使用它 + +`nlohmann.json` 以 C++23 模块暴露,不需要找头文件,也不用配 include 路径: + +```cpp +// src/main.cpp +import std; +import nlohmann.json; + +int main() { + nlohmann::json j = { {"answer", 42}, {"list", {1, 2, 3}} }; + std::println("{}", j.dump()); +} +``` + +每个包页面都会告诉你该写哪一行:模块包是 `import x;`,`compat.*` 移植层是 +`#include `。这些行取自本仓自己的测试代码 —— CI 编译过的。 + +## 5. 再运行一次 + +```bash +mcpp run +``` + +整个循环就是这样:`mcpp add` → `import` → `mcpp run`。 + +## 接下来 + +**使用包** + +- [四种库形态](package-types.md) —— 本索引中的四类库,以及它们对使用者各意味着什么 +- [CN 镜像](cn-mirror.md) —— 国内如何从 GitCode 镜像解析包 + +**贡献一个包** + +- [新增一个包](README.md) —— 端到端流程 +- [仓库结构与 schema](../repository-and-schema.md) —— 描述符字段、CI 行为、本地复现 lint + +**mcpp 本身** + +- [mcpp README](https://github.com/mcpp-community/mcpp) —— 构建系统、工具链管理、完整命令集 +- [mcpp.toml 指南](https://github.com/mcpp-community/mcpp/blob/main/docs/05-mcpp-toml.md) + —— 版本约束、git 引用、本地路径 From c21c9616928f2a3cfde644f129ee4fd1021baaa0 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 7 Aug 2026 03:24:09 +0800 Subject: [PATCH 3/5] chore(site): drop the hardcoded Docs label so it follows the page language --- .xpkgindex.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.xpkgindex.json b/.xpkgindex.json index 33043517..0ce9bf62 100644 --- a/.xpkgindex.json +++ b/.xpkgindex.json @@ -98,7 +98,6 @@ ".xpkgindex/plugins/mcpp.py" ], "docs": { - "nav_label": "Docs", "landing": "quick-start", "entries": [ { From 0542af42eac8cd7f3d53642d9fbf1ea7909802f3 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 7 Aug 2026 03:46:39 +0800 Subject: [PATCH 4/5] chore(site): say it in the reader's language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Site title and lede, the install labels, the quick-start card, the doc nav titles, and everything the plugin writes — the 'how you use it' axis, the Usage/Build/Features headings, the '✓ example' and 'CN mirror' badges, the notes explaining where a module name came from — now carry en / zh / zh-Hant. Identifiers stay untranslated on purpose: 'import', '#include', 'modules', 'targets' are what you type or what mcpp.toml calls the field, and a reader comparing the page against a manifest needs the same word in both. Also points the new website icon at https://mcpp.d2learn.org. --- .xpkgindex.json | 97 +++++++++++++++++++++++++++++++------ .xpkgindex/plugins/mcpp.py | 99 +++++++++++++++++++++++++++----------- 2 files changed, 151 insertions(+), 45 deletions(-) diff --git a/.xpkgindex.json b/.xpkgindex.json index 0ce9bf62..9c9ec71d 100644 --- a/.xpkgindex.json +++ b/.xpkgindex.json @@ -1,11 +1,20 @@ { "site": { - "title": "mcpp Package Index", - "description": "Modular C++23 packages for the mcpp build tool", + "title": { + "en": "mcpp Package Index", + "zh": "mcpp 包索引", + "zh-Hant": "mcpp 套件索引" + }, + "description": { + "en": "Modular C++23 packages for the mcpp build tool", + "zh": "面向 mcpp 构建工具的模块化 C++23 包", + "zh-Hant": "給 mcpp 建置工具使用的模組化 C++23 套件" + }, "logo": "mcpp index" }, "links": { "github": "https://github.com/mcpplibs/mcpp-index", + "website": "https://mcpp.d2learn.org", "custom": [ { "label": "mcpp", @@ -16,11 +25,19 @@ "url": "https://github.com/openxlings/xlings" }, { - "label": "xpkg V1 spec", + "label": { + "en": "xpkg V1 spec", + "zh": "xpkg V1 规范", + "zh-Hant": "xpkg V1 規範" + }, "url": "https://github.com/openxlings/xim-pkgindex/blob/main/docs/V1/xpackage-spec.md" }, { - "label": "mcpp ext", + "label": { + "en": "mcpp ext", + "zh": "mcpp 扩展字段", + "zh-Hant": "mcpp 擴充欄位" + }, "url": "https://github.com/mcpp-community/mcpp/blob/main/docs/04-schema-xpkg-extension.md" } ] @@ -28,7 +45,11 @@ "about": { "project_name": "mcpp", "project_url": "https://github.com/mcpp-community/mcpp", - "description": "Modern C++23 build & package management tool — modules-first, xlings-driven, single-binary install", + "description": { + "en": "Modern C++23 build & package management tool — modules-first, xlings-driven, single-binary install", + "zh": "现代 C++23 构建与包管理工具 —— 模块优先、由 xlings 驱动、单文件安装", + "zh-Hant": "現代 C++23 建置與套件管理工具 —— 模組優先、由 xlings 驅動、單一執行檔安裝" + }, "maintainers": [ "mcpp-community" ], @@ -53,7 +74,11 @@ }, "pkgs_dir": "pkgs", "growth": { - "total_label": "all packages", + "total_label": { + "en": "all packages", + "zh": "全部包", + "zh-Hant": "全部套件" + }, "series": [ { "label": "import", @@ -77,10 +102,18 @@ "install_command_template": "mcpp add {ref}@{version}", "install": { "primary": { - "label": "Install mcpp", + "label": { + "en": "Install mcpp", + "zh": "安装 mcpp", + "zh-Hant": "安裝 mcpp" + }, "command": "xlings install mcpp -y" }, - "summary": "Don't have xlings yet?", + "summary": { + "en": "Don't have xlings yet?", + "zh": "还没装 xlings?", + "zh-Hant": "還沒安裝 xlings?" + }, "os": [ { "id": "unix", @@ -102,7 +135,11 @@ "entries": [ { "slug": "quick-start", - "title": "Quick start", + "title": { + "en": "Quick start", + "zh": "快速开始", + "zh-Hant": "快速開始" + }, "path": "docs/quick-start.md", "translations": { "zh": "docs/zh/quick-start.md" @@ -110,7 +147,11 @@ }, { "slug": "contributing", - "title": "Adding a package", + "title": { + "en": "Adding a package", + "zh": "新增一个包", + "zh-Hant": "新增一個套件" + }, "path": "docs/README.md", "translations": { "zh": "docs/zh/README.md" @@ -118,7 +159,11 @@ }, { "slug": "package-types", - "title": "The four library shapes", + "title": { + "en": "The four library shapes", + "zh": "四种库形态", + "zh-Hant": "四種函式庫形態" + }, "path": "docs/package-types.md", "translations": { "zh": "docs/zh/package-types.md" @@ -126,7 +171,11 @@ }, { "slug": "cn-mirror", - "title": "CN mirror loop", + "title": { + "en": "CN mirror loop", + "zh": "国内镜像流程", + "zh-Hant": "中國鏡像流程" + }, "path": "docs/cn-mirror.md", "translations": { "zh": "docs/zh/cn-mirror.md" @@ -139,15 +188,31 @@ } ], "cta": { - "eyebrow": "New here?", - "title": "Quick start", - "description": "Install mcpp, create a project, add a library from this index, run it.", + "eyebrow": { + "en": "New here?", + "zh": "第一次用?", + "zh-Hant": "第一次用?" + }, + "title": { + "en": "Quick start", + "zh": "快速开始", + "zh-Hant": "快速開始" + }, + "description": { + "en": "Install mcpp, create a project, add a library from this index, run it.", + "zh": "装好 mcpp,建个项目,从索引里加一个库,跑起来。", + "zh-Hant": "裝好 mcpp,建個專案,從索引裡加一個函式庫,跑起來。" + }, "lines": [ "mcpp new hello", "mcpp add nlohmann.json@3.12.0", "mcpp run" ], - "action": "Read the guide" + "action": { + "en": "Read the guide", + "zh": "看指南", + "zh-Hant": "看指南" + } } }, "ecosystem": { diff --git a/.xpkgindex/plugins/mcpp.py b/.xpkgindex/plugins/mcpp.py index 889211ae..676f5529 100644 --- a/.xpkgindex/plugins/mcpp.py +++ b/.xpkgindex/plugins/mcpp.py @@ -27,11 +27,23 @@ # How a package is consumed. This is the axis a C++ user actually browses by, # and unlike `categories` (which no descriptor in this repo sets) it is # derivable from data that is already there. +def _t(en: str, zh: str, hant: str) -> Dict[str, str]: + """A string the site renders in the reader's language. + + The framework resolves these maps per locale; anything left as a plain + string stays as written. Identifiers are deliberately not run through + here — `import`, `#include`, `modules`, `targets` are what you actually + type or what mcpp.toml actually calls the field, and translating them + would break the link to the file the block is describing. + """ + return {"en": en, "zh": zh, "zh-Hant": hant} + + SURFACES = [ ("module", "import", "module"), ("header", "#include", "header"), ("tool", "tool", "tool"), - ("external", "upstream mcpp.toml", "neutral"), + ("external", _t("upstream mcpp.toml", "上游 mcpp.toml", "上游 mcpp.toml"), "neutral"), ] _IMPORT_RE = re.compile(r"^\s*import\s+([A-Za-z_][\w.]*)\s*;", re.M) @@ -288,7 +300,7 @@ def on_index(self, ctx) -> None: self._scan_examples(ctx) if self.examples: ctx.meta.set("hero_stats", [ - {"label": "with examples", "value": len(self.examples)}, + {"label": _t("with examples", "带示例", "附範例"), "value": len(self.examples)}, ]) def _scan_examples(self, ctx) -> None: @@ -387,11 +399,11 @@ def on_package(self, pkg, raw: Dict[str, Any]) -> None: if examples: ext["examples"] = examples ext["example"] = examples[0] - pkg.extensions.setdefault("_badges", []).append("✓ example") + pkg.extensions.setdefault("_badges", []).append(_t("✓ example", "✓ 有示例", "✓ 有範例")) mirrors = {m for v in pkg.versions for m in v.mirrors} if "CN" in mirrors: - pkg.extensions.setdefault("_badges", []).append("CN mirror") + pkg.extensions.setdefault("_badges", []).append(_t("CN mirror", "国内镜像", "中國鏡像")) pkg.extensions["mcpp"] = ext # After the examples are attached: the surfaces depend on which @@ -559,7 +571,7 @@ def _manifest_urls(self, pkg, ext: Dict[str, Any]) -> List[str]: # ------------------------------------------------------------ facets -- def facets(self) -> List[Facet]: - return [Facet(key="surface", label="how you use it", weight=10, values=[ + 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 ])] @@ -608,8 +620,10 @@ def row(self, pkg) -> RowSpec: lead=lead, code=code or placeholder, code_muted=not code, - note="This descriptor does not name the module or header — " - "open the package for its build details.", + note=_t("This descriptor does not name the module or header — " + "open the package for its build details.", + "这个描述符没有写明模块名或头文件 —— 打开包页面看它的构建信息。", + "這個描述符沒有寫明模組名或標頭檔 —— 開啟套件頁面看它的建置資訊。"), badges=badges, ) @@ -627,13 +641,18 @@ def detail_blocks(self, pkg) -> List[Block]: code = self._example_code(example["path"]) if code: blocks.append(Block( - kind="code", title="Usage", weight=20, + kind="code", title=_t("Usage", "用法", "用法"), weight=20, data={ "code": code, - "caption": "From this repository's own test project — " - "built and run by CI, not written for the website.", - "source": f"{example['path']} · project " - f"tests/examples/{example['project']}", + "caption": _t( + "From this repository's own test project — " + "built and run by CI, not written for the website.", + "来自本仓库自己的测试项目 —— CI 真的编译并运行过,不是为了网站写的。", + "來自本倉庫自己的測試專案 —— CI 真的編譯並執行過,不是為了網站寫的。"), + "source": _t( + f"{example['path']} · project tests/examples/{example['project']}", + f"{example['path']} · 项目 tests/examples/{example['project']}", + f"{example['path']} · 專案 tests/examples/{example['project']}"), })) if ext.get("form") == "B": @@ -655,7 +674,8 @@ def detail_blocks(self, pkg) -> List[Block]: items.append({"key": "include dirs", "value": ", ".join(ext["include_dirs"]), "mono": True}) if items: - blocks.append(Block(kind="kv", title="Build", data={"items": items}, weight=30)) + blocks.append(Block(kind="kv", title=_t("Build", "构建", "建置"), + data={"items": items}, weight=30)) feats = ext.get("features") or {} if feats: @@ -665,15 +685,19 @@ def detail_blocks(self, pkg) -> List[Block]: if isinstance(fdata, dict): detail = ", ".join(f"{k}" for k in fdata.keys()) rows.append([fname, detail]) - blocks.append(Block(kind="table", title="Features", weight=40, - data={"head": ["feature", "declares"], "rows": rows})) + blocks.append(Block(kind="table", title=_t("Features", "特性", "功能"), weight=40, + data={"head": [_t("feature", "特性", "功能"), + _t("declares", "声明了", "宣告了")], + "rows": rows})) if ext.get("sources"): - blocks.append(Block(kind="list", title=f"Sources ({len(ext['sources'])})", + blocks.append(Block(kind="list", title=_t(f"Sources ({len(ext['sources'])})", + f"源文件 ({len(ext['sources'])})", + f"原始檔 ({len(ext['sources'])})"), collapsed=True, weight=50, data={"items": ext["sources"]})) if ext.get("generated_files"): - blocks.append(Block(kind="list", title="Generated files", collapsed=True, + blocks.append(Block(kind="list", title=_t("Generated files", "生成的文件", "產生的檔案"), collapsed=True, weight=60, data={"items": ext["generated_files"]})) else: upstream = ext.get("upstream") or {} @@ -694,16 +718,25 @@ def detail_blocks(self, pkg) -> List[Block]: if upstream.get("deps"): items.append({"key": "dependencies", "value": ", ".join(upstream["deps"]), "mono": True}) - items.append({"key": "source", "value": ext.get("manifest_url", ""), "mono": True}) - blocks.append(Block(kind="kv", title="Build · upstream manifest", + items.append({"key": _t("source", "来源", "來源"), + "value": ext.get("manifest_url", ""), "mono": True}) + blocks.append(Block(kind="kv", + title=_t("Build · upstream manifest", + "构建 · 上游清单", "建置 · 上游資訊清單"), data={"items": items}, weight=30)) else: + at = f" at {ext['manifest']}" if ext.get("manifest") else "" + at_zh = f"(在 {ext['manifest']})" if ext.get("manifest") else "" blocks.append(Block( - kind="callout", title="Build", weight=30, - data={"text": "Form A package: the upstream archive ships its own " - "mcpp.toml" + (f" at {ext['manifest']}" if ext.get("manifest") else "") - + ". Its contents have not been fetched into this build — " - "run the index's refresh action to pull them in."})) + kind="callout", title=_t("Build", "构建", "建置"), weight=30, + data={"text": _t( + "Form A package: the upstream archive ships its own mcpp.toml" + + at + ". Its contents have not been fetched into this build — " + "run the index's refresh action to pull them in.", + "A 型包:上游压缩包自带 mcpp.toml" + at_zh + + "。本次构建没有抓取它的内容 —— 跑一次索引的刷新动作就能拉进来。", + "A 型套件:上游壓縮檔自帶 mcpp.toml" + at_zh + + "。本次建置沒有抓取它的內容 —— 執行一次索引的重新整理動作就能拉進來。")})) return blocks def _interface_lines(self, pkg, ext: Dict[str, Any]) -> List[Dict[str, str]]: @@ -721,7 +754,9 @@ def _interface_lines(self, pkg, ext: Dict[str, Any]) -> List[Dict[str, str]]: for code in codes: label, tone = _line_kind(str(code)) out.append({"code": str(code), "label": label, "tone": tone, - "note": "Curated in .xpkgindex/interfaces.json."}) + "note": _t("Curated in .xpkgindex/interfaces.json.", + "人工维护在 .xpkgindex/interfaces.json。", + "人工維護在 .xpkgindex/interfaces.json。")}) return out lines: List[Dict[str, str]] = [] @@ -729,10 +764,14 @@ def _interface_lines(self, pkg, ext: Dict[str, Any]) -> List[Dict[str, str]]: note = "" source = (ext.get("upstream") or {}).get("modules_source") if modules and source == "description": - note = "Module name taken from the upstream manifest's own description." + note = _t("Module name taken from the upstream manifest's own description.", + "模块名取自上游清单自己的 description。", + "模組名取自上游資訊清單自己的 description。") elif modules and source == "export": path = (ext.get("upstream") or {}).get("modules_path", "") - note = f"Module name read from the upstream `export module` declaration in {path}." + note = _t(f"Module name read from the upstream `export module` declaration in {path}.", + f"模块名读自上游 {path} 里的 `export module` 声明。", + f"模組名讀自上游 {path} 裡的 `export module` 宣告。") # A Form A manifest often never names its module, but a test project # that consumes the package writes the import for real — and CI @@ -741,8 +780,10 @@ def _interface_lines(self, pkg, ext: Dict[str, Any]) -> List[Dict[str, str]]: hit = self._scan_examples_for(ext, pkg.identity.name, self._import_line) if hit: modules = [hit] - note = ("Module name taken from this index's own test project " - "for the package.") + note = _t("Module name taken from this index's own test project " + "for the package.", + "模块名取自本索引给这个包写的测试项目。", + "模組名取自本索引給這個套件寫的測試專案。") if modules: lines.append({"code": f"import {modules[0]};", "label": "import", From 2019bc89aa6127b873e4998c6d5b0cc940e17ab9 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 7 Aug 2026 04:47:25 +0800 Subject: [PATCH 5/5] ci(site): stop shipping a site with no growth curve, and check it on PRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy-site and refresh-site-cache both checked out at the default depth of 1. xpkgindex detects a shallow clone and skips the growth curve, the history line and the contributor list rather than replaying a truncated log — so the deployed site has quietly been missing all three. fetch-depth: 0 restores them. Both now also pass GITHUB_TOKEN, which raises the API rate limit and enables the author -> login mapping that merges one person's several git identities. deploy-site's path filter gained .xpkgindex/** and docs/**: the plugin decides how every package reads, the docs are rendered as site pages, and the cache is what an offline build renders from. A change to any of them changes the site as much as a descriptor does, and none of them triggered a deploy. site-check is new. validate.yml proves the packages compile; this proves they still render — offline, so a pull request spends no rate limit and the result depends only on what is in the repository, and --strict, so a replayed history that disagrees with the tree fails rather than warns. It also fails on any warning at all, because a warning nobody reads becomes permanent, and asserts that the pages a reader actually lands on exist in all three locales. --- .github/workflows/deploy-site.yml | 15 ++++++ .github/workflows/refresh-site-cache.yml | 2 + .github/workflows/site-check.yml | 66 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 .github/workflows/site-check.yml diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index aad494c1..5d0e71fa 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -10,6 +10,11 @@ on: paths: - 'pkgs/**' - '.xpkgindex.json' + # The plugin decides how every package reads, the docs are rendered as + # site pages, and the cache is what an offline build renders from — a + # change to any of them changes the site as much as a descriptor does. + - '.xpkgindex/**' + - 'docs/**' - '.github/workflows/deploy-site.yml' workflow_dispatch: @@ -30,6 +35,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # The growth curve, the history line and the contributor list are + # replayed from the git log. xpkgindex detects a shallow clone and + # skips all three with a warning, so the default depth of 1 was + # quietly shipping a site with no curve at all. + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: '3.12' @@ -37,6 +48,10 @@ jobs: run: pip install git+https://github.com/openxlings/xpkgindex.git - name: Generate static site env: + # Raises the API rate limit and enables the author -> login mapping + # that merges one person's several git identities. The build works + # without it, with fewer avatars and less merging. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # xpkgindex >= Apr 2026 surfaces these on the About page. XPKGINDEX_BUILD_TIME: ${{ github.event.head_commit.timestamp }} XPKGINDEX_BUILD_COMMIT: ${{ github.sha }} diff --git a/.github/workflows/refresh-site-cache.yml b/.github/workflows/refresh-site-cache.yml index 5fcb6b62..6116e505 100644 --- a/.github/workflows/refresh-site-cache.yml +++ b/.github/workflows/refresh-site-cache.yml @@ -29,6 +29,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # same reason as deploy-site: history replay - uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/.github/workflows/site-check.yml b/.github/workflows/site-check.yml new file mode 100644 index 00000000..0387e133 --- /dev/null +++ b/.github/workflows/site-check.yml @@ -0,0 +1,66 @@ +name: site-check + +# Builds the site on a pull request without publishing it. `validate.yml` +# proves the packages compile; this proves they still render — a descriptor +# that parses fine can still collide with another package's URL, and the +# plugin reads fields no compiler checks. +# +# Deliberately offline: a pull request should not spend the API rate limit, +# and the result should depend only on what is in the repository. + +on: + pull_request: + paths: + - 'pkgs/**' + - '.xpkgindex.json' + - '.xpkgindex/**' + - 'docs/**' + - '.github/workflows/site-check.yml' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # --strict checks the replayed history against the tree + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install xpkgindex + run: pip install git+https://github.com/openxlings/xpkgindex.git + + - name: Build the site + # --strict turns the growth reconciliation warning into an error: if + # the replayed history disagrees with the tree, the curve on the site + # would be wrong, and that is worth failing a pull request over. + run: xpkgindex generate . --output /tmp/site --offline --strict | tee /tmp/build.log + + - name: No warnings + # A duplicate slug or an unparsable descriptor already fails above. + # Everything else the build is unhappy about arrives as a warning, and + # a warning nobody reads is a warning that becomes permanent. + run: | + if grep -q 'warning:' /tmp/build.log; then + echo "::error::the site build reported warnings" + grep 'warning:' /tmp/build.log + exit 1 + fi + + - name: The pages a reader lands on exist + run: | + set -e + for f in index.html index.json stats/index.html contributors/index.html \ + about/index.html docs/quick-start/index.html \ + zh/index.html zh-Hant/index.html; do + test -s "/tmp/site/$f" || { echo "::error::missing $f"; exit 1; } + done + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: site + path: /tmp/site + retention-days: 7