Skip to content
Open
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
105 changes: 101 additions & 4 deletions .github/workflows/win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ jobs:
- name: Install tools and dependencies
shell: pwsh
run: |
pip3 install --use-pep517 python-dotenv jinja2
pip3 install --use-pep517 python-dotenv jinja2 requests
go install golang.org/dl/go1.24.3@latest
go1.24.3 download
go1.24.3 version
Expand Down Expand Up @@ -265,18 +265,19 @@ jobs:
if ("${{ matrix.compiler }}" -eq "mingw") {
$buildArgs += "is_mingw=true"
$buildArgs += "is_clang=false"
$buildArgs += "ten_enable_nodejs_binding=false"
} else {
$buildArgs += "is_mingw=false" # msvc
$buildArgs += "is_clang=true" # choose to use clang-cl.exe instead of cl.exe, the latter does not work yet
$buildArgs += "vs_version=2022"
$buildArgs += "ten_enable_nodejs_binding=true"
}
$buildArgs += "log_level=1"
$buildArgs += "enable_serialized_actions=true"
$buildArgs += "ten_rust_enable_gen_cargo_config=false"
$buildArgs += "ten_enable_cargo_clean=true"
$buildArgs += "ten_enable_python_binding=true"
$buildArgs += "ten_enable_go_binding=true"
$buildArgs += "ten_enable_nodejs_binding=false"
$buildArgs += "ten_enable_rust_incremental_build=false"
$buildArgs += "ten_manager_enable_frontend=false"
$argsString = $buildArgs -join " "
Expand Down Expand Up @@ -1220,6 +1221,104 @@ jobs:
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3

test-integration-nodejs:
needs: build
runs-on: windows-latest
env:
PYTHONIOENCODING: utf-8
strategy:
matrix:
build_type: [debug, release]
compiler: [msvc]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: false

- name: Enable Windows long path support
shell: pwsh
run: |
Write-Output "Enabling Windows long path support..."
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
Write-Output "Long path support enabled"

- name: Trust working directory
run: git config --global --add safe.directory "${GITHUB_WORKSPACE}"

- name: Initialize and update submodules except portal/
shell: bash
run: |
# Retrieve all submodule paths, excluding `portal/`.
submodules=$(git config --file .gitmodules --get-regexp path | awk '$2 != "portal" { print $2 }')
git submodule init
for submodule in $submodules; do
echo "Initializing submodule: $submodule"
git submodule update --init --recursive --depth 1 "$submodule"
done

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Setup MSVC
if: matrix.compiler == 'msvc'
uses: ilammy/msvc-dev-cmd@v1

- uses: actions/setup-python@v5
with:
python-version: "3.10"

- uses: actions/setup-go@v5
with:
go-version: "stable"
cache: false

- name: Install tools and dependencies
run: |
pip3 install --use-pep517 python-dotenv jinja2
go install golang.org/dl/go1.24.3@latest
go1.24.3 download
go1.24.3 version
go env -w GOFLAGS="-buildvcs=false"

- name: Get Python executable path
run: |
$pythonPath = python -c "import sys; print(sys.executable)"
Write-Output "Python executable path: $pythonPath"
$pythonDir = Split-Path $pythonPath
Write-Output "Python directory path: $pythonDir"
echo "PYTHON3_PATH=$pythonDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh

- name: Use Python path
run: |
Write-Output "The Python directory is located at: $env:PYTHON3_PATH"
shell: pwsh

- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: tests-artifacts-win-${{ matrix.build_type }}-${{ matrix.compiler }}
path: out/win/x64

- name: Extract tests artifacts preserving permissions
shell: pwsh
run: |
Expand-Archive -Path "out/win/x64/tests-artifacts.zip" -DestinationPath "out/win/x64" -Force

- name: Install Python dependencies via script
run: |
python .github/tools/setup_pytest_dependencies.py

- name: Run Tests (ten_runtime Nodejs integration tests)
env:
TEN_ENABLE_BACKTRACE_DUMP: "true"
run: |
$ENV:PATH += ";$PWD/core/ten_gn"
cd out/win/x64/
pytest -s tests/ten_runtime/integration/nodejs/

test-integration-go:
needs: build
runs-on: windows-latest
Expand All @@ -1229,8 +1328,6 @@ jobs:
matrix:
build_type: [debug, release]
compiler: [msvc, mingw]
exclude:
- compiler: msvc # not implemented yet
steps:
- uses: actions/checkout@v4
with:
Expand Down
7 changes: 7 additions & 0 deletions core/src/ten_runtime/binding/nodejs/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ ten_package("ten_nodejs_binding_system_package") {
":ten_runtime_nodejs_js",
"native:ten_runtime_nodejs",
]

