Skip to content

Commit efd71e9

Browse files
authored
Nightly Node.js and Big Endian cross compilation Node.js (#1590)
This PR adds support for `./emsdk install node-nightly-64bit` which finds the latest nightly node.js version and installs that. It is a moving target, so the next day when a new nightly is produced, reissuing `./emsdk install node-nightly-64bit` will install the newer published version. Also, this PR adds a fixed 22.18.0 version for the cross compilation s390x Node.js target as well. On an x64 Linux system, this allows running `./emsdk install node-big-endian-crosscompile-22.16.0-64bit` to install the big endian Node.js. This greatly simplifies the steps at emscripten-core/emscripten@main...juj:emscripten:bigendian_test_suite#diff-c36b90121be240017fa490a1c00e63e47fa3235f5c1be0593e2b7502d017c778R9985-R10000 and enables a trivial way to switch between LE and BE Node.js versions for testing. CC @slavek-kucera
1 parent 0c45297 commit efd71e9

File tree

2 files changed

+114
-9
lines changed

2 files changed

+114
-9
lines changed

emsdk.py

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,77 @@ def find_emscripten_root(active_tools):
14771477
return root
14781478

14791479

1480+
def fetch_nightly_node_versions():
1481+
url = "https://nodejs.org/download/nightly/"
1482+
with urlopen(url) as response:
1483+
html = response.read().decode("utf-8")
1484+
1485+
# Regex to capture href values like v7.0.0-nightly2016080175c6d9dd95/
1486+
pattern = re.compile(r'<a href="(v[0-9]+\.[0-9]+\.[0-9]+-nightly[0-9a-f]+)/">')
1487+
matches = pattern.findall(html)
1488+
return matches
1489+
1490+
1491+
def dir_installed_nightly_node_versions():
1492+
path = os.path.abspath('node')
1493+
return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name)) and name.startswith("nightly-")]
1494+
1495+
1496+
def extract_newest_node_nightly_version(versions):
1497+
def parse(v):
1498+
# example: v7.0.0-nightly2016080175c6d9dd95
1499+
m = re.match(r'v(\d+)\.(\d+)\.(\d+)-nightly(\d+)', v)
1500+
if m:
1501+
major, minor, patch, nightly = m.groups()
1502+
return [int(major), int(minor), int(patch), int(nightly)]
1503+
else:
1504+
return []
1505+
1506+
try:
1507+
return max(versions, key=lambda v: parse(v))
1508+
except Exception:
1509+
return None
1510+
1511+
1512+
def download_node_nightly(tool):
1513+
nightly_versions = fetch_nightly_node_versions()
1514+
latest_nightly = extract_newest_node_nightly_version(nightly_versions)
1515+
print('Latest Node.js Nightly download available is "' + latest_nightly + '"')
1516+
1517+
output_dir = os.path.abspath('node/nightly-' + latest_nightly)
1518+
# Node.js zip structure quirk: Linux and macOS archives have a /bin,
1519+
# Windows does not. Unify the file structures.
1520+
if WINDOWS:
1521+
output_dir += '/bin'
1522+
1523+
if os.path.isdir(output_dir):
1524+
return True
1525+
1526+
url = tool.url.replace('%version%', latest_nightly)
1527+
if WINDOWS:
1528+
os_ = 'win'
1529+
elif LINUX:
1530+
os_ = 'linux'
1531+
elif MACOS:
1532+
os_ = 'darwin'
1533+
else:
1534+
os_ = ''
1535+
if platform.machine().lower() in ["x86_64", "amd64"]:
1536+
arch = 'x64'
1537+
elif platform.machine().lower() in ["arm64", "aarch64"]:
1538+
arch = 'arm64'
1539+
if WINDOWS:
1540+
zip_suffix = 'zip'
1541+
else:
1542+
zip_suffix = 'tar.gz'
1543+
url = url.replace('%os%', os_)
1544+
url = url.replace('%arch%', arch)
1545+
url = url.replace('%zip_suffix%', zip_suffix)
1546+
download_and_extract(url, output_dir)
1547+
open(tool.get_version_file_path(), 'w').write('node-nightly-64bit')
1548+
return True
1549+
1550+
14801551
# returns a tuple (string,string) of config files paths that need to used
14811552
# to activate emsdk env depending on $SHELL, defaults to bash.
14821553
def get_emsdk_shell_env_configs():
@@ -1511,7 +1582,10 @@ def generate_em_config(active_tools, permanently_activate, system):
15111582
activated_config['NODE_JS'] = node_fallback
15121583

15131584
for name, value in activated_config.items():
1514-
cfg += name + " = '" + value + "'\n"
1585+
if value.startswith('['):
1586+
cfg += name + " = " + value + "\n"
1587+
else:
1588+
cfg += name + " = '" + value + "'\n"
15151589

