Skip to content

fix(linux): rebuild from source instead of stale prebuilt objects - #118

Merged
oransel merged 1 commit into
oransel:masterfrom
AlexRMU:fix/linux-stale-prebuilt-objects
Oct 6, 2026
Merged

oransel merged 1 commit into
oransel:masterfrom
AlexRMU:fix/linux-stale-prebuilt-objects

Conversation

@AlexRMU

@AlexRMU AlexRMU commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Problem

The npm tarball for talib@2.0.4 ships prebuilt object files in src/lib/temp/csr/ (*.o). On install, make compares timestamps, considers these objects up-to-date, skips recompilation and archives them into the .a libraries. Depending on the toolchain, this surfaces in two ways:

  1. New binutils (Debian trixie, binutils >= 2.44) — the archives built from stale objects have no symbol index, and the linker rejects them during node-gyp build:

    /usr/bin/ld: ../src/lib/lib/libta_abstract_csr.a: error adding symbols: archive has no index; run ranlib to add one
    
  2. Older binutils — the link succeeds but the addon fails at load time, because the stale objects are missing symbols from parts of the source tree (e.g. table_*.c):

    Error: .../talib/build/Release/talib.node: undefined symbol: TA_DEF_TableASize
      code: 'ERR_DLOPEN_FAILED'
    

Both are reproducible in clean containers with a fresh toolchain; on dev machines they are often masked by local rebuilds.

Reproduction

Single Dockerfile — uncomment the workaround block to apply the fix (the workaround is exactly what this PR moves into src/lib/build.js, so it runs against the unmodified talib@2.0.4 from npm):

FROM node:24-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential python3 \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN npm init -y
RUN npm install talib@2.0.4

# --- FIX (what this PR moves into src/lib/build.js) — uncomment to apply ---
# RUN cd node_modules/talib \
#  # 1) drop stale prebuilt objects shipped in the npm tarball
#  && rm -f src/lib/temp/csr/*.o src/lib/temp/csr/*.obj \
#  # 2) rebuild the static TA-LIB archives from source
#  && make -C src/lib/make/csr/linux/g++ \
#  # 3) add symbol index to the archives for new binutils
#  && for f in src/lib/lib/*.a; do ranlib "$f"; done \
#  # 4) build the N-API addon
#  && GYP=/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js \
#  && node $GYP configure && node $GYP build
# --- end FIX ---

RUN node -e "const t = require('talib'); console.log(t.version, t.functions.length)"

Note: with the fix uncommented, use npm install --ignore-scripts talib@2.0.4 (the stock install script fails before the workaround gets a chance to run) — see the verified logs below.

Without the fix (actual log)

docker build --progress=plain . fails inside npm install talib@2.0.4. The make phase completes in ~13 s with zero gcc -c invocations — the stale objects from the tarball are archived as-is — and then the addon link step dies:

#8 13.48 npm error building talib functions...
...
#8 13.49 npm error /usr/bin/ld: ../src/lib/lib/libta_abstract_csr.a: error adding symbols: archive has no index; run ranlib to add one
#8 13.49 npm error collect2: error: ld returned 1 exit status
#8 13.49 npm error make: *** [talib.target.mk:142: Release/obj.target/talib.node] Error 1
#8 13.49 npm error gyp ERR! build error
#8 13.49 npm error gyp ERR! stack Error: `make` failed with exit code: 2
#8 13.49 npm error gyp ERR! System Linux 6.18.40.1-microsoft-standard-WSL2
#8 13.49 npm error gyp ERR! node -v v24.21.0
#8 13.49 npm error gyp ERR! node-gyp -v v12.4.0
#8 13.49 npm error gyp ERR! $npm_package_version 2.0.4
#8 ERROR: process "/bin/sh -c npm install talib@2.0.4" did not complete successfully: exit code: 1

With the fix (actual log)

Full recompile — 196 gcc -c lines in the build log, ~20 s of real compilation, then ranlib and a successful addon build:

#9 [6/7] RUN cd node_modules/talib && rm -f src/lib/temp/csr/*.o src/lib/temp/csr/*.obj && make -C src/lib/make/csr/linux/g++ && for f in src/lib/lib/*.a; do ranlib "$f"; done && ...
#9 0.433 building talib functions...
#9 21.18 make[1]: Leaving directory '/app/node_modules/talib/src/lib/make/csr/linux/g++/ta_libc'
...
#10 [7/7] RUN node -e "const t = require('talib'); console.log(t.version, t.functions.length)"
#10 1.359 1.1.6 161
#10 DONE 1.4s

Environment: node:24-slim (Debian trixie, binutils 2.44), Node v24.21.0, node-gyp v12.4.0, Docker 27.5.0 on WSL2.

Changes

In the linux/darwin branch of src/lib/build.js:

  • Delete prebuilt *.o/*.obj files in src/lib/temp/csr/ before running make, so the full source tree is recompiled for the current toolchain. Non-fatal on failure (try/catch with a warning).
  • After a successful make, run a best-effort ranlib pass over all built .a archives in src/lib/lib/ so the linker accepts them on new binutils. Also non-fatal.

Verification

  • node --check src/lib/build.js passes.
  • Both reproduction builds above were run locally; the same logic has been running as a downstream pnpm patch (patchedDependencies) in a production project since 2026-09 (Dokploy deploy on Debian trixie), where it fixed both the archive has no index link failure and the undefined symbol: TA_DEF_TableASize dlopen failure.

The npm tarball ships prebuilt .o/.obj files in src/lib/temp/csr/.
make treats them as up-to-date, skips recompilation and archives the
stale objects, which then fail at dlopen with
'undefined symbol: TA_DEF_TableASize' (reproducible in clean containers
with a fresh toolchain, e.g. node:24-slim on Debian trixie).

- delete prebuilt objects in temp/csr before make so the full source
  tree is recompiled for the current toolchain
- best-effort ranlib pass over the built .a archives: newer binutils
  (>= 2.44) reject archives without a symbol index
  ('error adding symbols: archive has no index')
@oransel
oransel merged commit 83abb56 into oransel:master Oct 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants