22
33load ("//toolchains:diagnostics.bzl" , "format_diagnostic_error" , "validate_system_tool" )
44load ("//toolchains:tool_cache.bzl" , "cache_tool" , "retrieve_cached_tool" , "validate_tool_functionality" )
5+ load ("//checksums:registry.bzl" , "get_tool_info" )
56
67def _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-
4926def _jco_toolchain_impl (ctx ):
5027 """Implementation of jco_toolchain rule"""
5128
@@ -102,187 +79,153 @@ def _detect_host_platform(repository_ctx):
10279def _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
287230def _create_jco_build_files (repository_ctx ):
288231 """Create BUILD files for jco toolchain"""
@@ -339,14 +282,13 @@ alias(
339282jco_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