Skip to content

Commit 7bf891d

Browse files
committed
fix: improve JCO hermetic wrapper script to avoid system Node.js dependency
- Call jco main script directly through hermetic Node.js instead of relying on npm-generated .bin scripts - Set NODE_PATH and working directory for proper module resolution - Verify jco package installation before creating wrapper - Fixes CI error: /usr/bin/env: 'node': No such file or directory
1 parent a438ef1 commit 7bf891d

File tree

1 file changed

+136
-194
lines changed

1 file changed

+136
-194
lines changed

toolchains/jco_toolchain.bzl

Lines changed: 136 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
load("//toolchains:diagnostics.bzl", "format_diagnostic_error", "validate_system_tool")
44
load("//toolchains:tool_cache.bzl", "cache_tool", "retrieve_cached_tool", "validate_tool_functionality")
5+
load("//checksums:registry.bzl", "get_tool_info")
56

67
def _get_nodejs_toolchain_info(repository_ctx):
78
"""Get Node.js toolchain info from the registered hermetic toolchain"""
@@ -22,30 +23,6 @@ def _get_nodejs_toolchain_info(repository_ctx):
2223

2324
return None
2425

25-
# jco platform mapping
26-
JCO_PLATFORMS = {
27-
"darwin_amd64": {
28-
"binary_name": "jco-x86_64-apple-darwin",
29-
"sha256": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
30-
},
31-
"darwin_arm64": {
32-
"binary_name": "jco-aarch64-apple-darwin",
33-
"sha256": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4",
34-
},
35-
"linux_amd64": {
36-
"binary_name": "jco-x86_64-unknown-linux-musl",
37-
"sha256": "e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6",
38-
},
39-
"linux_arm64": {
40-
"binary_name": "jco-aarch64-unknown-linux-musl",
41-
"sha256": "a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8",
42-
},
43-
"windows_amd64": {
44-
"binary_name": "jco-x86_64-pc-windows-gnu.exe",
45-
"sha256": "c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0",
46-
},
47-
}
48-
4926
def _jco_toolchain_impl(ctx):
5027
"""Implementation of jco_toolchain rule"""
5128

@@ -102,187 +79,153 @@ def _detect_host_platform(repository_ctx):
10279
def _jco_toolchain_repository_impl(repository_ctx):
10380
"""Create jco toolchain repository"""
10481

105-
strategy = repository_ctx.attr.strategy
10682
platform = _detect_host_platform(repository_ctx)
107-
version = repository_ctx.attr.version
83+
jco_version = repository_ctx.attr.version
84+
node_version = repository_ctx.attr.node_version
10885

109-
if strategy == "download":
110-
_setup_downloaded_jco_tools(repository_ctx, platform, version)
111-
elif strategy == "npm":
112-
_setup_npm_jco_tools(repository_ctx)
113-
else:
114-
fail(format_diagnostic_error(
115-
"E001",
116-
"Unknown jco strategy: {}".format(strategy),
117-
"Must be 'download' or 'npm'",
118-
))
86+
# Always use download strategy with hermetic Node.js + jco
87+
_setup_downloaded_jco_tools(repository_ctx, platform, jco_version, node_version)
11988

12089
# Create BUILD files
12190
_create_jco_build_files(repository_ctx)
12291

