Skip to content

Commit 8145abc

Browse files
authored
feat(compat.openblas): Windows support (import-lib + runtime DLL) + Windows CI (#55)
* feat(compat.openblas): Windows support via prebuilt import-lib + runtime DLL Adds Windows to compat.openblas. OpenBLAS has no upstream Make path for the MSVC-ABI Clang mcpp links with, so Windows uses the prebuilt x64 zip: link the MSVC import lib (lib/libopenblas.lib) and ship the runtime DLL (bin/libopenblas.dll) beside the consumer's .exe. mcpp >= 0.0.73 stages a dependency's [runtime] library_dirs *.dll next to the executable (PE has no RPATH), so the consumer launches standalone. Recipe: * xpm.windows: OpenBLAS-0.3.33-x64.zip (GLOBAL OpenMathLib GitHub; CN gitcode mcpp-res/openblas, uploaded + GET-verified byte-equal). Unpacks to bin/ lib/ include/ (no wrapper dir). * mcpp ldflags are now per-OS (the synthesized-manifest parser APPENDS ldflags, so a global -lopenblas would wrongly reach the Windows link): linux/macosx link the static -lopenblas; windows links -llibopenblas (clang MSVC maps it to libopenblas.lib). * mcpp.windows: [runtime] library_dirs=["bin"] (DLL deploy) + a generated_files anchor TU (no Make build on Windows). install() returns immediately on windows; linux/macosx keep the source build. CI (Phase D): smoke_compat_portable.sh gains a Windows-only OpenBLAS build-AND-run — cblas_dgemm must produce [19 22; 43 50], and the produced .exe is launched DIRECTLY from a neutral CWD so a clean exit proves the DLL was actually deployed beside it (not merely found via mcpp run's PATH prepend). MCPP_VERSION pinned 0.0.68 -> 0.0.73 (the release carrying DLL deployment). See mcpp .agents/docs/2026-06-29-windows-runtime-dll-deployment-and-openblas.md. * fix(smoke): openblas Windows direct-launch uses absolute exe path The Phase D direct-launch assertion ran the .exe found by `find target` (a path relative to the project dir) after `cd /`, so it resolved to a nonexistent path ("No such file or directory") and failed even though the build linked the import lib, deployed bin/libopenblas.dll beside the .exe (the DLL-presence check passed), and `mcpp run` succeeded. Absolutise the exe path (exedir via pwd + basename) before the neutral-CWD launch.
1 parent e1e3716 commit 8145abc

3 files changed

Lines changed: 118 additions & 11 deletions

File tree

.github/workflows/validate.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ on:
1111
workflow_dispatch:
1212

1313
env:
14-
MCPP_VERSION: "0.0.68"
14+
# Bumped to 0.0.73: carries Windows runtime-DLL deployment, required by the
15+
# compat.openblas Windows recipe (bin/libopenblas.dll staged beside the .exe).
16+
MCPP_VERSION: "0.0.73"
1517

1618
jobs:
1719
lint:
@@ -238,14 +240,14 @@ jobs:
238240
include:
239241
- platform: macos
240242
os: macos-15
241-
archive: mcpp-0.0.68-macosx-arm64.tar.gz
242-
root: mcpp-0.0.68-macosx-arm64
243+
archive: mcpp-0.0.73-macosx-arm64.tar.gz
244+
root: mcpp-0.0.73-macosx-arm64
243245
mcpp: bin/mcpp
244246
xlings: registry/bin/xlings
245247
- platform: windows
246248
os: windows-latest
247-
archive: mcpp-0.0.68-windows-x86_64.zip
248-
root: mcpp-0.0.68-windows-x86_64
249+
archive: mcpp-0.0.73-windows-x86_64.zip
250+
root: mcpp-0.0.73-windows-x86_64
249251
mcpp: bin/mcpp.exe
250252
xlings: registry/bin/xlings.exe
251253
steps:

pkgs/c/compat.openblas.lua

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,18 @@
1818
-- headers, and emits the anchor — then compiles the anchor and links the lib
1919
-- (`-Llib -lopenblas`, with `-Llib` rewritten to <verdir>/lib).
2020
--
21-
-- Platforms: linux/macosx build from source via Make. Windows (no upstream Make
22-
-- path; OpenBLAS ships prebuilt zips) is a follow-up.
21+
-- Platforms:
22+
-- * linux/macosx — build a fully static libopenblas.a from source via Make
23+
-- (no runtime artifact; install() emits the anchor → triggers the build).
24+
-- * windows — no upstream Make path with the MSVC-ABI Clang mcpp links with, so
25+
-- use OpenBLAS's prebuilt x64 zip: link the MSVC import lib (lib/libopenblas.lib)
26+
-- and ship the runtime DLL (bin/libopenblas.dll) beside the consumer's .exe.
27+
-- mcpp >= 0.0.73 deploys a dependency's [runtime] library_dirs *.dll next to
28+
-- the executable (PE has no RPATH); see
29+
-- mcpp/.agents/docs/2026-06-29-windows-runtime-dll-deployment-and-openblas.md.
30+
-- The Windows anchor is a generated_files TU (no Make build), so install()
31+
-- returns immediately on windows. The asymmetry is intentional: only Windows
32+
-- declares a [runtime] library_dir because only Windows ships a runtime DLL.
2333
package = {
2434
spec = "1",
2535
namespace = "compat",
@@ -50,21 +60,57 @@ package = {
5060
sha256 = "6761af1d9f5d353ab4f0b7497be2643313b36c8f31caec0144bfef198e71e6ab",
5161
},
5262
},
63+
windows = {
64+
-- Prebuilt x64 zip (no Make build). Unpacks to bin/ lib/ include/ with
65+
-- no wrapper dir: bin/libopenblas.dll, lib/libopenblas.lib (MSVC import
66+
-- lib), include/cblas.h.
67+
["0.3.33"] = {
68+
url = {
69+
GLOBAL = "https://github.com/OpenMathLib/OpenBLAS/releases/download/v0.3.33/OpenBLAS-0.3.33-x64.zip",
70+
CN = "https://gitcode.com/mcpp-res/openblas/releases/download/0.3.33/OpenBLAS-0.3.33-x64.zip",
71+
},
72+
sha256 = "7ad797ef0c9a5c42e28903bf726eaaaade307dafe187ff0e923d90cd4002780c",
73+
},
74+
},
5375
},
5476

5577
mcpp = {
5678
language = "c++23",
5779
import_std = false,
5880
c_standard = "c11",
59-
-- The anchor is NOT a generated_files entry: it is emitted by install()
60-
-- so mcpp must run install() (which also builds the lib) before it can
61-
-- compile this source. include/ + lib/ are produced by `make install`.
81+
-- On linux/macosx the anchor is NOT a generated_files entry: it is emitted
82+
-- by install() so mcpp must run install() (which also builds the lib)
83+
-- before it can compile this source. include/ + lib/ are produced by
84+
-- `make install`. On windows there is no Make build, so the anchor is a
85+
-- generated_files TU (see the windows block) and install() is a no-op.
6286
sources = { "mcpp_openblas_anchor.c" },
6387
targets = { ["openblas"] = { kind = "lib" } },
6488
include_dirs = { "include" },
65-
ldflags = { "-Llib", "-lopenblas" },
6689
provides = { "blas" },
6790
deps = { },
91+
92+
-- ldflags are per-OS: the synthesized-manifest parser APPENDS ldflags, so
93+
-- a global `-lopenblas` would also reach the Windows link (wrong: there
94+
-- the lib is the import lib libopenblas.lib). linux/macosx link the static
95+
-- archive; the per-OS merge picks exactly one of these blocks by host.
96+
linux = { ldflags = { "-Llib", "-lopenblas" } },
97+
macosx = { ldflags = { "-Llib", "-lopenblas" } },
98+
windows = {
99+
-- Link the MSVC import lib lib/libopenblas.lib (clang MSVC driver maps
100+
-- `-llibopenblas` → libopenblas.lib). `-Llib` is rewritten to
101+
-- <verdir>/lib by mcpp.
102+
ldflags = { "-Llib", "-llibopenblas" },
103+
-- bin/libopenblas.dll is staged beside the consumer .exe by mcpp's
104+
-- runtime-DLL deployment (mcpp >= 0.0.73). On non-Windows the *.dll
105+
-- filter makes this declaration inert.
106+
runtime = { library_dirs = { "bin" } },
107+
-- No Make build on Windows: provide the anchor TU directly so mcpp is
108+
-- self-sufficient and never triggers install() here. (linux/macosx get
109+
-- their anchor from install(), which is what triggers the Make build.)
110+
generated_files = {
111+
["mcpp_openblas_anchor.c"] = "int mcpp_compat_openblas_anchor(void) { return 0; }\n",
112+
},
113+
},
68114
},
69115
}
70116