# When forcing MSVC for nodejs binding, add explicit dependency on the
# msvc_force toolchain target and use its output path.
if (is_win && !is_mingw && is_clang) {
_msvc_toolchain = "//.gnfiles/build/toolchain/msvc_force:msvc_force"
deps += [ "native:ten_runtime_nodejs_msvc(${_msvc_toolchain})" ]
}
}

if (ten_enable_ten_manager) {
Expand Down
24 changes: 18 additions & 6 deletions core/src/ten_runtime/binding/nodejs/interface/addon_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import * as fs from "fs";
import * as path from "path";
import { dirname } from "path";
import { fileURLToPath } from "url";
import { fileURLToPath, pathToFileURL } from "url";

import type { Addon } from "./addon.js";
import ten_addon from "./ten_addon.js";

type Ctor<T> = {
new (): T;
new(): T;
prototype: T;
};

Expand Down Expand Up @@ -98,7 +98,7 @@ export class AddonManager {
const dirs = fs.opendirSync(extension_folder);
const loadPromises = [];

for (;;) {
for (; ;) {
const entry = dirs.readSync();
if (!entry) {
break;
Expand All @@ -113,9 +113,17 @@ export class AddonManager {
if (fs.existsSync(packageJsonFile)) {
// Log the extension name.
console.log(`_load_all_addons Loading extension ${entry.name}`);
loadPromises.push(
import(`${extension_folder}/${entry.name}/build/index.js`),

// On Windows, ESM dynamic import() requires file:// URLs, not raw paths.
// pathToFileURL() converts a path like "C:\foo\bar" to "file:///C:/foo/bar".
// Ref: https://nodejs.org/api/esm.html#urls
const modulePath = path.join(
extension_folder,
entry.name,
"build",
"index.js",
);
loadPromises.push(import(pathToFileURL(modulePath).href));
}
}

Expand Down Expand Up @@ -148,7 +156,11 @@ export class AddonManager {
}

try {
await import(`${extension_folder}/build/index.js`);
// On Windows, ESM dynamic import() requires file:// URLs, not raw paths.
// pathToFileURL() converts a path like "C:\foo\bar" to "file:///C:/foo/bar".
// Ref: https://nodejs.org/api/esm.html#urls
const modulePath = path.join(extension_folder, "build", "index.js");
await import(pathToFileURL(modulePath).href);
console.log(`Addon ${name} loaded`);
return true;
} catch (error) {
Expand Down
13 changes: 11 additions & 2 deletions core/src/ten_runtime/binding/nodejs/interface/ten_addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@ try {
// They can instead be loaded with `module.createRequire()` or
// `process.dlopen`.
const require = createRequire(import.meta.url);
addon = require("libten_runtime_nodejs");

// Try different module names based on platform:
// - Linux/macOS: libten_runtime_nodejs (with lib prefix)
// - Windows MSVC: ten_runtime_nodejs (without lib prefix)
try {
addon = require("libten_runtime_nodejs");
} catch {
// Fallback for Windows MSVC builds
addon = require("ten_runtime_nodejs");
}
} catch (e) {
console.error(`Failed to load libten_runtime_nodejs module: ${e}`);
console.error(`Failed to load ten_runtime_nodejs module: ${e}`);
}

export default addon as unknown as typeof import("libten_runtime_nodejs");
142 changes: 116 additions & 26 deletions core/src/ten_runtime/binding/nodejs/native/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,125 @@ config("ten_runtime_nodejs_config") {
}
}

ten_shared_library("ten_runtime_nodejs") {
configs = [ ":ten_runtime_nodejs_config" ]
# When under the msvc_force toolchain, build with a different target name.
# All deps are pinned back to the default clang toolchain so that only
# this target's own sources (init.c, win_delay_load_hook.cc) are compiled
# with cl.exe. The .obj files from clang-cl and cl.exe are ABI-compatible
# (both use MSVC ABI) so mixed linking is safe.
if (current_toolchain == "//.gnfiles/build/toolchain/msvc_force:msvc_force" &&
current_toolchain != default_toolchain) {
_default_tc = "//.gnfiles/build/platform/win:clang"

output_extension = "node"
shared_library("ten_runtime_nodejs_msvc") {
output_name = "ten_runtime_nodejs"
output_extension = "node"

if (is_mac || is_linux) {
add_configs = [ "//.gnfiles/build/toolchain/common:allow_undefined" ]
remove_configs = [ "//.gnfiles/build/toolchain/common:disallow_undefined" ]
}
configs += [ ":ten_runtime_nodejs_config(${_default_tc})" ]

include_dirs = ten_runtime_common_includes

defines = ten_runtime_common_defines

cflags = common_cflags
cflags_c = common_cflags_c
cflags_cc = common_cflags_cc

ldflags = [
"/DELAYLOAD:libnode.dll",
"/ignore:4199",
]
ldflags += common_ldflags

# Add rpath to find ten_runtime library.
if (is_mac) {
ldflags = [ "-Wl,-rpath,@loader_path/../../ten_runtime/lib" ]
} else if (is_linux || is_mingw) {
# MinGW uses GNU ld and supports rpath
ldflags = [ "-Wl,-rpath=\$ORIGIN/../../ten_runtime/lib" ]
libs = [ "delayimp.lib" ]
libs += common_libs

lib_dirs = common_lib_dirs

sources = [
"init.c",
"win_delay_load_hook.cc",
]

deps = [
"addon(${_default_tc})",
"app(${_default_tc})",
"common(${_default_tc})",
"error(${_default_tc})",
"extension(${_default_tc})",
"msg(${_default_tc})",
"ten_env(${_default_tc})",
"test(${_default_tc})",
"//core/src/ten_runtime:ten_runtime_library(${_default_tc})",
"//third_party/node(${_default_tc})",
]
}
} else if (is_win && !is_mingw && is_clang) {
# Default (clang) toolchain on Windows: delegate to msvc_force toolchain.
group("ten_runtime_nodejs") {
_msvc_toolchain = "//.gnfiles/build/toolchain/msvc_force:msvc_force"
public_deps = [ ":ten_runtime_nodejs_msvc(${_msvc_toolchain})" ]
}
} else {
# Non-Windows or non-clang: build normally.
ten_shared_library("ten_runtime_nodejs") {
configs = [ ":ten_runtime_nodejs_config" ]

sources = [ "init.c" ]

deps = [
"addon",
"app",
"common",
"error",
"extension",
"msg",
"ten_env",
"test",
"//core/src/ten_runtime:ten_runtime_library",
]
output_extension = "node"

if (is_mac || is_linux) {
add_configs = [ "//.gnfiles/build/toolchain/common:allow_undefined" ]
remove_configs = [ "//.gnfiles/build/toolchain/common:disallow_undefined" ]
}

# Add rpath to find ten_runtime library.
if (is_mac) {
ldflags = [ "-Wl,-rpath,@loader_path/../../ten_runtime/lib" ]
} else if (is_linux || is_mingw) {
# MinGW uses GNU ld and supports rpath
ldflags = [ "-Wl,-rpath=\$ORIGIN/../../ten_runtime/lib" ]
} else if (is_win && !is_mingw) {
# MSVC/clang-cl: Use /DELAYLOAD to avoid loading libnode.dll at startup.
# N-API symbols are provided by the host node.exe at runtime, so we don't
# actually need libnode.dll. But MSVC requires linking to resolve symbols.
# With delay load, the DLL won't be loaded unless we call functions from it,
# and since node.exe provides these symbols, libnode.dll is never loaded.
#
# The win_delay_load_hook.cc file implements a delay-load hook that:
# 1. For pure Node.js apps: returns handle to node.exe (which exports N-API)
# 2. For embedded scenarios: returns handle to libnode.dll if loaded
ldflags = [
"/DELAYLOAD:libnode.dll",

# Suppress linker warning LNK4199: /DELAYLOAD:libnode.dll ignored;
# no imports found from libnode.dll. This warning occurs because the
# delay-load hook redirects all imports before they reach libnode.dll.
"/ignore:4199",
]

# The delay import library provided by Microsoft. Required for delay-load
# support.
libs = [ "delayimp.lib" ]
}

sources = [ "init.c" ]

if (is_win && !is_mingw) {
# Include the delay-load hook for Windows MSVC/clang-cl builds.
# This hook intercepts the delay-load of libnode.dll and redirects it
# to either the already-loaded libnode.dll or the host node.exe.
sources += [ "win_delay_load_hook.cc" ]
}

deps = [
"addon",
"app",
"common",
"error",
"extension",
"msg",
"ten_env",
"test",
"//core/src/ten_runtime:ten_runtime_library",
]
}
}
Loading
Loading