123-
def _setup_downloaded_jco_tools(repository_ctx, platform, version):
124-
"""Download prebuilt jco tools"""
125-
126-
# Try to retrieve from cache first
127-
cached_jco = retrieve_cached_tool(repository_ctx, "jco", version, platform, "download")
128-
if not cached_jco:
129-
# Download jco binary
130-
if platform not in JCO_PLATFORMS:
131-
fail(format_diagnostic_error(
132-
"E001",
133-
"Unsupported platform {} for jco".format(platform),
134-
"Use 'npm' or 'system' strategy instead",
135-
))
136-
137-
platform_info = JCO_PLATFORMS[platform]
138-
binary_name = platform_info["binary_name"]
139-
140-
# jco releases are available from GitHub
141-
jco_url = "https://github.com/bytecodealliance/jco/releases/download/v{}/{}".format(
142-
version,
143-
binary_name,
144-
)
145-
146-
result = repository_ctx.download(
147-
url = jco_url,
148-
output = "jco",
149-
sha256 = platform_info["sha256"],
150-
executable = True,
151-
)
152-
if not result or (hasattr(result, "return_code") and result.return_code != 0):
153-
fail(format_diagnostic_error(
154-
"E003",
155-
"Failed to download jco",
156-
"Try 'npm' strategy: npm install -g @bytecodealliance/jco",
157-
))
158-
159-
# Validate downloaded tool
160-
validation_result = validate_tool_functionality(repository_ctx, "jco", "jco")
161-
if not validation_result["valid"]:
162-
fail(format_diagnostic_error(
163-
"E007",
164-
"Downloaded jco failed validation: {}".format(validation_result["error"]),
165-
"Try npm strategy or check platform compatibility",
166-
))
167-
168-
# Cache the tool
169-
tool_binary = repository_ctx.path("jco")
170-
cache_tool(repository_ctx, "jco", tool_binary, version, platform, "download", platform_info["sha256"])
171-
172-
# Set up Node.js and npm (assume system installation)
173-
_setup_node_tools_system(repository_ctx)
174-
175-
def _setup_npm_jco_tools(repository_ctx):
176-
"""Set up jco via hermetic npm installation"""
177-
178-
# Use the Node.js toolchain from rules_nodejs
179-
# The hermetic npm should be available through the nodejs_toolchains
180-
181-
# Try to find the hermetic npm binary
182-
# In MODULE.bazel mode, we need to reference the registered toolchain
183-
node_info = _get_nodejs_toolchain_info(repository_ctx)
92+
def _setup_downloaded_jco_tools(repository_ctx, platform, jco_version, node_version):
93+
"""Download hermetic Node.js and install jco via npm"""
94+
95+
# Get Node.js info from registry
96+
node_info = get_tool_info("nodejs", node_version, platform)
18497
if not node_info:
185-
# In repository rules, the Node.js toolchain may not be available during execution
186-
# This is a known limitation of rules_nodejs + MODULE.bazel + repository rules
187-
# Create a placeholder that indicates the issue
188-
print("Warning: Node.js toolchain not available during repository rule execution")
189-
print("This is expected in some BCR testing environments")
190-
print("jco functionality will be limited but basic WebAssembly builds will work")
191-
192-
# Create a placeholder jco binary that explains the situation
193-
repository_ctx.file("jco", """#!/bin/bash
194-
echo "jco not available - Node.js toolchain not accessible during repository rule execution"
195-
echo "This is a known limitation with rules_nodejs + MODULE.bazel + repository rules"
196-
echo "JavaScript/TypeScript WebAssembly component builds are not available"
197-
echo "Use download strategy or system jco as an alternative"
198-
exit 1
199-
""", executable = True)
200-
return
201-
202-
npm_binary = node_info.npm
203-
node_binary = node_info.node
204-
print("Using hermetic npm from Node.js toolchain: {}".format(npm_binary))
205-
206-
# Install jco and componentize-js globally via npm
207-
result = repository_ctx.execute([
208-
npm_binary,
209-
"install",
210-
"-g",
211-
"@bytecodealliance/jco@{}".format(repository_ctx.attr.version),
212-
"@bytecodealliance/componentize-js",
213-
])
214-
215-
if result.return_code != 0:
21698
fail(format_diagnostic_error(
217-
"E003",
218-
"Failed to install jco via npm: {}".format(result.stderr),
219-
"Check npm configuration and network connectivity",
99+
"E001",
100+
"Unsupported platform {} for Node.js {}".format(platform, node_version),
101+
"Check //checksums/tools/nodejs.json for supported platforms",
220102
))
221-
222-
# Find the installed jco binary path
223-
jco_result = repository_ctx.execute(["which", "jco"])
224-
if jco_result.return_code != 0:
103+
104+
print("Setting up hermetic Node.js {} + jco {} for platform {}".format(
105+
node_version, jco_version, platform))
106+
107+
# Download Node.js
108+
archive_name = "node-v{}-{}".format(node_version, node_info["url_suffix"])
109+
node_url = "https://nodejs.org/dist/v{}/{}".format(node_version, archive_name)
110+
111+
print("Downloading Node.js from: {}".format(node_url))
112+
113+
# Download and extract Node.js with SHA256 verification
114+
if archive_name.endswith(".tar.xz"):
115+
# For .tar.xz files (Linux)
116+
result = repository_ctx.download_and_extract(
117+
url = node_url,
118+
sha256 = node_info["sha256"],
119+
type = "tar.xz",
120+
)
121+
elif archive_name.endswith(".tar.gz"):
122+
# For .tar.gz files (macOS)
123+
result = repository_ctx.download_and_extract(
124+
url = node_url,
125+
sha256 = node_info["sha256"],
126+
type = "tar.gz",
127+
)
128+
elif archive_name.endswith(".zip"):
129+
# For .zip files (Windows)
130+
result = repository_ctx.download_and_extract(
131+
url = node_url,
132+
sha256 = node_info["sha256"],
133+
type = "zip",
134+
)
135+
else:
136+
fail("Unsupported Node.js archive format: {}".format(archive_name))
137+
138+
if not result or (hasattr(result, "return_code") and result.return_code != 0):
225139
fail(format_diagnostic_error(
226140
"E003",
227-
"jco binary not found after installation",
228-
"Check npm global installation path",
141+
"Failed to download Node.js {}".format(node_version),
142+
"Check network connectivity and Node.js version availability",
229143
))
230-
231-
jco_path = jco_result.stdout.strip()
232-
233-
# Set up Node.js and npm first to get paths
234-
_setup_node_tools_system(repository_ctx)
235-
236-
# Get Node.js path for JCO wrapper
237-
node_validation = validate_system_tool(repository_ctx, "node")
238-
node_path = node_validation.get("path", "node")
239-
240-
# Create symlink to jco binary (Bazel-native approach)
241-
if repository_ctx.path(jco_path).exists:
242-
repository_ctx.symlink(jco_path, "jco")
243-
else:
244-
fail("jco binary not found after installation at {}".format(jco_path))
245-
246-
print("Installed jco via npm globally")
247-
248-
def _setup_node_tools_system(repository_ctx):
249-
"""Set up system Node.js and npm tools"""
250-
251-
# Validate Node.js
252-
node_validation = validate_system_tool(repository_ctx, "node")
253-
if not node_validation["valid"]:
144+
145+
# Get paths to Node.js binaries
146+
node_binary_path = node_info["binary_path"].format(node_version)
147+
npm_binary_path = node_info["npm_path"].format(node_version)
148+
149+
# Verify Node.js installation
150+
node_binary = repository_ctx.path(node_binary_path)
151+
npm_binary = repository_ctx.path(npm_binary_path)
152+
153+
if not node_binary.exists:
154+
fail("Node.js binary not found at: {}".format(node_binary_path))
155+
156+
if not npm_binary.exists:
157+
fail("npm binary not found at: {}".format(npm_binary_path))
158+
159+
# Test Node.js installation
160+
node_test = repository_ctx.execute([node_binary, "--version"])
161+
if node_test.return_code != 0:
162+
fail("Node.js installation test failed: {}".format(node_test.stderr))
163+
164+
print("Successfully installed hermetic Node.js: {}".format(node_test.stdout.strip()))
165+
166+
# Install jco using the hermetic npm
167+
print("Installing jco {} using hermetic npm...".format(jco_version))
168+
169+
# Create a local node_modules for jco
170+
npm_install_result = repository_ctx.execute([
171+
npm_binary,
172+
"install",
173+
"--prefix", "jco_workspace",
174+
"@bytecodealliance/jco@{}".format(jco_version),
175+
"@bytecodealliance/componentize-js", # Required dependency
176+
])
177+
178+
if npm_install_result.return_code != 0:
254179
fail(format_diagnostic_error(
255-
"E006",
256-
"Node.js not found",
257-
"Install Node.js to use JavaScript component features",
180+
"E003",
181+
"Failed to install jco via hermetic npm: {}".format(npm_install_result.stderr),
182+
"Check jco version availability and network connectivity",
258183
))
259-
260-
# Validate npm
261-
npm_validation = validate_system_tool(repository_ctx, "npm")
262-
if not npm_validation["valid"]:
184+
185+
print("Successfully installed jco via hermetic npm")
186+
187+
# Create robust wrapper script for jco that always uses hermetic Node.js
188+
# Use npx with the jco package to ensure proper module resolution
189+
workspace_path = repository_ctx.path("jco_workspace").realpath
190+
191+
# Verify jco was installed
192+
jco_package_path = repository_ctx.path("jco_workspace/node_modules/@bytecodealliance/jco/package.json")
193+
if not jco_package_path.exists:
263194
fail(format_diagnostic_error(
264-
"E006",
265-
"npm not found",
266-
"Install npm (usually comes with Node.js)",
195+
"E004",
196+
"jco installation failed - package.json not found",
197+
"Check npm install output above for errors",
267198
))
268-
269-
# Get absolute paths to tools
270-
node_path = node_validation.get("path", "node")
271-
npm_path = npm_validation.get("path", "npm")
272-
273-
# Create symlink to Node.js binary (Bazel-native approach)
274-
if node_path and repository_ctx.path(node_path).exists:
275-
repository_ctx.symlink(node_path, "node")
276-
else:
277-
# Fallback: use PATH resolution
278-
repository_ctx.file("node", "node", executable = True)
279-
280-
# Create symlink to npm binary (Bazel-native approach)
281-
if npm_path and repository_ctx.path(npm_path).exists:
282-
repository_ctx.symlink(npm_path, "npm")
199+
200+
print("jco installation verified, creating hermetic wrapper...")
201+
202+
if platform.startswith("windows"):
203+
wrapper_content = """@echo off
204+
cd /d "{workspace}"
205+
set NODE_PATH={workspace}/node_modules
206+
"{node}" "{workspace}/node_modules/@bytecodealliance/jco/src/jco.js" %*
207+
""".format(
208+
node = node_binary.realpath,
209+
workspace = workspace_path
210+
)
211+
repository_ctx.file("jco.cmd", wrapper_content, executable = True)
212+
repository_ctx.symlink("jco.cmd", "jco")
283213
else:
284-
# Fallback: use PATH resolution
285-
repository_ctx.file("npm", "npm", executable = True)
214+
wrapper_content = """#!/bin/bash
215+
cd "{workspace}"
216+
export NODE_PATH="{workspace}/node_modules"
217+
exec "{node}" "{workspace}/node_modules/@bytecodealliance/jco/src/jco.js" "$@"
218+
""".format(
219+
node = node_binary.realpath,
220+
workspace = workspace_path
221+
)
222+
repository_ctx.file("jco", wrapper_content, executable = True)
223+
224+
# Create symlinks for Node.js and npm binaries for the toolchain
225+
repository_ctx.symlink(node_binary, "node")
226+
repository_ctx.symlink(npm_binary, "npm")
227+
228+
print("Hermetic jco toolchain setup complete")
286229

287230
def _create_jco_build_files(repository_ctx):
288231
"""Create BUILD files for jco toolchain"""
@@ -339,14 +282,13 @@ alias(
339282
jco_toolchain_repository = repository_rule(
340283
implementation = _jco_toolchain_repository_impl,
341284
attrs = {
342-
"strategy": attr.string(
343-
doc = "Tool acquisition strategy: 'download' or 'npm'",
344-
default = "npm",
345-
values = ["download", "npm"],
346-
),
347285
"version": attr.string(
348286
doc = "jco version to use",
349287
default = "1.4.0",
350288
),
289+
"node_version": attr.string(
290+
doc = "Node.js version to use for download strategy",
291+
default = "18.19.0",
292+
),
351293
},
352-
)
294+
)

0 commit comments

Comments
 (0)