@@ -155,6 +201,12 @@ local function _install_impl()
155201
end
156202

157203
function install()
204+
-- Windows ships a prebuilt zip (import lib + DLL); nothing to build. The
205+
-- anchor TU comes from mcpp `generated_files`, so mcpp never needs install()
206+
-- to produce a source here — this is a no-op success.
207+
if os.host() == "windows" then
208+
return true
209+
end
158210
local ok, err = pcall(_install_impl)
159211
if not ok then
160212
log.error("compat.openblas install() failed: %s", tostring(err))

tests/smoke_compat_portable.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,4 +414,57 @@ EOF
414414
"$MCPP_BIN_POSIX" build
415415
"$MCPP_BIN_POSIX" run
416416

417+
# compat.openblas — Windows-only build-AND-run. This is the only place that
418+
# exercises the mcpp >= 0.0.73 runtime-DLL deployment end to end: the consumer
419+
# links the MSVC import lib (lib/libopenblas.lib) and, at `mcpp run`, loads the
420+
# bin/libopenblas.dll that mcpp staged beside the .exe (a build-only check would
421+
# pass even if the DLL were never deployed). cblas_dgemm computes a 2x2 GEMM and
422+
# the program returns nonzero unless the result is exactly [19 22; 43 50], so a
423+
# wrong/missing BLAS fails the run. Scoped to Windows: linux/macosx link a static
424+
# libopenblas.a (no DLL, nothing new to validate here) and macosx would add a
425+
# multi-minute source build to every portable run.
426+
if [[ "$platform" == "Windows_NT" ]]; then
427+
make_project "compat-portable-openblas-smoke"
428+
cat >> mcpp.toml <<'EOF'
429+
430+
[dependencies.compat]
431+
openblas = "0.3.33"
432+
EOF
433+
cat > src/main.cpp <<'EOF'
434+
#include <cblas.h>
435+
436+
int main() {
437+
// A (2x2, row-major) * B (2x2) = C; expect [[19,22],[43,50]].
438+
const double A[4] = {1, 2, 3, 4};
439+
const double B[4] = {5, 6, 7, 8};
440+
double C[4] = {0, 0, 0, 0};
441+
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
442+
2, 2, 2, 1.0, A, 2, B, 2, 0.0, C, 2);
443+
const double expected[4] = {19, 22, 43, 50};
444+
for (int i = 0; i < 4; ++i) {
445+
if (C[i] != expected[i]) return 10 + i; // wrong/missing BLAS
446+
}
447+
return 0;
448+
}
449+
EOF
450+
"$MCPP_BIN_POSIX" build
451+
# `mcpp run` also prepends the source bin/ to PATH, so it alone would not
452+
# prove the DLL was deployed. Run the produced .exe DIRECTLY from a neutral
453+
# CWD: Windows searches the executable's own directory first, so a clean exit
454+
# proves bin/libopenblas.dll was actually staged beside app.exe (the deploy).
455+
"$MCPP_BIN_POSIX" run
456+
exe="$(find target -type f -name '*.exe' | head -1)"
457+
[ -n "$exe" ] || { echo "FAIL: openblas smoke produced no .exe"; exit 1; }
458+
# Absolutise: `find` yields a path relative to the project dir, but the launch
459+
# runs from a neutral CWD (so the DLL can only be found beside the .exe, not
460+
# via CWD). pwd makes exedir absolute; build an absolute native exe path.
461+
exedir="$(cd "$(dirname "$exe")" && pwd)"
462+
exeabs="$exedir/$(basename "$exe")"
463+
[ -f "$exedir/libopenblas.dll" ] || {
464+
echo "FAIL: libopenblas.dll was not deployed beside the .exe ($exedir)"
465+
ls -la "$exedir"; exit 1; }
466+
( cd / && "$(to_native_path "$exeabs")" ) || {
467+
echo "FAIL: direct .exe launch failed — deployed DLL not loadable"; exit 1; }
468+
fi
469+
417470
echo "OK"

0 commit comments

Comments
 (0)