diff --git a/.agents/docs/2026-10-13-add-pybind11-plan.md b/.agents/docs/2026-10-13-add-pybind11-plan.md new file mode 100644 index 00000000..2b77a2a0 --- /dev/null +++ b/.agents/docs/2026-10-13-add-pybind11-plan.md @@ -0,0 +1,22 @@ +# Add compat.pybind11 + +The upstream revision 6254086d96062249c5153c17eab6b4437dd1589f identifies +itself as 3.2.0a0, not released 3.2.0. The index version is therefore +3.2.0-dev.6254086. Its archive hash was checked twice; license BSD-3-Clause. + +Form B: original header-only include/ plus a generated C anchor. The descriptor +identity and path are compat.pybind11 and pkgs/c/compat.pybind11.lua. No optional +compiled sources exist, so there are no package features. Plain-string GLOBAL +URLs retain the documented CN fallback. + +Python is a consumer SDK dependency, not vendored into pybind11. The example +declares xim:python 3.13.12 and asks mcpp::xpkg_dir for its actual payload. +Headers, library and standard library all come from that SDK, avoiding system +Python multiarch configuration and mixed-libc failures. It selects python313.lib +on Windows and python3.13 on Unix; The SDK runtime directory lets mcpp deploy the matching Python DLLs on Windows. +It never invents Python configuration macros or uses Linux-only link syntax. + +The example embeds Python, evaluates a sum and calls a C++ function from +Python, asserting both results. Local validation with CI-pinned mcpp +2026.10.1.2 and gcc 16.1.0: `mcpp test -p pybind11`: +`test result ok. 1 passed; 0 failed`. diff --git a/README.md b/README.md index 879a3ecf..7e968212 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ the online index; open a PR and confirm CI is green. A PR runs lint and tests only the workspace members that depend on the changed descriptors; after the merge, `deploy-site` publishes the site. +Additional example: [`compat.pybind11`](pkgs/c/compat.pybind11.lua) — Header-only Python bindings; [`consumer test`](tests/examples/pybind11/mcpp.toml). Additional example: [`compat.etl`](pkgs/c/compat.etl.lua) — Header-only; [`consumer test`](tests/examples/etl/mcpp.toml). Additional example: [`compat.richdem`](pkgs/c/compat.richdem.lua) — C++ sources, serial terrain analysis; [`consumer test`](tests/examples/richdem/mcpp.toml). diff --git a/README.zh-CN.md b/README.zh-CN.md index 65434191..bc12df94 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -59,6 +59,7 @@ agent(如 Claude Code)即可: 提交 PR 后,CI 执行 lint,并只测试依赖了被修改描述符的 workspace 成员;合并后,`deploy-site` 发布站点。 +新增示例: [`compat.pybind11`](pkgs/c/compat.pybind11.lua) — Header-only Python bindings; [`消费测试`](tests/examples/pybind11/mcpp.toml)。 新增示例: [`compat.etl`](pkgs/c/compat.etl.lua) — Header-only; [`消费测试`](tests/examples/etl/mcpp.toml)。 新增示例: [`compat.richdem`](pkgs/c/compat.richdem.lua) — C++ sources, serial terrain analysis; [`消费测试`](tests/examples/richdem/mcpp.toml)。 diff --git a/mcpp.toml b/mcpp.toml index b4324a79..8019d7f0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -186,6 +186,7 @@ members = [ "tests/examples/nanosvg", "tests/examples/nanodbc", "tests/examples/plf-hive", + "tests/examples/pybind11", "tests/examples/spirv-reflect", "tests/examples/vulkan-memory-allocator", "tests/examples/wamr", diff --git a/pkgs/c/compat.pybind11.lua b/pkgs/c/compat.pybind11.lua new file mode 100644 index 00000000..4b2a6cb4 --- /dev/null +++ b/pkgs/c/compat.pybind11.lua @@ -0,0 +1,50 @@ +-- Form B inline descriptor for pybind11, a header-only library for creating +-- Python bindings from C++. +-- +-- Python headers and libraries come from a matching consumer Python SDK. +-- The example declares xim:python and uses its payload rather than host Python. +package = { + spec = "1", + namespace = "compat", + name = "pybind11", + description = "Seamless operability between C++11 and Python", + licenses = {"BSD-3-Clause"}, + repo = "https://github.com/pybind/pybind11", + type = "package", + + xpm = { + linux = { + ["3.2.0-dev.6254086"] = { + url = "https://github.com/pybind/pybind11/archive/6254086d96062249c5153c17eab6b4437dd1589f.tar.gz", + sha256 = "4a77778750ae4bd27d29dd328902b03a7efb9c3468c6f23180fa39d80c05f635", + }, + }, + macosx = { + ["3.2.0-dev.6254086"] = { + url = "https://github.com/pybind/pybind11/archive/6254086d96062249c5153c17eab6b4437dd1589f.tar.gz", + sha256 = "4a77778750ae4bd27d29dd328902b03a7efb9c3468c6f23180fa39d80c05f635", + }, + }, + windows = { + ["3.2.0-dev.6254086"] = { + url = "https://github.com/pybind/pybind11/archive/6254086d96062249c5153c17eab6b4437dd1589f.tar.gz", + sha256 = "4a77778750ae4bd27d29dd328902b03a7efb9c3468c6f23180fa39d80c05f635", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include" }, + generated_files = { + ["mcpp_generated/pybind11_anchor.c"] = [==[ +int mcpp_pybind11_headers_anchor(void) { return 0; } +]==], + }, + sources = { "mcpp_generated/pybind11_anchor.c" }, + targets = { ["pybind11"] = { kind = "lib" } }, + deps = {}, + }, +} diff --git a/tests/examples/pybind11/build.mcpp b/tests/examples/pybind11/build.mcpp new file mode 100644 index 00000000..91220515 --- /dev/null +++ b/tests/examples/pybind11/build.mcpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +import mcpp; + +int main() { + namespace fs = std::filesystem; + const fs::path python = mcpp::xpkg_dir("xim", "python"); + if (python.empty() || !fs::is_directory(python)) { + std::fputs("the declared xim:python SDK is unavailable\n", stderr); + return 1; + } + const bool windows = std::string(mcpp::target_os()) == "windows"; + const fs::path headers = python / "include" / (windows ? "" : "python3.13"); + const fs::path libraries = python / (windows ? "libs" : "lib"); + if (!fs::exists(headers / "Python.h")) return 1; + mcpp::include_dir(headers.string().c_str()); + mcpp::link_search(libraries.string().c_str()); + mcpp::link_lib(windows ? "python313" : "python3.13"); + mcpp::runtime_search_dir((python / (windows ? "" : "lib")).string().c_str()); + // Resolve the standard library in the same SDK when embedding Python. + const fs::path config_dir = fs::absolute(".mcpp/python-sdk"); + fs::create_directories(config_dir); + std::ofstream config(config_dir / "python_sdk.h"); + config << "#define MCPP_PYTHON_HOME R\"SDK(" << python.generic_string() << ")SDK\"\n"; + config.close(); + if (!config) return 1; + mcpp::include_dir(config_dir.string().c_str()); + mcpp::rerun_if_changed("build.mcpp"); + return 0; +} diff --git a/tests/examples/pybind11/mcpp.toml b/tests/examples/pybind11/mcpp.toml new file mode 100644 index 00000000..5c5e4931 --- /dev/null +++ b/tests/examples/pybind11/mcpp.toml @@ -0,0 +1,12 @@ +[package] +name = "pybind11-tests" +version = "0.1.0" + +[dependencies.compat] +pybind11 = "3.2.0-dev.6254086" + +[indices] +compat = { path = "../../.." } + +[xlings.workspace] +"xim:python" = "3.13.12" diff --git a/tests/examples/pybind11/tests/embed.cpp b/tests/examples/pybind11/tests/embed.cpp new file mode 100644 index 00000000..7a7e0308 --- /dev/null +++ b/tests/examples/pybind11/tests/embed.cpp @@ -0,0 +1,20 @@ +#include +#include + +int main() { + namespace py = pybind11; + PyConfig config; + PyConfig_InitPythonConfig(&config); + const PyStatus status = PyConfig_SetBytesString(&config, &config.home, MCPP_PYTHON_HOME); + if (PyStatus_Exception(status)) { + PyConfig_Clear(&config); + return 1; + } + py::scoped_interpreter interpreter(&config); + PyConfig_Clear(&config); + const int answer = py::eval("sum([1, 2, 3, 4])").cast(); + py::dict globals; + globals["double_value"] = py::cpp_function([](int value) { return 2 * value; }); + py::exec("result = double_value(21)", globals); + return answer == 10 && globals["result"].cast() == 42 ? 0 : 2; +}