diff --git a/.agents/docs/2026-10-13-add-openmp-plan.md b/.agents/docs/2026-10-13-add-openmp-plan.md new file mode 100644 index 00000000..e23bef5d --- /dev/null +++ b/.agents/docs/2026-10-13-add-openmp-plan.md @@ -0,0 +1,31 @@ +# Add compat.openmp + +LLVM libomp 22.1.8 is a separable CMake project inside llvm-project. +The descriptor downloads the full official llvm-project-22.1.8.src.tar.xz, +whose SHA-256 was checked twice; license Apache-2.0 WITH LLVM-exception. +The install hook builds upstream openmp/ and installs the generated omp.h +and real runtime. It writes the C anchor only after installation succeeds. + +Unix uses static libomp.a, which implements both Clang and GCC OpenMP ABIs +without a C++ standard-library ABI dependency. The hook uses the system C/C++ +SDK explicitly because installation precedes mcpp consumer toolchain selection. +Windows uses the upstream-supported DLL/import library with Visual Studio 2022; +the DLL is declared as a runtime artifact directory for consumer deployment. +The Visual Studio generator can omit the archive for upstream's source-less +ompimp target. In that case the hook installs the import library produced by +the DLL link under the expected libomp.lib name; DLL and imports are from the +same build. Windows CMake commands run through a batch file, preserving quoted +SDK paths and propagating failures with the build log tail. +CMake and Python are declared build dependencies. Offloading and OMPT tools +are disabled; there is no misleading sources-only feature for these builds. + +No mirror upload was performed. Plain-string GLOBAL URLs provide CN fallback. +The example redirects compat to this checkout. It enables OpenMP with normal +GCC/Clang flags (Apple Clang uses -Xclang -fopenmp), links the package runtime, +launches two threads, and checks thread identity plus a reduction sum. The +header never defines _OPENMP: a missing compiler flag is a compile error. + +Local validation with CI-pinned mcpp 2026.10.1.2 and gcc 16.1.0: +`mcpp test -p openmp`: `test result ok. 1 passed; 0 failed`. +An independent upstream CMake build also succeeded. Cross-platform behavior +is validated by the PR CI matrix rather than inferred from a SIMD-only test. diff --git a/README.md b/README.md index 7e968212..4dbb50fc 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ One descriptor per common shape: | Generated config header | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | | C++23 module shipped by upstream | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) | -| External build system (`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) | +| External build system (`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) · [`compat.openmp`](pkgs/c/compat.openmp.lua) | Every other shape, and why each descriptor is written the way it is, is in **[Descriptor examples by shape](docs/descriptor-examples.md)**. diff --git a/README.zh-CN.md b/README.zh-CN.md index bc12df94..ba5aa21f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -41,7 +41,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 生成 config 头 | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | | 上游自带 C++23 module | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) | -| 外部构建系统(`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) | +| 外部构建系统(`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) · [`compat.openmp`](pkgs/c/compat.openmp.lua) | 其余形态,以及每个描述符为何这样写,见 **[描述符示例总览(按形态)](docs/zh/descriptor-examples.md)**。 diff --git a/mcpp.toml b/mcpp.toml index 8019d7f0..179e43c5 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -82,6 +82,7 @@ members = [ "tests/examples/mysql-connector-cpp", "tests/examples/nlohmann.json", "tests/examples/openblas", + "tests/examples/openmp", "tests/examples/openssl", "tests/examples/recastnavigation", "tests/examples/recastnavigation-features", diff --git a/pkgs/c/compat.openmp.lua b/pkgs/c/compat.openmp.lua new file mode 100644 index 00000000..77ef8822 --- /dev/null +++ b/pkgs/c/compat.openmp.lua @@ -0,0 +1,150 @@ +-- LLVM OpenMP runtime built from its pinned release sources. +-- libomp implements the Clang and (on Unix) GCC OpenMP ABIs. Consumers enable +-- OpenMP compilation themselves; this package supplies the header and runtime. +-- Unix uses a static C-ABI runtime; Windows ships the upstream DLL/import lib. +package = { + spec = "1", + namespace = "compat", + name = "openmp", + description = "LLVM OpenMP runtime and omp.h, built from source", + licenses = {"Apache-2.0 WITH LLVM-exception"}, + repo = "https://github.com/llvm/llvm-project/tree/main/openmp", + type = "package", + xpm = { + linux = { + deps = { "xim:cmake@latest", "xim:python@3.13.12" }, + ["22.1.8"] = { + url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz", + sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888", + }, + }, + macosx = { + deps = { "xim:cmake@latest", "xim:python@3.13.12" }, + ["22.1.8"] = { + url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz", + sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888", + }, + }, + windows = { + deps = { "xim:cmake@latest", "xim:python@3.13.12" }, + ["22.1.8"] = { + url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz", + sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888", + }, + }, + }, + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = {"mcpp_openmp/include"}, + -- Written last by install(), after CMake has installed the runtime. + sources = {"mcpp_openmp_anchor.c"}, + targets = {["openmp"] = {kind = "lib"}}, + linux = {ldflags = {"-Lmcpp_openmp/lib", "-lomp", "-lpthread", "-ldl"}}, + macosx = {ldflags = {"-Lmcpp_openmp/lib", "-lomp"}}, + windows = { + ldflags = {"-Lmcpp_openmp/lib", "-llibomp"}, + runtime = {library_dirs = {"mcpp_openmp/bin"}}, + }, + deps = {}, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +local function quote(value) + value = tostring(value) + if os.host() == "windows" then + return '"' .. value:gsub('"', '\\"') .. '"' + end + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function tool(dep, name) + local info = pkginfo.build_dep(dep) + if info then + for _, candidate in ipairs({ + path.join(info.bin or info.path or "", name), + path.join(info.path or "", "CMake.app", "Contents", "bin", name), + }) do + if os.isfile(candidate) then return candidate end + end + end + return name +end + +local function run(command, logfile) + command = command .. " >> " .. quote(logfile) .. " 2>&1" + if os.host() == "windows" then + -- xlings cannot reliably invoke a quoted .exe directly on Windows. + -- A batch file preserves CMake's arguments and reports its exit code. + local bat = path.join(pkginfo.install_dir(), "mcpp_openmp_run.bat") + io.writefile(bat, "@echo off\r\n" .. command + .. "\r\nif errorlevel 1 exit /b 1\r\nexit /b 0\r\n") + command = "cmd /c " .. quote(bat) + end + local ok, result = pcall(os.exec, command) + if ok and result then return true end + local lines = {} + if os.isfile(logfile) then + for line in (io.readfile(logfile) .. "\n"):gmatch("(.-)\n") do + lines[#lines + 1] = line + end + end + local tail = table.concat(lines, "\n", math.max(1, #lines - 59), #lines) + log.error("compat.openmp: CMake failed; see %s\n%s", logfile, tail) + return false +end + +function install() + local prefix = pkginfo.install_dir() + local root = "llvm-project-" .. pkginfo.version() .. ".src" + if not os.isdir(root) then + log.error("compat.openmp: missing extracted source %s", root) + return false + end + os.tryrm(prefix) + os.mv(root, prefix) + local build = path.join(prefix, "mcpp_openmp_build") + local output = path.join(prefix, "mcpp_openmp") + local logfile = path.join(prefix, "mcpp_openmp_build.log") + local windows = os.host() == "windows" + local cmake = tool("xim:cmake", windows and "cmake.exe" or "cmake") + local python = tool("xim:python", windows and "python.exe" or "python3") + local options = windows and ' -G "Visual Studio 17 2022" -A x64 -DLIBOMP_ENABLE_SHARED=ON' + or ' -G "Unix Makefiles" -DLIBOMP_ENABLE_SHARED=OFF' + if not windows then + -- install() runs before consumer toolchain resolution. Build the C-ABI + -- runtime with the system SDK; libomp has no C++ standard-library ABI. + options = options .. " -DCMAKE_C_COMPILER=/usr/bin/cc -DCMAKE_CXX_COMPILER=/usr/bin/c++" + .. " -DCMAKE_ASM_COMPILER=/usr/bin/cc -DCMAKE_MAKE_PROGRAM=/usr/bin/make" + end + local configure = quote(cmake) .. " -S " .. quote(path.join(prefix, "openmp")) + .. " -B " .. quote(build) .. " -DCMAKE_BUILD_TYPE=Release" + .. " -DCMAKE_INSTALL_PREFIX=" .. quote(output) + .. " -DPython3_EXECUTABLE=" .. quote(python) + .. " -DOPENMP_ENABLE_LIBOMPTARGET=OFF -DOPENMP_ENABLE_OMPT_TOOLS=OFF" + .. " -DLIBOMP_OMPT_SUPPORT=OFF -DLIBOMP_USE_ITT_NOTIFY=OFF" .. options + if not run(configure, logfile) then return false end + if not run(quote(cmake) .. " --build " .. quote(build) .. " --config Release --parallel 4", logfile) then return false end + if windows then + -- The Visual Studio generator can omit the archive of upstream's + -- source-less ompimp target. The DLL link already produced an import + -- library for these exact exports; give install() its expected name. + local expected = path.join(build, "runtime", "src", "Release", "libomp.lib") + local linked = path.join(build, "runtime", "src", "Release", "libomp.dll.lib") + if not os.isfile(expected) and os.isfile(linked) then + os.cp(linked, expected) + end + end + if not run(quote(cmake) .. " --install " .. quote(build) .. " --config Release", logfile) then return false end + local library = path.join(output, "lib", windows and "libomp.lib" or "libomp.a") + if not os.isfile(library) or not os.isfile(path.join(output, "include", "omp.h")) then + log.error("compat.openmp: missing installed header/library; see %s", logfile) + return false + end + io.writefile(path.join(prefix, "mcpp_openmp_anchor.c"), "int mcpp_compat_openmp_anchor(void) { return 0; }\n") + return true +end diff --git a/tests/examples/openmp/mcpp.toml b/tests/examples/openmp/mcpp.toml new file mode 100644 index 00000000..bfd87cd5 --- /dev/null +++ b/tests/examples/openmp/mcpp.toml @@ -0,0 +1,20 @@ +# OpenMP consumer test: the package supplies the compiler-facing omp.h entry +# point, while this member enables the selected compiler's OpenMP runtime. +[package] +name = "openmp-tests" +version = "0.1.0" + +[dependencies.compat] +openmp = "22.1.8" + +[target.'cfg(linux)'.build] +cxxflags = ["-fopenmp"] + +[target.'cfg(macos)'.build] +cxxflags = ["-Xclang", "-fopenmp"] + +[target.'cfg(windows)'.build] +cxxflags = ["-fopenmp"] + +[indices] +compat = { path = "../../.." } diff --git a/tests/examples/openmp/tests/openmp.cpp b/tests/examples/openmp/tests/openmp.cpp new file mode 100644 index 00000000..e8992207 --- /dev/null +++ b/tests/examples/openmp/tests/openmp.cpp @@ -0,0 +1,18 @@ +#include + +#ifndef _OPENMP +#error "The compiler must enable OpenMP; a header cannot supply _OPENMP." +#endif + +int main() { + omp_set_dynamic(0); + int sum = 0; + int threads = 0; +#pragma omp parallel num_threads(2) reduction(+:sum) + { +#pragma omp single + threads = omp_get_num_threads(); + sum += omp_get_thread_num() + 1; + } + return threads == 2 && sum == 3 ? 0 : 1; +}