15161590
emroot = find_emscripten_root(active_tools)
15171591
if emroot:
@@ -1606,6 +1680,11 @@ def expand_vars(self, str):
16061680
str = str.replace('%.exe%', '.exe' if WINDOWS else '')
16071681
if '%llvm_build_bin_dir%' in str:
16081682
str = str.replace('%llvm_build_bin_dir%', llvm_build_bin_dir(self))
1683+
if '%latest_downloaded_node_nightly_dir%' in str:
1684+
installed_node_nightlys = dir_installed_nightly_node_versions()
1685+
latest_node_nightly = extract_newest_node_nightly_version(installed_node_nightlys)
1686+
if latest_node_nightly:
1687+
str = str.replace('%latest_downloaded_node_nightly_dir%', latest_node_nightly)
16091688

16101689
return str
16111690

@@ -1727,10 +1806,11 @@ def is_installed(self, skip_version_check=False):
17271806
return False
17281807

17291808
if self.download_url() is None:
1730-
# This tool does not contain downloadable elements, so it is installed by default.
1809+
debug_print(str(self) + ' has no files to download, so is installed by default.')
17311810
return True
17321811

17331812
content_exists = is_nonempty_directory(self.installation_path())
1813+
debug_print(str(self) + ' installation path is ' + self.installation_path() + ', exists: ' + str(content_exists) + '.')
17341814

17351815
# For e.g. fastcomp clang from git repo, the activated PATH is the
17361816
# directory where the compiler is built to, and installation_path is
@@ -1876,12 +1956,14 @@ def install_tool(self):
18761956
print("Installing tool '" + str(self) + "'..")
18771957
url = self.download_url()
18781958

1879-
if hasattr(self, 'custom_install_script') and self.custom_install_script == 'build_llvm':
1880-
success = build_llvm(self)
1881-
elif hasattr(self, 'custom_install_script') and self.custom_install_script == 'build_ninja':
1882-
success = build_ninja(self)
1883-
elif hasattr(self, 'custom_install_script') and self.custom_install_script == 'build_ccache':
1884-
success = build_ccache(self)
1959+
custom_install_scripts = {
1960+
'build_llvm': build_llvm,
1961+
'build_ninja': build_ninja,
1962+
'build_ccache': build_ccache,
1963+
'download_node_nightly': download_node_nightly
1964+
}
1965+
if hasattr(self, 'custom_install_script') and self.custom_install_script in custom_install_scripts:
1966+
success = custom_install_scripts[self.custom_install_script](self)
18851967
elif hasattr(self, 'git_branch'):
18861968
success = git_clone_checkout_and_pull(url, self.installation_path(), self.git_branch, getattr(self, 'remote_name', 'origin'))
18871969
elif url.endswith(ARCHIVE_SUFFIXES):
@@ -1896,7 +1978,7 @@ def install_tool(self):
18961978
if hasattr(self, 'custom_install_script'):
18971979
if self.custom_install_script == 'emscripten_npm_install':
18981980
success = emscripten_npm_install(self, self.installation_path())
1899-
elif self.custom_install_script in ('build_llvm', 'build_ninja', 'build_ccache'):
1981+
elif self.custom_install_script in ('build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly'):
19001982
# 'build_llvm' is a special one that does the download on its
19011983
# own, others do the download manually.
19021984
pass

emsdk_manifest.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,29 @@
204204
"activated_cfg": "NODE_JS='%installation_dir%/bin/node%.exe%'",
205205
"activated_env": "EMSDK_NODE=%installation_dir%/bin/node%.exe%"
206206
},
207+
{
208+
"id": "node-big-endian-crosscompile",
209+
"version": "22.16.0",
210+
"arch": "x86_64",
211+
"bitness": 64,
212+
"linux_url": "https://nodejs.org/download/release/v22.16.0/node-v22.16.0-linux-s390x.tar.gz",
213+
"activated_path": "%installation_dir%/bin",
214+
"activated_path_skip": "node",
215+
"activated_cfg": "NODE_JS_TEST=['qemu-s390x', '-L', '/usr/s390x-linux-gnu/', '%installation_dir%/bin/node']"
216+
},
217+
{
218+
"id": "node",
219+
"version": "nightly",
220+
"bitness": 64,
221+
"install_path": "node/%latest_downloaded_node_nightly_dir%",
222+
"custom_install_script": "download_node_nightly",
223+
"activated_path": "node/%latest_downloaded_node_nightly_dir%/bin",
224+
"activated_path_skip": "node",
225+
"activated_cfg": "NODE_JS='%installation_dir%/bin/node%.exe%'",
226+
"activated_env": "EMSDK_NODE=%installation_dir%/bin/node%.exe%",
227+
"url": "https://nodejs.org/download/nightly/%version%/node-%version%-%os%-%arch%.%zip_suffix%",
228+
"git_branch": "nightly"
229+
},
207230

208231

209232
{

0 commit comments

Comments
 (0)