Skip to content

Commit 2d806f8

Browse files
fs-eireSanket Kale
authored andcommitted
[build] optimize search for nodejs in CMake (microsoft#25466)
### Description optimize search for nodejs in CMake. ### Motivation and Context The default behavior of CMake's `find_program()` is to search `/bin/` folder before `$PATH`. This may cause a very old Node.js to be used.
1 parent 031775c commit 2d806f8

File tree

6 files changed

+125
-55
lines changed

6 files changed

+125
-55
lines changed

.github/workflows/linux-wasm-ci-build-and-test-workflow.yml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,6 @@ jobs:
5050
with:
5151
node-version: "22"
5252

53-
# The image used in this workflow has an old version of Node.js installed at /bin/node and npm at /bin/npm.
54-
# We need to remove the system Node.js and npm, otherwise CMake will not be able to find the correct Node.js and npm executables.
55-
- name: Remove the system Node.js
56-
run: |
57-
if [ -f /bin/node ]; then
58-
rm /bin/node
59-
fi
60-
if [ -f /bin/npm ]; then
61-
rm /bin/npm
62-
fi
63-
6453
- name: Set up Python
6554
uses: actions/setup-python@v5
6655
with:

cmake/node_helper.cmake

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
cmake_minimum_required(VERSION 3.25)
5+
6+
# Function to get NPM path from Node.js path
7+
function(get_npm_path_from_node result_var node_path)
8+
get_filename_component(NODE_DIR ${node_path} DIRECTORY)
9+
if(WIN32)
10+
set(NPM_CLI_CANDIDATE "${NODE_DIR}/npm.cmd")
11+
if(NOT EXISTS ${NPM_CLI_CANDIDATE})
12+
set(NPM_CLI_CANDIDATE "${NODE_DIR}/npm")
13+
endif()
14+
else()
15+
set(NPM_CLI_CANDIDATE "${NODE_DIR}/npm")
16+
endif()
17+
18+
set(${result_var} ${NPM_CLI_CANDIDATE} PARENT_SCOPE)
19+
endfunction()
20+
21+
# Validator function for Node.js installation (checks both Node.js and NPM versions)
22+
function(validate_nodejs_installation result_var node_path)
23+
# First validate Node.js version
24+
execute_process(
25+
COMMAND ${node_path} --version
26+
OUTPUT_VARIABLE NODE_VERSION_OUTPUT
27+
ERROR_VARIABLE NODE_VERSION_ERROR
28+
RESULT_VARIABLE NODE_VERSION_RESULT
29+
OUTPUT_STRIP_TRAILING_WHITESPACE
30+
)
31+
32+
if(NODE_VERSION_RESULT EQUAL 0)
33+
# Node version output starts with 'v', e.g., "v20.10.0"
34+
string(REGEX MATCH "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)" NODE_VERSION_MATCH ${NODE_VERSION_OUTPUT})
35+
if(NODE_VERSION_MATCH)
36+
set(NODE_VERSION_MAJOR ${CMAKE_MATCH_1})
37+
38+
if(NODE_VERSION_MAJOR LESS 20)
39+
message(STATUS "Node.js at ${node_path} version ${NODE_VERSION_OUTPUT} is too old. Required: >= v20.0.0")
40+
set(${result_var} FALSE PARENT_SCOPE)
41+
return()
42+
endif()
43+
44+
message(STATUS "Found Node.js at ${node_path} with version: ${NODE_VERSION_OUTPUT}")
45+
else()
46+
message(STATUS "Could not parse Node.js version from ${node_path}: ${NODE_VERSION_OUTPUT}")
47+
set(${result_var} FALSE PARENT_SCOPE)
48+
return()
49+
endif()
50+
else()
51+
message(STATUS "Failed to get Node.js version from ${node_path}: ${NODE_VERSION_ERROR}")
52+
set(${result_var} FALSE PARENT_SCOPE)
53+
return()
54+
endif()
55+
56+
# Now validate NPM from the same installation directory
57+
get_npm_path_from_node(NPM_CLI_CANDIDATE ${node_path})
58+
59+
if(NOT EXISTS ${NPM_CLI_CANDIDATE})
60+
get_filename_component(NODE_DIR ${node_path} DIRECTORY)
61+
message(STATUS "Could not find NPM in the same directory as Node.js: ${NODE_DIR}")
62+
set(${result_var} FALSE PARENT_SCOPE)
63+
return()
64+
endif()
65+
66+
# Validate NPM version
67+
execute_process(
68+
COMMAND ${NPM_CLI_CANDIDATE} --version
69+
OUTPUT_VARIABLE NPM_VERSION_OUTPUT
70+
ERROR_VARIABLE NPM_VERSION_ERROR
71+
RESULT_VARIABLE NPM_VERSION_RESULT
72+
OUTPUT_STRIP_TRAILING_WHITESPACE
73+
)
74+
75+
if(NPM_VERSION_RESULT EQUAL 0)
76+
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" NPM_VERSION_MATCH ${NPM_VERSION_OUTPUT})
77+
if(NPM_VERSION_MATCH)
78+
set(NPM_VERSION_MAJOR ${CMAKE_MATCH_1})
79+
80+
if(NPM_VERSION_MAJOR LESS 10)
81+
message(STATUS "NPM at ${NPM_CLI_CANDIDATE} version ${NPM_VERSION_OUTPUT} is too old. Required: >= 10.0.0")
82+
set(${result_var} FALSE PARENT_SCOPE)
83+
return()
84+
endif()
85+
86+
message(STATUS "Found NPM at ${NPM_CLI_CANDIDATE} with version: ${NPM_VERSION_OUTPUT}")
87+
set(${result_var} TRUE PARENT_SCOPE)
88+
else()
89+
message(STATUS "Could not parse NPM version from ${NPM_CLI_CANDIDATE}: ${NPM_VERSION_OUTPUT}")
90+
set(${result_var} FALSE PARENT_SCOPE)
91+
endif()
92+
else()
93+
message(STATUS "Failed to get NPM version from ${NPM_CLI_CANDIDATE}: ${NPM_VERSION_ERROR}")
94+
set(${result_var} FALSE PARENT_SCOPE)
95+
endif()
96+
endfunction()
97+
98+
# Check if both Node.js and NPM are already provided
99+
if((NOT NPM_CLI) OR (NOT NODE_EXECUTABLE))
100+
# Find node executable with combined Node.js + NPM validation
101+
find_program(NODE_EXECUTABLE
102+
NAMES "node.exe" "node"
103+
DOC "Node.js executable"
104+
VALIDATOR validate_nodejs_installation
105+
REQUIRED
106+
)
107+
108+
# Set NPM_CLI from the validated Node.js installation
109+
get_npm_path_from_node(NPM_CLI ${NODE_EXECUTABLE})
110+
set(NPM_CLI ${NPM_CLI} CACHE FILEPATH "NPM command line client" FORCE)
111+
message(STATUS "Using Node.js and NPM from the same validated installation:")
112+
message(STATUS " Node.js: ${NODE_EXECUTABLE}")
113+
message(STATUS " NPM: ${NPM_CLI}")
114+
endif()

cmake/onnxruntime_nodejs.cmake

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,8 @@ set(JS_ROOT ${REPO_ROOT}/js)
55
set(JS_COMMON_ROOT ${JS_ROOT}/common)
66
set(JS_NODE_ROOT ${JS_ROOT}/node)
77

8-
find_program(NPM_CLI
9-
NAMES "npm.cmd" "npm"
10-
DOC "NPM command line client"
11-
REQUIRED
12-
)
13-
14-
# verify Node.js and NPM
15-
execute_process(COMMAND node --version
16-
WORKING_DIRECTORY ${JS_NODE_ROOT}
17-
OUTPUT_VARIABLE node_version
18-
RESULT_VARIABLE had_error
19-
OUTPUT_STRIP_TRAILING_WHITESPACE)
20-
if(had_error)
21-
message(FATAL_ERROR "Failed to find Node.js: " ${had_error})
22-
endif()
23-
execute_process(COMMAND ${NPM_CLI} --version
24-
WORKING_DIRECTORY ${JS_NODE_ROOT}
25-
OUTPUT_VARIABLE npm_version
26-
RESULT_VARIABLE had_error
27-
OUTPUT_STRIP_TRAILING_WHITESPACE)
28-
if(had_error)
29-
message(FATAL_ERROR "Failed to find NPM: " ${had_error})
30-
endif()
8+
# Include the Node.js helper for finding and validating Node.js and NPM
9+
include(node_helper.cmake)
3110

3211
# setup ARCH
3312
if (APPLE)

cmake/onnxruntime_providers_webgpu.cmake

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,13 @@
135135
set(WGSL_TEMPLATES_DIR "${ONNXRUNTIME_ROOT}/core/providers/webgpu/wgsl_templates")
136136
set(WGSL_GENERATED_ROOT "${CMAKE_CURRENT_BINARY_DIR}/wgsl_generated")
137137

138-
# Find npm and node executables
139-
find_program(NPM_EXECUTABLE "npm.cmd" "npm" REQUIRED)
140-
if(NOT NPM_EXECUTABLE)
141-
message(FATAL_ERROR "npm is required for WGSL template generation but was not found")
142-
endif()
143-
find_program(NODE_EXECUTABLE "node" REQUIRED)
144-
if (NOT NODE_EXECUTABLE)
145-
message(FATAL_ERROR "Node is required for WGSL template generation but was not found")
146-
endif()
138+
# Include the Node.js helper for finding and validating Node.js and NPM
139+
include(node_helper.cmake)
147140

148141
# Install npm dependencies
149142
add_custom_command(
150143
OUTPUT "${WGSL_TEMPLATES_DIR}/node_modules/.install_complete"
151-
COMMAND ${NPM_EXECUTABLE} ci
144+
COMMAND ${NPM_CLI} ci
152145
COMMAND ${CMAKE_COMMAND} -E touch "${WGSL_TEMPLATES_DIR}/node_modules/.install_complete"
153146
DEPENDS "${WGSL_TEMPLATES_DIR}/package.json" "${WGSL_TEMPLATES_DIR}/package-lock.json"
154147
WORKING_DIRECTORY ${WGSL_TEMPLATES_DIR}
@@ -203,7 +196,7 @@
203196
# Generate WGSL templates
204197
add_custom_command(
205198
OUTPUT ${WGSL_GENERATED_INDEX_H} ${WGSL_GENERATED_INDEX_IMPL_H} ${WGSL_GENERATED_TEMPLATES_JS}
206-
COMMAND ${NPM_EXECUTABLE} run gen -- ${WGSL_GEN_OPTIONS}
199+
COMMAND ${NPM_CLI} run gen -- ${WGSL_GEN_OPTIONS}
207200
DEPENDS "${WGSL_TEMPLATES_DIR}/node_modules/.install_complete" ${WGSL_TEMPLATE_FILES}
208201
WORKING_DIRECTORY ${WGSL_TEMPLATES_DIR}
209202
COMMENT "Generating WGSL templates from *.wgsl.template files"

cmake/onnxruntime_unittests.cmake

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,8 @@ function(AddTest)
198198
# xctest_add_test(xctest.${_UT_TARGET} ${_UT_TARGET}_xc)
199199
else()
200200
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
201-
# We might have already executed the following "find_program" code when we build ORT nodejs binding.
202-
# Then the program is found the result is stored in the variable and the search will not be repeated.
203-
find_program(NPM_CLI
204-
NAMES "npm.cmd" "npm"
205-
DOC "NPM command line client"
206-
REQUIRED
207-
)
201+
# Include the Node.js helper for finding and validating Node.js and NPM
202+
include(node_helper.cmake)
208203

209204
if (onnxruntime_WEBASSEMBLY_RUN_TESTS_IN_BROWSER)
210205
add_custom_command(TARGET ${_UT_TARGET} POST_BUILD

tools/ci_build/github/linux/docker/scripts/manylinux/install_shared_deps.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@ function GetFile {
3939
cd /tmp/src
4040

4141
echo "Installing Node.js"
42-
# The EOL for nodejs v18.17.1 LTS is April 2025
43-
GetFile https://nodejs.org/dist/v18.17.1/node-v18.17.1-linux-x64.tar.gz /tmp/src/node-v18.17.1-linux-x64.tar.gz
44-
tar --strip 1 -xf /tmp/src/node-v18.17.1-linux-x64.tar.gz -C /usr
42+
# The EOL for nodejs v22.17.1 LTS is April 2027
43+
GetFile https://nodejs.org/dist/v22.17.1/node-v22.17.1-linux-x64.tar.gz /tmp/src/node-v22.17.1-linux-x64.tar.gz
44+
tar --strip 1 -xf /tmp/src/node-v22.17.1-linux-x64.tar.gz -C /usr

0 commit comments

Comments
 (0)