From 9b1e87a34236a13d65ccae837d59d557f6483142 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 01:35:11 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(openssl):=20build=20on=20windows=20too?= =?UTF-8?q?=20=E2=80=94=20perl=20Configure=20VC-WIN64A=20+=20NMAKE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit windows was the one platform this package skipped, and it blocks more than itself: mcpplibs.grpc cannot ship a windows build at all while its TLS dependency has no windows entry, and CI proves it before compiling anything — error: xlings install_packages failed for 'compat.openssl@3.5.1' E_NOT_FOUND: package 'compat:openssl@3.5.1' not found No prebuilt MSVC archive is uploaded anywhere, which is what the old comment proposed. windows builds the SAME tarball as linux and macOS, in place. OpenSSL leaves exactly one option for x64 windows, and it was checked rather than assumed: Configurations/10-main.conf offers VC-WIN64A, the clang-cl configs in 50-win-clang-cl.conf are Windows-on-ARM only, and VC-WIN64A's build_scheme is VC-common — the generated makefile is for NMAKE, so GNU make cannot drive it. That brings two HOST requirements this package cannot supply itself, and the descriptor says so instead of letting them fail as noise: * perl — xim:perl ships no windows build ("The Windows answer is Strawberry Perl", xim-pkgindex pkgs/p/perl.lua). This is the same shape as the existing macOS-era perl requirement, not a new kind of dependency. * a Visual Studio C++ toolset, for nmake. xim:make is GNU make and linux-only. Both are located at build time through vswhere — installed with every VS 2017+ at a fixed path and the supported way to find a toolset; a hardcoded VS path breaks on the next release. `-products *` is not optional: without it, machines carrying only Build Tools (which CI images often are) report nothing. Windows-specific logic lives in a generated .bat rather than being one-lined through `cmd /c`. Nesting quotes through cmd for a `call vcvars64.bat && perl Configure ... && nmake` chain is its own failure mode, and a script on disk is what a maintainer can re-run by hand after a failed job. Its exit codes are distinct (10-13 toolset discovery, 20-22 Configure/nmake) and the error message maps them, so a failure names its cause instead of printing a batch error. Link flags follow what a no-shared VC build actually produces: lib\libssl.lib and lib\libcrypto.lib. Under the MSVC ABI the driver maps -l to .lib, so these carry their `lib` prefix — NOT the unix convention where -lssl finds libssl.a. System imports are the set OpenSSL's own VC build links: ws2_32, crypt32, advapi32, user32, bcrypt. tests/examples/openssl loses its windows gate: the member now declares the dependency and HAVE_OPENSSL on all three platforms, so the test is a real test everywhere rather than a no-op main() on windows. The linux and macOS paths are untouched — install() dispatches to the new windows implementation and falls through to the existing one otherwise. --- pkgs/c/compat.openssl.lua | 134 +++++++++++++++++++++++++++++-- tests/examples/openssl/mcpp.toml | 12 ++- 2 files changed, 136 insertions(+), 10 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index 884fb47..c5ab471 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -37,7 +37,15 @@ -- Platforms: -- * linux/macosx — build a fully static libcrypto.a + libssl.a from source -- via install() hook (anchor-triggered build, same pattern as compat.openblas). --- * windows — deferred (requires prebuilt MSVC libs uploaded to xlings-res). +-- * windows — source build too, through OpenSSL's only x64 windows +-- configuration: `perl Configure VC-WIN64A` + NMAKE. No prebuilt MSVC +-- archive is uploaded anywhere; the same tarball every other platform uses +-- is built in place. Note the HOST requirements this brings (see the +-- windows xpm block and _install_windows_impl): perl, because xim:perl +-- ships no windows build, and a Visual Studio C++ toolset, because +-- VC-WIN64A's build_scheme is VC-common — an NMAKE makefile, which +-- xim:make (GNU make, linux-only anyway) cannot drive. Both are probed +-- with named errors rather than left to fail as an unreadable batch error. package = { spec = "1", namespace = "compat", @@ -80,7 +88,20 @@ package = { sha256 = "529043b15cffa5f36077a4d0af83f3de399807181d607441d734196d889b641f", }, }, - -- windows deferred (prebuilt zip not yet prepared) + windows = { + -- No build deps here, and that is not an oversight: xim:perl ships + -- no windows build ("The Windows answer is Strawberry Perl" — see + -- xim-pkgindex pkgs/p/perl.lua) and xim:make is linux-only, while + -- OpenSSL's x64 windows path needs NMAKE specifically. Both are + -- HOST requirements, probed with named errors in install(). + ["3.5.1"] = { + url = { + GLOBAL = "https://github.com/openssl/openssl/releases/download/openssl-3.5.1/openssl-3.5.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/openssl/releases/download/3.5.1/openssl-3.5.1.tar.gz", + }, + sha256 = "529043b15cffa5f36077a4d0af83f3de399807181d607441d734196d889b641f", + }, + }, }, mcpp = { @@ -121,6 +142,21 @@ package = { -- this package built, so name resolution has nothing else to find, and -- libSystem already carries dl/pthread. macosx = { ldflags = { "-Llib", "-lssl", "-lcrypto" } }, + -- Windows: `nmake install_sw` on a no-shared build lays down + -- lib\libssl.lib + lib\libcrypto.lib. Under the MSVC ABI the driver + -- maps -l to .lib, so the names carry their `lib` prefix + -- (this is NOT the unix convention where -lssl finds libssl). The + -- system imports are the set OpenSSL's own VC build links: ws2_32 for + -- sockets, crypt32 for the certificate store, advapi32/user32 for the + -- entropy and UI paths, and bcrypt for RtlGenRandom. + windows = { + ldflags = { + "-Llib", + "-llibssl", + "-llibcrypto", + "-lws2_32", "-lcrypt32", "-ladvapi32", "-luser32", "-lbcrypt", + }, + }, }, } @@ -352,13 +388,97 @@ local function _install_impl() return true end +-- Windows build. OpenSSL's only x64 windows configuration is VC-WIN64A +-- (Configurations/10-main.conf; the clang-cl configs in 50-win-clang-cl.conf +-- are Windows-on-ARM only), and its build_scheme is VC-common — i.e. the +-- generated makefile is for NMAKE, not GNU make. So this path needs two things +-- from the HOST that xim cannot supply: perl, and a Visual Studio developer +-- environment for nmake. +-- +-- Everything windows-specific lives in a generated .bat rather than being +-- one-lined through `cmd /c`. Nesting quotes through cmd for a `call +-- vcvars64.bat && perl Configure ... && nmake` chain is its own failure mode, +-- and a script on disk is also what a maintainer can re-run by hand after a +-- failed CI job. +local function _install_windows_impl() + local ifile = pkginfo.install_file() + local srcroot = ifile and tostring(ifile):replace(".tar.gz", "") + or ("openssl-" .. pkginfo.version()) + if not os.isdir(srcroot) then + srcroot = "openssl-" .. pkginfo.version() + end + srcroot = path.absolute(srcroot) + + local prefix = pkginfo.install_dir() + os.tryrm(prefix) + os.mkdir(prefix) + local logf = path.join(prefix, "mcpp_openssl_build.log") + local bat = path.join(prefix, "mcpp_openssl_build.bat") + + -- vswhere is installed with every VS 2017+ at a fixed location, and is the + -- supported way to find the toolset; hardcoding a VS path breaks on the + -- next release. `-products *` is required or Build Tools-only machines + -- (which is what CI images often are) report nothing. + io.writefile(bat, table.concat({ + "@echo off", + "setlocal", + 'set "VSWHERE=%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe"', + 'if not exist "%VSWHERE%" exit /b 10', + 'for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * ' .. + '-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ' .. + '-property installationPath`) do set "VSPATH=%%i"', + 'if not defined VSPATH exit /b 11', + 'set "VCVARS=%VSPATH%\\VC\\Auxiliary\\Build\\vcvars64.bat"', + 'if not exist "%VCVARS%" exit /b 12', + 'call "%VCVARS%" >nul || exit /b 13', + 'cd /d "' .. srcroot .. '" || exit /b 14', + '(perl --version && nmake /? ) >> "' .. logf .. '" 2>&1', + 'perl Configure VC-WIN64A no-shared no-tests no-apps no-engine no-dso ' .. + '--prefix="' .. prefix .. '" --openssldir="' .. prefix .. '\\ssl" ' .. + '>> "' .. logf .. '" 2>&1 || exit /b 20', + 'nmake >> "' .. logf .. '" 2>&1 || exit /b 21', + 'nmake install_sw >> "' .. logf .. '" 2>&1 || exit /b 22', + "exit /b 0", + }, "\r\n") .. "\r\n") + + local ok, err = pcall(os.exec, string.format('cmd /c "%s"', bat)) + if not ok then + local tail = tail_lines(logf, 40) or "" + log.error("%s", "compat.openssl: windows build failed (" .. tostring(err) .. + ")\nexit 10-13 = no Visual Studio C++ toolset found (vswhere/vcvars64), " .. + "20-22 = Configure/nmake failed.\nHOST REQUIREMENTS on windows: perl " .. + "(Strawberry Perl -- xim:perl has no windows build) and a Visual Studio " .. + "C++ toolset for nmake.\n--- last 40 lines of " .. tostring(logf) .. + " ---\n" .. tail) + return false + end + + -- no-shared VC builds land libssl.lib / libcrypto.lib in \lib. + local libdir = path.join(prefix, "lib") + if not os.isfile(path.join(libdir, "libssl.lib")) + or not os.isfile(path.join(libdir, "libcrypto.lib")) then + log.error("compat.openssl: windows build produced no libssl.lib / " + .. "libcrypto.lib under %s (see %s)", libdir, logf) + return false + end + + io.writefile(path.join(prefix, "mcpp_openssl_anchor.c"), + "int mcpp_compat_openssl_anchor(void) { return 0; }\n") + return true +end + function install() - -- Windows is deferred: there is no windows xpm block, so version - -- resolution already fails before this point. Kept as a named error in - -- case a windows entry is added before this hook learns to build there. if os.host() == "windows" then - log.error("compat.openssl: windows is not yet supported") - return false + local okw, resw = pcall(_install_windows_impl) + if not okw then + log.error("compat.openssl install() failed on windows: %s", tostring(resw)) + return false + end + if not resw then + log.error("compat.openssl install() returned false on windows") + return false + end + return true end local ok, result = pcall(_install_impl) if not ok then diff --git a/tests/examples/openssl/mcpp.toml b/tests/examples/openssl/mcpp.toml index ebe8c4c..bfccb20 100644 --- a/tests/examples/openssl/mcpp.toml +++ b/tests/examples/openssl/mcpp.toml @@ -7,9 +7,9 @@ # and this member is what isolates a failure to openssl itself rather than to # the feature wiring around it. # -# linux + macOS only: there is no windows xpm entry yet (prebuilt MSVC archives -# unpublished), so on windows the member carries no dependency and the test -# compiles to a no-op main(). +# All three platforms: windows now builds from the same tarball through +# `perl Configure VC-WIN64A` + NMAKE, so the dependency and HAVE_OPENSSL are +# declared there too and the test is a real test on every platform. [package] name = "openssl-tests" version = "0.1.0" @@ -25,3 +25,9 @@ openssl = "3.5.1" [target.'cfg(macos)'.build] cxxflags = ["-DHAVE_OPENSSL=1"] + +[target.'cfg(windows)'.dependencies.compat] +openssl = "3.5.1" + +[target.'cfg(windows)'.build] +cxxflags = ["-DHAVE_OPENSSL=1"] From fe2a6cfcae84418f34c67b8d477f24a07914f134 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 01:39:35 +0800 Subject: [PATCH 2/7] debug(openssl): make the windows install() observable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first windows attempt failed in 3s with nothing to go on: xlings reports a bare `E_INTERNAL: [openssl] failed:`, log.error output never reached the job, and CI's log-dump step printed 'no install() build logs found' — the log did not exist yet because the failure was upstream of writing it. So the log is now opened FIRST and appended at every step from the Lua side, before anything that can fail: prefix, cwd, install_file, the resolved srcroot (with a directory listing when it cannot be found), the batch invocation and its result, and the contents of lib/ during verification. That file is the only channel that survives a windows install() failure, and CI's 'Dump install() build logs on failure' step is what prints it. --- pkgs/c/compat.openssl.lua | 43 ++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index c5ab471..7173765 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -401,19 +401,40 @@ end -- and a script on disk is also what a maintainer can re-run by hand after a -- failed CI job. local function _install_windows_impl() - local ifile = pkginfo.install_file() + -- The log is opened FIRST and appended to at every step, before anything + -- that can fail. xlings swallows an install() hook's log.error on windows — + -- a failure surfaces only as a bare `E_INTERNAL: [openssl] failed:` — so + -- this file is the single channel that survives, and CI's "Dump install() + -- build logs on failure" step is what prints it. Without it a windows + -- failure is undebuggable from a CI run. + local prefix = pkginfo.install_dir() + os.tryrm(prefix) + os.mkdir(prefix) + local logf = path.join(prefix, "mcpp_openssl_build.log") + local bat = path.join(prefix, "mcpp_openssl_build.bat") + + local function note(msg) + local fh = io.open(logf, "a") + if fh then fh:write("[mcpp] " .. tostring(msg) .. "\n"); fh:close() end + end + note("windows install() start; prefix=" .. tostring(prefix)) + note("cwd=" .. tostring(os.curdir())) + + local ifile = pkginfo.install_file() + note("install_file=" .. tostring(ifile)) local srcroot = ifile and tostring(ifile):replace(".tar.gz", "") or ("openssl-" .. pkginfo.version()) if not os.isdir(srcroot) then + note("srcroot '" .. tostring(srcroot) .. "' is not a dir; falling back") srcroot = "openssl-" .. pkginfo.version() end + if not os.isdir(srcroot) then + note("FATAL: no source dir found; entries in cwd:") + for _, f in ipairs(os.filedirs("*")) do note(" " .. tostring(f)) end + return false + end srcroot = path.absolute(srcroot) - - local prefix = pkginfo.install_dir() - os.tryrm(prefix) - os.mkdir(prefix) - local logf = path.join(prefix, "mcpp_openssl_build.log") - local bat = path.join(prefix, "mcpp_openssl_build.bat") + note("srcroot=" .. srcroot) -- vswhere is installed with every VS 2017+ at a fixed location, and is the -- supported way to find the toolset; hardcoding a VS path breaks on the @@ -441,7 +462,9 @@ local function _install_windows_impl() "exit /b 0", }, "\r\n") .. "\r\n") + note("wrote " .. bat .. "; running it") local ok, err = pcall(os.exec, string.format('cmd /c "%s"', bat)) + note("batch returned; ok=" .. tostring(ok) .. " err=" .. tostring(err)) if not ok then local tail = tail_lines(logf, 40) or "" log.error("%s", "compat.openssl: windows build failed (" .. tostring(err) .. @@ -455,6 +478,12 @@ local function _install_windows_impl() -- no-shared VC builds land libssl.lib / libcrypto.lib in \lib. local libdir = path.join(prefix, "lib") + note("checking " .. libdir) + if os.isdir(libdir) then + for _, f in ipairs(os.files(path.join(libdir, "*"))) do note(" lib/ " .. tostring(f)) end + else + note(" (no lib/ directory was produced)") + end if not os.isfile(path.join(libdir, "libssl.lib")) or not os.isfile(path.join(libdir, "libcrypto.lib")) then log.error("compat.openssl: windows build produced no libssl.lib / " From 37ae1942d6cf5ebca145692bba9fa3c6b1bc1057 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 02:33:44 +0800 Subject: [PATCH 3/7] fix(openssl): the xlings sandbox has no os.curdir(), and an unknown call kills install() silently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The diagnostic log added last commit did its job on the first run and contained exactly one line: [mcpp] windows install() start; prefix=D:\a\...\compat-x-openssl\3.5.1 i.e. install() died on the very next statement, os.curdir(), with no message anywhere — not in the job log, not in xlings' error, which stayed a bare E_INTERNAL. The xlings sandbox exposes a SUBSET of xmake's Lua API, and calling outside it terminates the hook silently. That is a nasty failure mode to debug blind, so it is now structural rather than a one-off fix: os.curdir() is gone, and every remaining call that might not be in the sandbox (pkginfo.install_file, os.filedirs, path.absolute, os.files) goes through a `safe` helper that pcalls it and writes 'call failed: -> ' into the log with a fallback value. --- pkgs/c/compat.openssl.lua | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index 7173765..a60d21e 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -418,9 +418,22 @@ local function _install_windows_impl() if fh then fh:write("[mcpp] " .. tostring(msg) .. "\n"); fh:close() end end note("windows install() start; prefix=" .. tostring(prefix)) - note("cwd=" .. tostring(os.curdir())) - local ifile = pkginfo.install_file() + -- Every call below goes through this. The xlings sandbox exposes a SUBSET + -- of xmake's Lua API, and calling something outside it kills install() + -- silently — the first attempt died on os.curdir() with no message at all, + -- leaving only the line above in the log. `safe` turns that class of + -- failure into a log line naming the call. + local function safe(label, fn, fallback) + local ok, res = pcall(fn) + if not ok then + note("call failed: " .. label .. " -> " .. tostring(res)) + return fallback + end + return res + end + + local ifile = safe("pkginfo.install_file()", function() return pkginfo.install_file() end) note("install_file=" .. tostring(ifile)) local srcroot = ifile and tostring(ifile):replace(".tar.gz", "") or ("openssl-" .. pkginfo.version()) @@ -429,12 +442,13 @@ local function _install_windows_impl() srcroot = "openssl-" .. pkginfo.version() end if not os.isdir(srcroot) then - note("FATAL: no source dir found; entries in cwd:") - for _, f in ipairs(os.filedirs("*")) do note(" " .. tostring(f)) end + note("FATAL: no source dir found. Entries beside it:") + local entries = safe("os.filedirs('*')", function() return os.filedirs("*") end, {}) + for _, f in ipairs(entries) do note(" " .. tostring(f)) end return false end - srcroot = path.absolute(srcroot) - note("srcroot=" .. srcroot) + srcroot = safe("path.absolute(srcroot)", function() return path.absolute(srcroot) end, srcroot) + note("srcroot=" .. tostring(srcroot)) -- vswhere is installed with every VS 2017+ at a fixed location, and is the -- supported way to find the toolset; hardcoding a VS path breaks on the @@ -480,7 +494,8 @@ local function _install_windows_impl() local libdir = path.join(prefix, "lib") note("checking " .. libdir) if os.isdir(libdir) then - for _, f in ipairs(os.files(path.join(libdir, "*"))) do note(" lib/ " .. tostring(f)) end + local produced = safe("os.files(lib/*)", function() return os.files(path.join(libdir, "*")) end, {}) + for _, f in ipairs(produced) do note(" lib/ " .. tostring(f)) end else note(" (no lib/ directory was produced)") end From 6fdf286e9a8cfc9024aa858066e6eb4275fc405d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 02:40:34 +0800 Subject: [PATCH 4/7] fix(openssl/windows): report through the log, not through the exit code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second windows run, with the instrumentation from the previous commit: [mcpp] call failed: path.absolute(srcroot) -> attempt to call a nil value [mcpp] wrote ...\mcpp_openssl_build.bat; running it [mcpp] batch returned; ok=true err=nil [mcpp] (no lib/ directory was produced) Two findings, both acted on: 1. path.absolute() is not in the xlings sandbox either — the `safe` helper caught it and fell back, which is what let the run continue at all. The call is now gone rather than guarded: pkginfo.install_file() already returns an absolute path, so srcroot derived from it is absolute too. What the path DOES need is separator normalisation — it arrives with '/' and '\' mixed, and cmd wants backslashes. 2. os.exec reported success for a batch that produced nothing and wrote nothing to the log. That channel cannot be trusted here, so the batch no longer communicates through its exit code: every step echoes into the log BEFORE running, and the script always exits 0 after recording RESULT=, which Lua reads back and decides on. os.exec's return is kept but demoted to an advisory log line. The batch is also written with plain \n now. io.writefile already produces CRLF on windows, so emitting \r\n gave \r\r\n — a batch whose stray CR lands inside `set` values and breaks parsing in exactly the way observed: no output, no work, and a clean exit. --- pkgs/c/compat.openssl.lua | 75 ++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index a60d21e..c27dd4e 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -447,41 +447,74 @@ local function _install_windows_impl() for _, f in ipairs(entries) do note(" " .. tostring(f)) end return false end - srcroot = safe("path.absolute(srcroot)", function() return path.absolute(srcroot) end, srcroot) + -- path.absolute() is NOT in the xlings sandbox (verified: "attempt to call + -- a nil value"), and it is not needed — pkginfo.install_file() already + -- returns an absolute path, so srcroot derived from it is absolute too. + -- cmd wants backslashes; the path arrives with both separators mixed. + srcroot = tostring(srcroot):gsub("/", "\\") note("srcroot=" .. tostring(srcroot)) -- vswhere is installed with every VS 2017+ at a fixed location, and is the -- supported way to find the toolset; hardcoding a VS path breaks on the -- next release. `-products *` is required or Build Tools-only machines -- (which is what CI images often are) report nothing. + -- The batch reports through the LOG, not through its exit code: the first + -- attempt came back ok=true from os.exec while having produced nothing and + -- written nothing, so that channel cannot be trusted here. Every step + -- announces itself into the log BEFORE running, and the script always + -- exits 0 after recording RESULT=, which is what Lua then reads. + -- + -- Written with plain \n: io.writefile on windows already produces CRLF, and + -- emitting \r\n here would give \r\r\n — a batch file whose stray CR ends + -- up inside `set` values and breaks parsing in ways that look like nothing + -- happened at all. + local logw = tostring(logf):gsub("/", "\\") + local prefw = tostring(prefix):gsub("/", "\\") io.writefile(bat, table.concat({ "@echo off", - "setlocal", + 'echo [bat] started >> "' .. logw .. '" 2>&1', 'set "VSWHERE=%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe"', - 'if not exist "%VSWHERE%" exit /b 10', - 'for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * ' .. - '-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ' .. - '-property installationPath`) do set "VSPATH=%%i"', - 'if not defined VSPATH exit /b 11', + 'echo [bat] vswhere=%VSWHERE% >> "' .. logw .. '" 2>&1', + 'if not exist "%VSWHERE%" ( echo [bat] RESULT=10 vswhere missing >> "' .. logw .. '" & exit /b 0 )', + 'for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"', + 'echo [bat] vspath=%VSPATH% >> "' .. logw .. '" 2>&1', + 'if not defined VSPATH ( echo [bat] RESULT=11 no VC toolset >> "' .. logw .. '" & exit /b 0 )', 'set "VCVARS=%VSPATH%\\VC\\Auxiliary\\Build\\vcvars64.bat"', - 'if not exist "%VCVARS%" exit /b 12', - 'call "%VCVARS%" >nul || exit /b 13', - 'cd /d "' .. srcroot .. '" || exit /b 14', - '(perl --version && nmake /? ) >> "' .. logf .. '" 2>&1', - 'perl Configure VC-WIN64A no-shared no-tests no-apps no-engine no-dso ' .. - '--prefix="' .. prefix .. '" --openssldir="' .. prefix .. '\\ssl" ' .. - '>> "' .. logf .. '" 2>&1 || exit /b 20', - 'nmake >> "' .. logf .. '" 2>&1 || exit /b 21', - 'nmake install_sw >> "' .. logf .. '" 2>&1 || exit /b 22', + 'if not exist "%VCVARS%" ( echo [bat] RESULT=12 no vcvars64 >> "' .. logw .. '" & exit /b 0 )', + 'call "%VCVARS%" >> "' .. logw .. '" 2>&1', + 'if errorlevel 1 ( echo [bat] RESULT=13 vcvars failed >> "' .. logw .. '" & exit /b 0 )', + 'echo [bat] toolset ready >> "' .. logw .. '" 2>&1', + 'cd /d "' .. srcroot .. '"', + 'if errorlevel 1 ( echo [bat] RESULT=14 cd failed >> "' .. logw .. '" & exit /b 0 )', + 'where perl >> "' .. logw .. '" 2>&1', + 'where nmake >> "' .. logw .. '" 2>&1', + 'echo [bat] configuring >> "' .. logw .. '" 2>&1', + 'perl Configure VC-WIN64A no-shared no-tests no-apps no-engine no-dso --prefix="' .. prefw .. '" --openssldir="' .. prefw .. '\\ssl" >> "' .. logw .. '" 2>&1', + 'if errorlevel 1 ( echo [bat] RESULT=20 Configure failed >> "' .. logw .. '" & exit /b 0 )', + 'echo [bat] building >> "' .. logw .. '" 2>&1', + 'nmake >> "' .. logw .. '" 2>&1', + 'if errorlevel 1 ( echo [bat] RESULT=21 nmake failed >> "' .. logw .. '" & exit /b 0 )', + 'echo [bat] installing >> "' .. logw .. '" 2>&1', + 'nmake install_sw >> "' .. logw .. '" 2>&1', + 'if errorlevel 1 ( echo [bat] RESULT=22 nmake install_sw failed >> "' .. logw .. '" & exit /b 0 )', + 'echo [bat] RESULT=0 >> "' .. logw .. '" 2>&1', "exit /b 0", - }, "\r\n") .. "\r\n") + }, "\n") .. "\n") note("wrote " .. bat .. "; running it") - local ok, err = pcall(os.exec, string.format('cmd /c "%s"', bat)) - note("batch returned; ok=" .. tostring(ok) .. " err=" .. tostring(err)) - if not ok then + local batw = tostring(bat):gsub("/", "\\") + local ok, err = pcall(os.exec, string.format('cmd /c "%s"', batw)) + note("os.exec ok=" .. tostring(ok) .. " err=" .. tostring(err) + .. " (advisory only -- RESULT= in this log decides)") + + local content = "" + local rok, rdata = pcall(io.readfile, logf) + if rok and rdata then content = tostring(rdata) end + local result = content:match("%[bat%] RESULT=(%d+)") + note("batch RESULT=" .. tostring(result)) + if result ~= "0" then local tail = tail_lines(logf, 40) or "" - log.error("%s", "compat.openssl: windows build failed (" .. tostring(err) .. + log.error("%s", "compat.openssl: windows build failed (RESULT=" .. tostring(result) .. ")\nexit 10-13 = no Visual Studio C++ toolset found (vswhere/vcvars64), " .. "20-22 = Configure/nmake failed.\nHOST REQUIREMENTS on windows: perl " .. "(Strawberry Perl -- xim:perl has no windows build) and a Visual Studio " .. From eae9fed96ca627285f1dafb5ed0013e159b917f3 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 02:43:53 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix(openssl/windows):=20the=20batch=20needs?= =?UTF-8?q?=20CRLF=20=E2=80=94=20cmd=20resumes=20a=20called=20script=20by?= =?UTF-8?q?=20file=20offset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third run, and this time the log shows the batch working right up to the point it matters: [bat] vspath=C:\Program Files\Microsoft Visual Studio\18\Enterprise ** Visual Studio 2026 Developer Command Prompt v18.8.2 [vcvarsall.bat] Environment initialized for: 'x64' …and then nothing. No 'toolset ready', no 'where perl', no RESULT, exit 0. cmd reads a batch file by FILE OFFSET and that bookkeeping assumes CRLF. With the LF-only file the previous commit introduced, returning from `call` resumes at the wrong position and lands on EOF — a script that reports success having done nothing past its first call. This also corrects the reasoning in that commit: it claimed io.writefile translates \n to CRLF on windows, so emitting \r\n would double it. The run disproves that — io.writefile writes bytes verbatim, which is exactly why the LF-only file reached cmd as LF-only. Line endings go back to \r\n; everything else from that commit (per-step logging, RESULT= reporting rather than trusting os.exec) stays, and is what made this diagnosable at all. --- pkgs/c/compat.openssl.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index c27dd4e..0012e51 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -464,10 +464,14 @@ local function _install_windows_impl() -- announces itself into the log BEFORE running, and the script always -- exits 0 after recording RESULT=, which is what Lua then reads. -- - -- Written with plain \n: io.writefile on windows already produces CRLF, and - -- emitting \r\n here would give \r\r\n — a batch file whose stray CR ends - -- up inside `set` values and breaks parsing in ways that look like nothing - -- happened at all. + -- CRLF line endings are REQUIRED, and this was established the hard way. + -- io.writefile writes bytes verbatim (it does not translate \n), and with + -- an LF-only batch the run got as far as `call "%VCVARS%"` — the log even + -- shows "[vcvarsall.bat] Environment initialized for: 'x64'" — and then + -- stopped dead: no further echo, no RESULT, exit 0. cmd reads a batch by + -- FILE OFFSET and its bookkeeping assumes CRLF, so on returning from a + -- `call` it resumes at the wrong position and hits EOF. The symptom is a + -- script that "succeeds" having done nothing after the first call. local logw = tostring(logf):gsub("/", "\\") local prefw = tostring(prefix):gsub("/", "\\") io.writefile(bat, table.concat({ @@ -499,7 +503,7 @@ local function _install_windows_impl() 'if errorlevel 1 ( echo [bat] RESULT=22 nmake install_sw failed >> "' .. logw .. '" & exit /b 0 )', 'echo [bat] RESULT=0 >> "' .. logw .. '" 2>&1', "exit /b 0", - }, "\n") .. "\n") + }, "\r\n") .. "\r\n") note("wrote " .. bat .. "; running it") local batw = tostring(bat):gsub("/", "\\") From bcbeb9cbc4f2e23d7a6627fc90fd25ed5e6fa365 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 03:04:22 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix(openssl/windows):=20run=20vcvars=20in?= =?UTF-8?q?=20a=20CHILD=20cmd=20=E2=80=94=20it=20terminates=20its=20caller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three runs in a row ended the same way, and the CRLF change did not move it: [bat] vspath=C:\Program Files\Microsoft Visual Studio\18\Enterprise ** Visual Studio 2026 Developer Command Prompt v18.8.2 [vcvarsall.bat] Environment initialized for: 'x64' vcvars SUCCEEDS and then the script is gone: no further echo, no RESULT, exit 0. Visual Studio's developer-prompt script terminates the batch that calls it, so no amount of error handling after the `call` can ever run — the previous commit blamed line endings, and this run disproves that (it ran with CRLF and behaved identically). The fix is structural rather than another guess at how vcvars exits: the build moves into a second script invoked as `cmd /c `. vcvars can then only take that CHILD process down, while the outer script survives to record RESULT=%errorlevel%. The vcvars path travels by environment variable instead of as an argument, because a child cmd inherits the environment and that avoids another layer of quoting around a path containing spaces. The inner script keeps the same exit codes (13 vcvars, 14 cd, 20/21/22 Configure/nmake/install_sw) and the same per-step logging, so the next failure — if there is one — still names itself. --- pkgs/c/compat.openssl.lua | 58 +++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index 0012e51..0d02bf2 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -412,6 +412,7 @@ local function _install_windows_impl() os.mkdir(prefix) local logf = path.join(prefix, "mcpp_openssl_build.log") local bat = path.join(prefix, "mcpp_openssl_build.bat") + local inner = path.join(prefix, "mcpp_openssl_inner.bat") local function note(msg) local fh = io.open(logf, "a") @@ -474,34 +475,57 @@ local function _install_windows_impl() -- script that "succeeds" having done nothing after the first call. local logw = tostring(logf):gsub("/", "\\") local prefw = tostring(prefix):gsub("/", "\\") - io.writefile(bat, table.concat({ + local innerw = tostring(inner):gsub("/", "\\") + io.writefile(inner, table.concat({ "@echo off", - 'echo [bat] started >> "' .. logw .. '" 2>&1', - 'set "VSWHERE=%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe"', - 'echo [bat] vswhere=%VSWHERE% >> "' .. logw .. '" 2>&1', - 'if not exist "%VSWHERE%" ( echo [bat] RESULT=10 vswhere missing >> "' .. logw .. '" & exit /b 0 )', - 'for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"', - 'echo [bat] vspath=%VSPATH% >> "' .. logw .. '" 2>&1', - 'if not defined VSPATH ( echo [bat] RESULT=11 no VC toolset >> "' .. logw .. '" & exit /b 0 )', - 'set "VCVARS=%VSPATH%\\VC\\Auxiliary\\Build\\vcvars64.bat"', - 'if not exist "%VCVARS%" ( echo [bat] RESULT=12 no vcvars64 >> "' .. logw .. '" & exit /b 0 )', - 'call "%VCVARS%" >> "' .. logw .. '" 2>&1', - 'if errorlevel 1 ( echo [bat] RESULT=13 vcvars failed >> "' .. logw .. '" & exit /b 0 )', + -- vcvars runs HERE, in a child cmd, so whatever it does to its caller + -- cannot reach the outer script. + 'call "%MCPP_VCVARS%" >> "' .. logw .. '" 2>&1', + 'if errorlevel 1 exit /b 13', 'echo [bat] toolset ready >> "' .. logw .. '" 2>&1', 'cd /d "' .. srcroot .. '"', - 'if errorlevel 1 ( echo [bat] RESULT=14 cd failed >> "' .. logw .. '" & exit /b 0 )', + 'if errorlevel 1 exit /b 14', 'where perl >> "' .. logw .. '" 2>&1', 'where nmake >> "' .. logw .. '" 2>&1', 'echo [bat] configuring >> "' .. logw .. '" 2>&1', 'perl Configure VC-WIN64A no-shared no-tests no-apps no-engine no-dso --prefix="' .. prefw .. '" --openssldir="' .. prefw .. '\\ssl" >> "' .. logw .. '" 2>&1', - 'if errorlevel 1 ( echo [bat] RESULT=20 Configure failed >> "' .. logw .. '" & exit /b 0 )', + 'if errorlevel 1 exit /b 20', 'echo [bat] building >> "' .. logw .. '" 2>&1', 'nmake >> "' .. logw .. '" 2>&1', - 'if errorlevel 1 ( echo [bat] RESULT=21 nmake failed >> "' .. logw .. '" & exit /b 0 )', + 'if errorlevel 1 exit /b 21', 'echo [bat] installing >> "' .. logw .. '" 2>&1', 'nmake install_sw >> "' .. logw .. '" 2>&1', - 'if errorlevel 1 ( echo [bat] RESULT=22 nmake install_sw failed >> "' .. logw .. '" & exit /b 0 )', - 'echo [bat] RESULT=0 >> "' .. logw .. '" 2>&1', + 'if errorlevel 1 exit /b 22', + "exit /b 0", + }, "\r\n") .. "\r\n") + + -- Outer script: find the toolset, then hand the actual build to the inner + -- script in a CHILD cmd and record its exit code. + -- + -- The child process is the whole point. Three runs in a row died silently + -- right after `call "%VCVARS%"` succeeded — the log even showed + -- "[vcvarsall.bat] Environment initialized for: 'x64'" — and then nothing: + -- no further echo, no RESULT, exit 0. Visual Studio's developer-prompt + -- script terminates the batch that calls it. Running it inside `cmd /c + -- ` means it can only take that child down, and the outer + -- script still runs to write RESULT. The vcvars path travels by ENVIRONMENT + -- VARIABLE rather than as an argument, because a child cmd inherits the + -- environment and that avoids another layer of quoting around a path with + -- spaces. + io.writefile(bat, table.concat({ + "@echo off", + 'echo [bat] started >> "' .. logw .. '" 2>&1', + 'set "VSWHERE=%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe"', + 'echo [bat] vswhere=%VSWHERE% >> "' .. logw .. '" 2>&1', + 'if not exist "%VSWHERE%" ( echo [bat] RESULT=10 vswhere missing >> "' .. logw .. '" & exit /b 0 )', + 'for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"', + 'echo [bat] vspath=%VSPATH% >> "' .. logw .. '" 2>&1', + 'if not defined VSPATH ( echo [bat] RESULT=11 no VC toolset >> "' .. logw .. '" & exit /b 0 )', + 'set "MCPP_VCVARS=%VSPATH%\\VC\\Auxiliary\\Build\\vcvars64.bat"', + 'if not exist "%MCPP_VCVARS%" ( echo [bat] RESULT=12 no vcvars64 >> "' .. logw .. '" & exit /b 0 )', + 'echo [bat] handing build to child cmd >> "' .. logw .. '" 2>&1', + 'cmd /c "' .. innerw .. '"', + 'echo [bat] RESULT=%errorlevel% >> "' .. logw .. '" 2>&1', "exit /b 0", }, "\r\n") .. "\r\n") From f7fadf020dcd5848fd5eef2aeef7974c874a4150 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 5 Aug 2026 03:39:22 +0800 Subject: [PATCH 7/7] fix(openssl/windows): capture the VS environment instead of calling vcvars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running vcvars in a child cmd did not help — the log gained '[bat] handing build to child cmd' and vcvars' own success banner, and then stopped again with no RESULT from the OUTER script either. Whatever vcvars does on exit here, it is not something a caller can survive, in this process or a child of it. So it is no longer called. It now runs in a cmd whose only job is to dump the environment it produced, and those variables are imported into the build script: cmd /c ""%MCPP_VCVARS%" & set" > vsenv.txt for /f "tokens=1* delims==" %%a in (vsenv.txt) do set "%%a=%%b" This is the standard way build systems capture a Visual Studio environment, and it removes the dependency on vcvars returning to anybody. `&` is deliberate rather than `&&`: `set` has to run whatever exit status vcvars leaves behind. The dump also becomes evidence in its own right — if the toolset is not really there, the file is missing or short, and that is now a named failure (13) rather than a silent stop. --- pkgs/c/compat.openssl.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index 0d02bf2..f22f3e1 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -476,12 +476,21 @@ local function _install_windows_impl() local logw = tostring(logf):gsub("/", "\\") local prefw = tostring(prefix):gsub("/", "\\") local innerw = tostring(inner):gsub("/", "\\") + local envdump = path.join(prefix, "mcpp_vsenv.txt") + local envw = tostring(envdump):gsub("/", "\\") io.writefile(inner, table.concat({ "@echo off", - -- vcvars runs HERE, in a child cmd, so whatever it does to its caller - -- cannot reach the outer script. - 'call "%MCPP_VCVARS%" >> "' .. logw .. '" 2>&1', - 'if errorlevel 1 exit /b 13', + -- vcvars is never `call`ed. Three runs showed the caller vanishing the + -- moment it finished — even from a child cmd — so instead it runs in a + -- cmd whose only job is to dump the resulting environment, and those + -- variables are imported here. This is the standard way build systems + -- capture a VS environment, and it does not depend on vcvars returning + -- to anyone. Note `&` rather than `&&`: `set` must run whatever exit + -- status vcvars leaves behind. + 'echo [bat] capturing VS environment >> "' .. logw .. '" 2>&1', + 'cmd /c ""%MCPP_VCVARS%" & set" > "' .. envw .. '" 2>>"' .. logw .. '"', + 'if not exist "' .. envw .. '" ( echo [bat] no env dump produced >> "' .. logw .. '" & exit /b 13 )', + 'for /f "usebackq tokens=1* delims==" %%a in ("' .. envw .. '") do set "%%a=%%b"', 'echo [bat] toolset ready >> "' .. logw .. '" 2>&1', 'cd /d "' .. srcroot .. '"', 'if errorlevel 1 exit /b 14',