Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
# This replaces the complex manual configuration with CMake-based builds
{
"variables": {
"use_global_liblzma%": "<!(node -p \"process.env.USE_GLOBAL || (process.platform === 'linux' || process.platform === 'darwin' ? 'true' : 'false')\")",
"runtime_link%": "<!(node -p \"(process.env.RUNTIME_LINK && process.env.RUNTIME_LINK.length > 0) ? process.env.RUNTIME_LINK : (process.platform === 'linux' || process.platform === 'darwin' ? 'shared' : 'static')\")",
"enable_thread_support%": "<!(node -p \"process.env.ENABLE_THREAD_SUPPORT || 'yes'\")",
# All defaults are resolved via Python (injected by node-gyp as
# <(python)) instead of `node -p`. Required because npm spawns install
# scripts in a shell whose PATH does not include mise/aube/pnpm-managed
# node.exe on Windows (#153). node-gyp >=10 (shipped with node >=20)
# writes the absolute python path into build/config.gypi, so <(python)
# always resolves before binding.gyp parses. Quoting around <(python)
# and <(module_root_dir) preserves paths containing spaces (e.g.
# C:\Program Files\Python311\python.exe — the literal value reported
# in the issue logs).
"py3%": "<(python)",
"use_global_liblzma%": "<!(\"<(python)\" \"<(module_root_dir)/scripts/binding_config.py\" use_global_liblzma)",
"runtime_link%": "<!(\"<(python)\" \"<(module_root_dir)/scripts/binding_config.py\" runtime_link)",
"enable_thread_support%": "<!(\"<(python)\" \"<(module_root_dir)/scripts/binding_config.py\" enable_thread_support)",
Comment thread
oorabona marked this conversation as resolved.
"xz_vendor_dir": "<(module_root_dir)/deps/xz",
"py3": "<!(node -p \"process.env.npm_config_python || 'python3'\")",
"target_dir": "<(module_root_dir)/build",
"liblzma_install_dir": "<(target_dir)/liblzma"
},
Expand Down Expand Up @@ -202,10 +211,10 @@
],
"targets": [{
"target_name": "node_lzma",
"include_dirs": ["<!(node -p \"require('node-addon-api').include_dir\")"],
"include_dirs": ["<!(\"<(python)\" \"<(module_root_dir)/scripts/binding_config.py\" node_addon_api_include)"],
"defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"],
"sources": ["<!@(<(py3) scripts/walk_sources.py src)"],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"sources": ["<!@(\"<(python)\" \"<(module_root_dir)/scripts/walk_sources.py\" src)"],
"dependencies": ["<!(\"<(python)\" \"<(module_root_dir)/scripts/binding_config.py\" node_addon_api_gyp)"],
"cflags": [
"-std=c++2a",
"-Wall",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"prebuilds/",
"src/bindings/",
"src/wasm/liblzma.d.ts",
"scripts/binding_config.py",
"scripts/build_xz_with_cmake.py",
"scripts/download_xz_from_github.py",
"scripts/copy_dll.py",
Expand Down
113 changes: 113 additions & 0 deletions scripts/binding_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Resolve binding.gyp variables without invoking node.

Replaces the previous `<!(node -p "process.env.X || default")` shell-outs
that fail on Windows under mise/pnpm/aube when node.exe is not in the
shell PATH inherited by node-gyp's child process (see issue #153).

Python is used because node-gyp already requires it to evaluate gyp files
(see `find Python` step in node-gyp output), so it is the most reliable
interpreter available at this point in the install lifecycle.

Subcommands:
use_global_liblzma env USE_GLOBAL or platform default ("true" on linux/darwin, "false" elsewhere)
runtime_link env RUNTIME_LINK or platform default ("shared" on linux/darwin, "static" elsewhere)
enable_thread_support env ENABLE_THREAD_SUPPORT or "yes"
node_addon_api_include absolute path to node-addon-api include dir (POSIX-style)
Comment thread
oorabona marked this conversation as resolved.
node_addon_api_gyp absolute path to node-addon-api node_api.gyp (POSIX-style)
"""

import json
import os
import sys
from pathlib import Path


def is_unix():
return sys.platform in ("linux", "darwin")


def env_or(name, default):
val = os.environ.get(name)
return val if val else default


def use_global_liblzma():
return env_or("USE_GLOBAL", "true" if is_unix() else "false")


def runtime_link():
return env_or("RUNTIME_LINK", "shared" if is_unix() else "static")


def enable_thread_support():
return env_or("ENABLE_THREAD_SUPPORT", "yes")


def find_node_addon_api():
"""Locate node-addon-api package by walking up from the script dir.

pnpm/aube can hoist node-addon-api outside the local node_modules tree,
so we walk every parent's node_modules looking for the package.
"""
here = Path(__file__).resolve().parent
for ancestor in [here, *here.parents]:
nm = ancestor / "node_modules" / "node-addon-api"
if nm.is_dir():
return nm
sys.exit("node-addon-api not found in any parent node_modules")


def node_addon_api_include():
pkg = find_node_addon_api()
pkg_json = pkg / "package.json"
include_subpath = None
if pkg_json.is_file():
try:
with pkg_json.open(encoding="utf-8") as f:
data = json.load(f)
include_subpath = data.get("include_dir") or data.get("include")
except (OSError, json.JSONDecodeError):
include_subpath = None
if include_subpath:
resolved = (pkg / include_subpath).resolve()
else:
resolved = pkg
return str(resolved).replace(os.sep, "/")


def node_addon_api_gyp():
"""Return the gyp dependency reference matching `require('node-addon-api').gyp`.

node-addon-api exposes `node_api.gyp:nothing` (single legacy target) as
its canonical gyp dependency string. The `:nothing` suffix tells gyp
which target to depend on; without it, gyp can't resolve the dependency
(target name defaults differ from file basename here).
"""
pkg = find_node_addon_api()
gyp_path = (pkg / "node_api.gyp").resolve()
if not gyp_path.is_file():
sys.exit(f"node-addon-api node_api.gyp not found at {gyp_path}")
return f"{str(gyp_path).replace(os.sep, '/')}:nothing"


COMMANDS = {
"use_global_liblzma": use_global_liblzma,
"runtime_link": runtime_link,
"enable_thread_support": enable_thread_support,
"node_addon_api_include": node_addon_api_include,
"node_addon_api_gyp": node_addon_api_gyp,
}


def main():
if len(sys.argv) != 2:
sys.exit(f"usage: {sys.argv[0]} <{'|'.join(COMMANDS)}>")
name = sys.argv[1]
handler = COMMANDS.get(name)
if handler is None:
sys.exit(f"unknown variable: {name}")
sys.stdout.write(handler())


if __name__ == "__main__":
main()
Loading