From 44be4cf3d7080f9a73833c6991d535e808305b2f Mon Sep 17 00:00:00 2001 From: kingchenc Date: Tue, 18 Aug 2026 21:35:18 +0200 Subject: [PATCH] fix(r): build the Windows import library without objdump R CMD INSTALL failed on Windows/arm64 in configure.win. The export list was dumped from the staged wickra_abi.dll with objdump, but the objdump that resolves there is the runner image's x86_64 mingw copy at C:\mingw64, which carries no aarch64 PE backend and rejects the DLL with "file format not recognized". Asking the compiler for a matching tool via -print-prog-name returned that same x64 copy, so the previous attempt did not help. Take the export list from the staged cbindgen header instead. The header declares exactly the exported symbols -- it and the PE export table agree on all 3951 entries, for both the x86_64 and the aarch64 release artifact -- so producing the .def needs no binutils at all. dlltool is still required for the import library itself and has the same blind spot, so pass the target machine explicitly and try the candidates R can point at (BINPREF, llvm-dlltool) ahead of the bare name. A binutils build without the requested PE backend fails outright ("invalid bfd target") rather than quietly emitting a wrong-architecture archive, so the first candidate that succeeds is by construction one that can target this architecture; if none can, the build stops with the full list of attempts. On x86_64 the resulting import library is unchanged: same 3951 symbols, same referenced DLL. --- bindings/r/configure.win | 112 +++++++++++++++++++++++++++++--------- bindings/r/src/.gitignore | 1 + 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/bindings/r/configure.win b/bindings/r/configure.win index a1b84caf..fabc9fae 100644 --- a/bindings/r/configure.win +++ b/bindings/r/configure.win @@ -1,9 +1,8 @@ #!/bin/sh # Windows build: the package compiles to wickra.dll, which would collide with the # C ABI's own wickra.dll (the loader would resolve the import to the package -# itself). Stage a renamed copy, wickra_abi.dll, into src/ and build a mingw -# import library that references it by that name (objdump + dlltool, both shipped -# with Rtools — no gendef/pexports needed). install.libs.R then bundles the DLL. +# itself). Stage a renamed copy, wickra_abi.dll, into src/ and build an import +# library that references it by that name. install.libs.R then bundles the DLL. # # Self-contained by default: download the prebuilt wickra-c-.tar.gz # release asset matching this package's version. Set WICKRA_INCLUDE_DIR + @@ -30,22 +29,33 @@ wickra_download() { return 1 } +# Detect the R architecture, not the shell's. On the Windows/arm64 runner +# `uname -m` reports x86_64 (the shell runs under emulation), so the x64 C ABI +# would be pulled into an arm64 R and fail to load ("%1 is not a valid Win32 +# application"). R.version$arch reflects the R build that loads the DLL, and it +# also selects the import library's target machine below. +arch=$("${R_HOME}/bin/Rscript" -e 'cat(R.version$arch)') +case "${arch}" in + x86_64) + triple="x86_64-pc-windows-msvc" + dll_machine="i386:x86-64" + ;; + aarch64 | arm64) + triple="aarch64-pc-windows-msvc" + dll_machine="arm64" + ;; + *) + echo "wickra: unsupported Windows arch '${arch}' — set WICKRA_INCLUDE_DIR/WICKRA_LIB_DIR" + exit 1 + ;; +esac + if [ -n "${WICKRA_INCLUDE_DIR}" ] && [ -n "${WICKRA_LIB_DIR}" ]; then echo "wickra: using C ABI from WICKRA_INCLUDE_DIR / WICKRA_LIB_DIR (dev override)" inc="${WICKRA_INCLUDE_DIR}" lib="${WICKRA_LIB_DIR}" else version=$(sed -n 's/^Version:[[:space:]]*//p' DESCRIPTION) - # Detect the R architecture, not the shell's. On the Windows/arm64 runner - # `uname -m` reports x86_64 (the shell runs under emulation), so the x64 C - # ABI would be pulled into an arm64 R and fail to load ("%1 is not a valid - # Win32 application"). R.version$arch reflects the R build that loads the DLL. - arch=$("${R_HOME}/bin/Rscript" -e 'cat(R.version$arch)') - case "${arch}" in - x86_64) triple="x86_64-pc-windows-msvc" ;; - aarch64 | arm64) triple="aarch64-pc-windows-msvc" ;; - *) echo "wickra: unsupported Windows arch '${arch}' — set WICKRA_INCLUDE_DIR/WICKRA_LIB_DIR"; exit 1 ;; - esac url="https://github.com/wickra-lib/wickra/releases/download/v${version}/wickra-c-${triple}.tar.gz" echo "wickra: downloading C ABI ${triple} for v${version}" wickra_download "${url}" "src/wickra-c.tar.gz" || exit 1 @@ -56,20 +66,72 @@ fi cp "${inc}/wickra.h" src/wickra.h cp "${lib}/wickra.dll" src/wickra_abi.dll -# Use the binutils that match R's compiler (and thus the target arch). Bare -# `objdump`/`dlltool` resolve via PATH to the x64 mingw copies, which cannot read -# an arm64 wickra_abi.dll ("file format not recognized"). -print-prog-name asks -# the compiler for the matching GNU tool; fall back to the bare name otherwise. -CC=$("${R_HOME}/bin/R" CMD config CC | awk '{print $1}') -OBJDUMP=$("${CC}" -print-prog-name=objdump 2>/dev/null) -DLLTOOL=$("${CC}" -print-prog-name=dlltool 2>/dev/null) -case "${OBJDUMP}" in ""|objdump) OBJDUMP=objdump ;; esac -case "${DLLTOOL}" in ""|dlltool) DLLTOOL=dlltool ;; esac + +# Take the export list from the staged header rather than by dumping the DLL. +# cbindgen declares exactly the exported symbols, so header and PE export table +# agree entry for entry, and reading the header needs no binutils at all: the +# objdump that resolves on Windows/arm64 is the runner image's x86_64 mingw copy, +# which carries no aarch64 PE backend and rejects an arm64 wickra_abi.dll with +# "file format not recognized". { echo 'LIBRARY wickra_abi.dll' echo 'EXPORTS' - "${OBJDUMP}" -p src/wickra_abi.dll | awk '/\[ *[0-9]+\]/ {print $NF}' | grep '^wickra_' + awk ' + # Strip block and line comments first so a symbol mentioned in the generated + # documentation is never mistaken for a declaration. + BEGIN { inblock = 0 } + { + out = "" + i = 1 + n = length($0) + while (i <= n) { + two = substr($0, i, 2) + if (inblock) { + if (two == "*/") { inblock = 0; i += 2 } else { i++ } + } else if (two == "/*") { + inblock = 1 + i += 2 + } else if (two == "//") { + break + } else { + out = out substr($0, i, 1) + i++ + } + } + print out + } + ' src/wickra.h \ + | grep -oE '\bwickra_[A-Za-z0-9_]*[[:space:]]*\(' \ + | sed 's/[[:space:]]*($//' \ + | LC_ALL=C sort -u } > src/wickra_abi.def -"${DLLTOOL}" --input-def src/wickra_abi.def --dllname wickra_abi.dll \ - --output-lib src/libwickra_abi.dll.a + +# The import library has to target R's architecture. A binutils build only +# carries the PE backends it was configured with, so the x86_64 mingw dlltool +# that resolves on PATH cannot emit an arm64 library — asked for one via -m it +# fails outright ("invalid bfd target") instead of quietly producing the wrong +# thing. That loud failure is what makes trying candidates in order safe: the +# first that succeeds is by construction one that can target this architecture. +# BINPREF is R's own pointer at the matching Rtools toolchain, and llvm-dlltool +# (shipped with the clang-based aarch64 Rtools) carries every backend. +BINPREF=$("${R_HOME}/bin/R" CMD config BINPREF 2>/dev/null | tr -d '\r') || BINPREF="" +dlltool_log="src/dlltool.log" +: > "${dlltool_log}" +DLLTOOL="" +for cand in "${WICKRA_DLLTOOL}" "${BINPREF}llvm-dlltool" "${BINPREF}dlltool" llvm-dlltool dlltool; do + [ -n "${cand}" ] || continue + echo "wickra: trying ${cand} -m ${dll_machine}" >> "${dlltool_log}" + if "${cand}" -m "${dll_machine}" --input-def src/wickra_abi.def \ + --dllname wickra_abi.dll --output-lib src/libwickra_abi.dll.a >> "${dlltool_log}" 2>&1; then + DLLTOOL="${cand}" + break + fi +done +if [ -z "${DLLTOOL}" ]; then + echo "wickra: no dlltool could build a ${dll_machine} import library; attempts:" + cat "${dlltool_log}" + exit 1 +fi +rm -f "${dlltool_log}" +echo "wickra: import library built by ${DLLTOOL} (-m ${dll_machine})" exit 0 diff --git a/bindings/r/src/.gitignore b/bindings/r/src/.gitignore index c4c79468..4502d396 100644 --- a/bindings/r/src/.gitignore +++ b/bindings/r/src/.gitignore @@ -6,6 +6,7 @@ *.dylib wickra.h wickra_abi.def +dlltool.log symbols.rds wickra-c.tar.gz wickra-c/