From 45de1d6a22e3f91a611c276f15a4bbf8933789c0 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 11 Dec 2025 20:40:40 +0100 Subject: [PATCH 1/2] feat: implement toolchain version bundles for guaranteed compatibility Adds version bundle system for pre-validated, tested tool version combinations: - checksums/toolchain_bundles.json: Bundle definitions with 3 initial bundles: - stable-2025-12: Full toolchain with all 10 tools for all languages - minimal: wasm-tools, wit-bindgen, wasmtime for Rust-only builds - composition: Adds wac, wkg for component composition workflows - toolchains/bundle.bzl: Bundle API for reading/validating bundles: - get_bundle(), get_bundle_tool_version() - validate_bundle(), resolve_tool_versions() - print_bundle_info() for diagnostics - toolchains/extensions.bzl: Module extension tag for bundle configuration: - wasm_tool_repositories.configure(bundle = "stable-2025-12") Each bundle includes: - Tested tool versions with compatibility guarantees - Component model version (0x1000d) and WASI version (preview2) - Tested platform list - Version constraints documenting tool interdependencies Closes #254 --- checksums/toolchain_bundles.json | 113 +++++++++++++ toolchains/bundle.bzl | 215 +++++++++++++++++++++++++ toolchains/extensions.bzl | 22 ++- toolchains/tool_versions.bzl | 11 ++ toolchains/wasm_tools_repositories.bzl | 17 +- 5 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 checksums/toolchain_bundles.json create mode 100644 toolchains/bundle.bzl diff --git a/checksums/toolchain_bundles.json b/checksums/toolchain_bundles.json new file mode 100644 index 00000000..9b95e670 --- /dev/null +++ b/checksums/toolchain_bundles.json @@ -0,0 +1,113 @@ +{ + "$schema": "toolchain_bundles.schema.json", + "description": "Validated toolchain version bundles for rules_wasm_component", + "bundles": { + "stable-2025-12": { + "description": "December 2025 stable release - tested and validated", + "status": "stable", + "release_date": "2025-12-11", + "tools": { + "wasm-tools": "1.243.0", + "wit-bindgen": "0.49.0", + "wac": "0.8.1", + "wkg": "0.13.0", + "wasmtime": "39.0.1", + "wizer": "10.0.0", + "wasi-sdk": "29", + "tinygo": "0.38.0", + "jco": "1.4.0", + "nodejs": "20.18.0" + }, + "compatibility": { + "component_model_version": "0x1000d", + "wasi_version": "preview2", + "wit_bindgen_abi": "0.49" + }, + "tested_platforms": [ + "darwin_arm64", + "darwin_amd64", + "linux_amd64", + "linux_arm64", + "windows_amd64" + ], + "notes": "Initial stable bundle with WASI Preview 2 support" + }, + "minimal": { + "description": "Minimal bundle for basic WASM component builds", + "status": "stable", + "release_date": "2025-12-11", + "tools": { + "wasm-tools": "1.243.0", + "wit-bindgen": "0.49.0", + "wasmtime": "39.0.1" + }, + "compatibility": { + "component_model_version": "0x1000d", + "wasi_version": "preview2", + "wit_bindgen_abi": "0.49" + }, + "tested_platforms": [ + "darwin_arm64", + "darwin_amd64", + "linux_amd64", + "linux_arm64", + "windows_amd64" + ], + "notes": "Minimal tools for Rust-only component builds without composition" + }, + "composition": { + "description": "Bundle for component composition workflows", + "status": "stable", + "release_date": "2025-12-11", + "tools": { + "wasm-tools": "1.243.0", + "wit-bindgen": "0.49.0", + "wac": "0.8.1", + "wkg": "0.13.0", + "wasmtime": "39.0.1" + }, + "compatibility": { + "component_model_version": "0x1000d", + "wasi_version": "preview2", + "wit_bindgen_abi": "0.49" + }, + "tested_platforms": [ + "darwin_arm64", + "darwin_amd64", + "linux_amd64", + "linux_arm64", + "windows_amd64" + ], + "notes": "Includes WAC and WKG for component composition and registry operations" + } + }, + "default_bundle": "stable-2025-12", + "version_constraints": { + "description": "Known version compatibility constraints between tools", + "constraints": [ + { + "tools": ["wasm-tools", "wit-bindgen"], + "rule": "wit-bindgen output must be compatible with wasm-tools component model version", + "validated_pairs": [ + {"wasm-tools": "1.243.0", "wit-bindgen": "0.49.0"}, + {"wasm-tools": "1.240.0", "wit-bindgen": "0.40.0"} + ] + }, + { + "tools": ["wasmtime", "wasm-tools"], + "rule": "wasmtime must support component model version produced by wasm-tools", + "validated_pairs": [ + {"wasmtime": "39.0.1", "wasm-tools": "1.243.0"}, + {"wasmtime": "29.0.0", "wasm-tools": "1.240.0"} + ] + }, + { + "tools": ["wizer", "wasmtime"], + "rule": "wizer and wasmtime should use compatible cranelift versions", + "validated_pairs": [ + {"wizer": "10.0.0", "wasmtime": "39.0.1"} + ] + } + ] + } +} diff --git a/toolchains/bundle.bzl b/toolchains/bundle.bzl new file mode 100644 index 00000000..685c461b --- /dev/null +++ b/toolchains/bundle.bzl @@ -0,0 +1,215 @@ +"""Toolchain bundle management for rules_wasm_component. + +This module provides functions to load and validate toolchain version bundles, +ensuring compatible tool versions are used together. +""" + +# Bundle status values +BUNDLE_STATUS_STABLE = "stable" +BUNDLE_STATUS_BETA = "beta" +BUNDLE_STATUS_DEPRECATED = "deprecated" + +def _read_bundles_json(repository_ctx): + """Read the toolchain_bundles.json file. + + Args: + repository_ctx: Repository context for file operations. + + Returns: + Parsed JSON dict or None if file doesn't exist. + """ + bundles_path = repository_ctx.path(Label("//checksums:toolchain_bundles.json")) + if not bundles_path.exists: + return None + content = repository_ctx.read(bundles_path) + return json.decode(content) + +def get_bundle(repository_ctx, bundle_name = None): + """Get a toolchain bundle by name. + + Args: + repository_ctx: Repository context for file operations. + bundle_name: Name of the bundle to get. If None, uses default bundle. + + Returns: + Dict containing bundle configuration, or None if not found. + """ + bundles_data = _read_bundles_json(repository_ctx) + if not bundles_data: + return None + + if not bundle_name: + bundle_name = bundles_data.get("default_bundle", "stable-2025-12") + + bundles = bundles_data.get("bundles", {}) + return bundles.get(bundle_name) + +def get_bundle_tool_version(repository_ctx, bundle_name, tool_name): + """Get a specific tool version from a bundle. + + Args: + repository_ctx: Repository context for file operations. + bundle_name: Name of the bundle. + tool_name: Name of the tool (e.g., "wasm-tools", "wit-bindgen"). + + Returns: + Version string or None if not found. + """ + bundle = get_bundle(repository_ctx, bundle_name) + if not bundle: + return None + + tools = bundle.get("tools", {}) + return tools.get(tool_name) + +def get_all_bundle_names(repository_ctx): + """Get list of all available bundle names. + + Args: + repository_ctx: Repository context for file operations. + + Returns: + List of bundle name strings. + """ + bundles_data = _read_bundles_json(repository_ctx) + if not bundles_data: + return [] + + return list(bundles_data.get("bundles", {}).keys()) + +def get_default_bundle_name(repository_ctx): + """Get the default bundle name. + + Args: + repository_ctx: Repository context for file operations. + + Returns: + Default bundle name string. + """ + bundles_data = _read_bundles_json(repository_ctx) + if not bundles_data: + return "stable-2025-12" + + return bundles_data.get("default_bundle", "stable-2025-12") + +def validate_bundle(repository_ctx, bundle_name): + """Validate that a bundle exists and has required tools. + + Args: + repository_ctx: Repository context for file operations. + bundle_name: Name of the bundle to validate. + + Returns: + Tuple of (is_valid, error_message). + """ + bundle = get_bundle(repository_ctx, bundle_name) + if not bundle: + available = get_all_bundle_names(repository_ctx) + return (False, "Bundle '{}' not found. Available bundles: {}".format( + bundle_name, ", ".join(available) if available else "none")) + + # Check required fields + required_fields = ["tools", "compatibility"] + for field in required_fields: + if field not in bundle: + return (False, "Bundle '{}' is missing required field: {}".format( + bundle_name, field)) + + # Check tools is non-empty + if not bundle.get("tools"): + return (False, "Bundle '{}' has no tools defined".format(bundle_name)) + + # Check status is not deprecated (warn only) + status = bundle.get("status", BUNDLE_STATUS_STABLE) + if status == BUNDLE_STATUS_DEPRECATED: + # We still allow deprecated bundles but could log a warning + pass + + return (True, None) + +def resolve_tool_versions(repository_ctx, bundle_name = None, overrides = None): + """Resolve tool versions from a bundle with optional overrides. + + This is the main entry point for toolchains to get their versions. + + Args: + repository_ctx: Repository context for file operations. + bundle_name: Name of the bundle to use. If None, uses default. + overrides: Dict of tool_name -> version to override bundle versions. + + Returns: + Dict of tool_name -> version for all tools in the bundle. + """ + bundle = get_bundle(repository_ctx, bundle_name) + if not bundle: + # Fall back to empty dict if no bundle found + return overrides or {} + + # Start with bundle tools + versions = dict(bundle.get("tools", {})) + + # Apply overrides + if overrides: + for tool, version in overrides.items(): + versions[tool] = version + + return versions + +def print_bundle_info(repository_ctx, bundle_name = None): + """Print information about a bundle (for debugging/diagnostics). + + Args: + repository_ctx: Repository context for file operations. + bundle_name: Name of the bundle. If None, uses default. + + Returns: + Formatted string with bundle information. + """ + bundle = get_bundle(repository_ctx, bundle_name) + if not bundle: + return "Bundle not found: {}".format(bundle_name or "default") + + lines = [] + lines.append("Bundle: {}".format(bundle_name or get_default_bundle_name(repository_ctx))) + lines.append("Description: {}".format(bundle.get("description", "N/A"))) + lines.append("Status: {}".format(bundle.get("status", "unknown"))) + lines.append("Release Date: {}".format(bundle.get("release_date", "N/A"))) + lines.append("") + lines.append("Tools:") + for tool, version in sorted(bundle.get("tools", {}).items()): + lines.append(" {}: {}".format(tool, version)) + + compat = bundle.get("compatibility", {}) + if compat: + lines.append("") + lines.append("Compatibility:") + lines.append(" Component Model: {}".format(compat.get("component_model_version", "N/A"))) + lines.append(" WASI Version: {}".format(compat.get("wasi_version", "N/A"))) + + platforms = bundle.get("tested_platforms", []) + if platforms: + lines.append("") + lines.append("Tested Platforms: {}".format(", ".join(platforms))) + + notes = bundle.get("notes") + if notes: + lines.append("") + lines.append("Notes: {}".format(notes)) + + return "\n".join(lines) + +# Public API +bundle_api = struct( + get_bundle = get_bundle, + get_bundle_tool_version = get_bundle_tool_version, + get_all_bundle_names = get_all_bundle_names, + get_default_bundle_name = get_default_bundle_name, + validate_bundle = validate_bundle, + resolve_tool_versions = resolve_tool_versions, + print_bundle_info = print_bundle_info, + + # Constants + STATUS_STABLE = BUNDLE_STATUS_STABLE, + STATUS_BETA = BUNDLE_STATUS_BETA, + STATUS_DEPRECATED = BUNDLE_STATUS_DEPRECATED, +) diff --git a/toolchains/extensions.bzl b/toolchains/extensions.bzl index 5f8ab162..78f5c3f4 100644 --- a/toolchains/extensions.bzl +++ b/toolchains/extensions.bzl @@ -5,11 +5,31 @@ load("//toolchains:symmetric_wit_bindgen_toolchain.bzl", "symmetric_wit_bindgen_ def _wasm_tool_repositories_impl(module_ctx): """Implementation of wasm_tool_repositories extension""" - register_wasm_tool_repositories() + # Check for bundle configuration from tags + bundle_name = None + for mod in module_ctx.modules: + for tag in mod.tags.configure: + if tag.bundle: + bundle_name = tag.bundle + + # Pass bundle to repository registration (for future use) + register_wasm_tool_repositories(bundle = bundle_name) + +_configure_tag = tag_class( + attrs = { + "bundle": attr.string( + doc = "Name of the toolchain bundle to use (e.g., 'stable-2025-12', 'minimal', 'composition'). See checksums/toolchain_bundles.json for available bundles.", + default = "", + ), + }, +) wasm_tool_repositories = module_extension( implementation = _wasm_tool_repositories_impl, doc = "Extension for registering modernized WASM tool repositories", + tag_classes = { + "configure": _configure_tag, + }, ) def _symmetric_wit_bindgen_impl(module_ctx): diff --git a/toolchains/tool_versions.bzl b/toolchains/tool_versions.bzl index 8ba260ff..3c952722 100644 --- a/toolchains/tool_versions.bzl +++ b/toolchains/tool_versions.bzl @@ -3,12 +3,23 @@ This file defines the canonical versions for all WebAssembly toolchain components. All toolchain setup code MUST reference these constants to ensure version consistency. +TOOLCHAIN BUNDLES: +For validated, tested combinations of tool versions, see checksums/toolchain_bundles.json. +Bundles provide: +- Pre-tested tool version combinations +- Guaranteed compatibility +- Easy atomic upgrades + +To use a bundle: + wasm_component_toolchains(bundle = "stable-2025-12") + IMPORTANT: When updating versions here: 1. Update corresponding JSON registry in checksums/tools/.json 2. Verify compatibility using validate_tool_compatibility() in checksums/registry.bzl 3. Check embedded runtimes (rust_wasm_component_bindgen.bzl) for API compatibility 4. Update Cargo.toml dependencies if using the tool as a crate 5. Test the full build pipeline +6. Update checksums/toolchain_bundles.json with new validated combinations """ # Tool versions - single source of truth diff --git a/toolchains/wasm_tools_repositories.bzl b/toolchains/wasm_tools_repositories.bzl index c29ee9a7..b017644f 100644 --- a/toolchains/wasm_tools_repositories.bzl +++ b/toolchains/wasm_tools_repositories.bzl @@ -3,17 +3,32 @@ This file replaces shell-based git clone operations with proper Bazel git_repository rules. For cargo builds, we use rules_rust to create proper Bazel targets that integrate with Bazel's caching and dependency management. + +BUNDLE SUPPORT: +Pass a bundle name to use pre-validated version combinations from +checksums/toolchain_bundles.json. Available bundles: +- stable-2025-12: Full toolchain with all languages +- minimal: Just wasm-tools, wit-bindgen, wasmtime +- composition: Tools for component composition workflows """ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") -def register_wasm_tool_repositories(): +def register_wasm_tool_repositories(bundle = None): """Register git repositories for all WASM tools This replaces ctx.execute(["git", "clone", ...]) operations with proper Bazel repository rules that integrate with Bazel's caching and hermeticity. Uses rules_rust for dependency management instead of shell cargo commands. + + Args: + bundle: Optional name of a toolchain bundle from checksums/toolchain_bundles.json. + If provided, uses versions from the bundle. If None, uses default versions. """ + # Note: Bundle version resolution is currently informational. + # Future work will wire bundle versions into the git_repository tags. + # For now, versions are hardcoded below (matching stable-2025-12 bundle). + _ = bundle # Mark as used to avoid unused parameter warning # wasm-tools: WebAssembly manipulation tools git_repository( From dcc329997eebcfa6acad2f56aad09e19267a6c3b Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Fri, 12 Dec 2025 06:52:20 +0100 Subject: [PATCH 2/2] feat: wire bundle versions to all toolchain repository rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit completes the Option B (Full Integration) architecture for toolchain version bundles: Changes: - Add get_version_for_tool() and log_bundle_usage() helpers to bundle.bzl - Add 'bundle' attribute to all 8 toolchain repository rules: - wasm_toolchain_repository (wasm-tools, wac, wit-bindgen) - wasmtime_repository - wizer_toolchain_repository - wkg_toolchain_repository - tinygo_toolchain_repository - wasi_sdk_repository - cpp_component_toolchain_repository - jco_toolchain_repository - Add unified wasm_component_bundle extension to wasm/extensions.bzl that configures all toolchains from a single bundle parameter Architecture: MODULE.bazel │ wasm_component_bundle.configure(bundle = "stable-2025-12") ▼ Module Extension (wasm/extensions.bzl) │ Pass bundle to each toolchain repo rule ▼ Repository Rules (each toolchain) │ Read checksums/toolchain_bundles.json via Label path │ Extract version for this tool from bundle ▼ Downloaded Tools (with compatible versions) Each repository rule: 1. Accepts optional 'bundle' attribute 2. If bundle set, reads version from toolchain_bundles.json 3. Falls back to explicit version attribute if not in bundle 4. Logs bundle usage for debugging This ensures all tools are downloaded with compatible, pre-validated versions when using a bundle, eliminating version mismatch issues. Part of #254: Toolchain Version Bundles for Guaranteed Compatibility --- toolchains/bundle.bzl | 54 +++++++++++ toolchains/cpp_component_toolchain.bzl | 21 ++++- toolchains/jco_toolchain.bzl | 32 ++++++- toolchains/tinygo_toolchain.bzl | 25 +++++- toolchains/wasi_sdk_toolchain.bzl | 22 ++++- toolchains/wasm_toolchain.bzl | 49 +++++++++- toolchains/wasmtime_toolchain.bzl | 21 ++++- toolchains/wizer_toolchain.bzl | 21 ++++- toolchains/wkg_toolchain.bzl | 22 ++++- wasm/extensions.bzl | 119 +++++++++++++++++++++++++ 10 files changed, 366 insertions(+), 20 deletions(-) diff --git a/toolchains/bundle.bzl b/toolchains/bundle.bzl index 685c461b..dcfd1f3d 100644 --- a/toolchains/bundle.bzl +++ b/toolchains/bundle.bzl @@ -198,6 +198,58 @@ def print_bundle_info(repository_ctx, bundle_name = None): return "\n".join(lines) +def get_version_for_tool(repository_ctx, tool_name, bundle_name = None, fallback_version = None): + """Get version for a specific tool from bundle, with fallback support. + + This is the main entry point for toolchain repository rules to get their version. + + Args: + repository_ctx: Repository context for file operations. + tool_name: Name of the tool (e.g., "wasm-tools", "wasmtime"). + bundle_name: Name of the bundle to use. If empty/None, uses default bundle. + fallback_version: Version to use if tool not found in bundle. + + Returns: + Version string for the tool. + """ + # If no bundle specified, try to use default + effective_bundle = bundle_name if bundle_name else None + + version = get_bundle_tool_version(repository_ctx, effective_bundle, tool_name) + + if version: + return version + + # Tool not in bundle - use fallback or fail + if fallback_version: + return fallback_version + + # No fallback - check if bundle exists but doesn't have this tool + bundle = get_bundle(repository_ctx, effective_bundle) + if bundle: + available_tools = list(bundle.get("tools", {}).keys()) + fail("Tool '{}' not found in bundle '{}'. Available tools: {}".format( + tool_name, + effective_bundle or get_default_bundle_name(repository_ctx), + ", ".join(available_tools) if available_tools else "none", + )) + + # No bundle found at all - fail with helpful message + fail("Could not determine version for tool '{}'. No bundle found and no fallback specified.".format(tool_name)) + +def log_bundle_usage(repository_ctx, tool_name, version, bundle_name = None): + """Log which version is being used from which bundle (for debugging). + + Args: + repository_ctx: Repository context for file operations. + tool_name: Name of the tool. + version: Version being used. + bundle_name: Bundle name (or None for default). + """ + effective_bundle = bundle_name or get_default_bundle_name(repository_ctx) + # buildifier: disable=print + print("Bundle '{}': using {} version {}".format(effective_bundle, tool_name, version)) + # Public API bundle_api = struct( get_bundle = get_bundle, @@ -207,6 +259,8 @@ bundle_api = struct( validate_bundle = validate_bundle, resolve_tool_versions = resolve_tool_versions, print_bundle_info = print_bundle_info, + get_version_for_tool = get_version_for_tool, + log_bundle_usage = log_bundle_usage, # Constants STATUS_STABLE = BUNDLE_STATUS_STABLE, diff --git a/toolchains/cpp_component_toolchain.bzl b/toolchains/cpp_component_toolchain.bzl index 5df48f4e..e4e7a1a9 100644 --- a/toolchains/cpp_component_toolchain.bzl +++ b/toolchains/cpp_component_toolchain.bzl @@ -1,5 +1,6 @@ """C/C++ WebAssembly component toolchain definitions for Preview2""" +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") load("//toolchains:diagnostics.bzl", "format_diagnostic_error", "validate_system_tool") def _cpp_component_toolchain_impl(ctx): @@ -98,7 +99,19 @@ def _cpp_component_toolchain_repository_impl(repository_ctx): strategy = repository_ctx.attr.strategy platform = _detect_host_platform(repository_ctx) - wasi_sdk_version = repository_ctx.attr.wasi_sdk_version + bundle_name = repository_ctx.attr.bundle + + # Resolve version from bundle if specified, otherwise use explicit version + if bundle_name: + wasi_sdk_version = get_version_for_tool( + repository_ctx, + "wasi-sdk", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.wasi_sdk_version, + ) + log_bundle_usage(repository_ctx, "wasi-sdk", wasi_sdk_version, bundle_name) + else: + wasi_sdk_version = repository_ctx.attr.wasi_sdk_version if strategy == "download": _setup_downloaded_cpp_tools(repository_ctx, platform, wasi_sdk_version) @@ -345,13 +358,17 @@ alias( cpp_component_toolchain_repository = repository_rule( implementation = _cpp_component_toolchain_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name. If set, wasi_sdk_version is read from checksums/toolchain_bundles.json", + default = "", + ), "strategy": attr.string( doc = "Tool acquisition strategy: 'download' or 'build'", default = "download", values = ["download", "build"], ), "wasi_sdk_version": attr.string( - doc = "WASI SDK version to use", + doc = "WASI SDK version to use. Ignored if bundle is specified.", default = "27", ), }, diff --git a/toolchains/jco_toolchain.bzl b/toolchains/jco_toolchain.bzl index f84145ce..7bba9e52 100644 --- a/toolchains/jco_toolchain.bzl +++ b/toolchains/jco_toolchain.bzl @@ -1,5 +1,6 @@ """jco (JavaScript Component Tools) toolchain definitions""" +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") load("//toolchains:diagnostics.bzl", "format_diagnostic_error", "validate_system_tool") load("//toolchains:tool_cache.bzl", "cache_tool", "retrieve_cached_tool", "validate_tool_functionality") load("//checksums:registry.bzl", "get_tool_info") @@ -86,8 +87,27 @@ def _jco_toolchain_repository_impl(repository_ctx): """Create jco toolchain repository""" platform = _detect_host_platform(repository_ctx) - jco_version = repository_ctx.attr.version - node_version = repository_ctx.attr.node_version + bundle_name = repository_ctx.attr.bundle + + # Resolve versions from bundle if specified, otherwise use explicit versions + if bundle_name: + jco_version = get_version_for_tool( + repository_ctx, + "jco", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.version, + ) + node_version = get_version_for_tool( + repository_ctx, + "nodejs", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.node_version, + ) + log_bundle_usage(repository_ctx, "jco", jco_version, bundle_name) + log_bundle_usage(repository_ctx, "nodejs", node_version, bundle_name) + else: + jco_version = repository_ctx.attr.version + node_version = repository_ctx.attr.node_version # Always use download strategy with hermetic Node.js + jco _setup_downloaded_jco_tools(repository_ctx, platform, jco_version, node_version) @@ -360,12 +380,16 @@ alias( jco_toolchain_repository = repository_rule( implementation = _jco_toolchain_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name. If set, versions are read from checksums/toolchain_bundles.json", + default = "", + ), "version": attr.string( - doc = "jco version to use", + doc = "jco version to use. Ignored if bundle is specified.", default = "1.4.0", ), "node_version": attr.string( - doc = "Node.js version to use for download strategy", + doc = "Node.js version to use for download strategy. Ignored if bundle is specified.", default = "18.19.0", ), }, diff --git a/toolchains/tinygo_toolchain.bzl b/toolchains/tinygo_toolchain.bzl index daed2e3f..040b5dbd 100644 --- a/toolchains/tinygo_toolchain.bzl +++ b/toolchains/tinygo_toolchain.bzl @@ -10,6 +10,11 @@ Architecture: - wasm-tools for component transformation """ +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") + +_TINYGO_TOOLCHAIN_DOC = """ +""" + def _detect_host_platform(repository_ctx): """Detect the host platform for tool downloads""" os_name = repository_ctx.os.name.lower() @@ -267,7 +272,19 @@ def _tinygo_toolchain_repository_impl(repository_ctx): """Implementation of TinyGo toolchain repository rule""" platform = _detect_host_platform(repository_ctx) - tinygo_version = repository_ctx.attr.tinygo_version + bundle_name = repository_ctx.attr.bundle + + # Resolve version from bundle if specified, otherwise use explicit version + if bundle_name: + tinygo_version = get_version_for_tool( + repository_ctx, + "tinygo", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.tinygo_version, + ) + log_bundle_usage(repository_ctx, "tinygo", tinygo_version, bundle_name) + else: + tinygo_version = repository_ctx.attr.tinygo_version print("Setting up TinyGo toolchain v{} for {}".format(tinygo_version, platform)) @@ -387,8 +404,12 @@ toolchain( tinygo_toolchain_repository = repository_rule( implementation = _tinygo_toolchain_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name. If set, version is read from checksums/toolchain_bundles.json", + default = "", + ), "tinygo_version": attr.string( - doc = "TinyGo version to download and use", + doc = "TinyGo version to download and use. Ignored if bundle is specified.", default = "0.39.0", ), }, diff --git a/toolchains/wasi_sdk_toolchain.bzl b/toolchains/wasi_sdk_toolchain.bzl index f2c4ff83..6bc73988 100644 --- a/toolchains/wasi_sdk_toolchain.bzl +++ b/toolchains/wasi_sdk_toolchain.bzl @@ -1,6 +1,7 @@ """WASI SDK toolchain definitions""" load("//checksums:registry.bzl", "get_tool_info") +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") def _get_wasi_sdk_platform_info(platform, version): """Get platform info and checksum for WASI SDK from centralized registry""" @@ -121,7 +122,20 @@ def _wasi_sdk_repository_impl(repository_ctx): def _setup_downloaded_wasi_sdk(repository_ctx): """Download WASI SDK from GitHub releases""" - version = repository_ctx.attr.version + bundle_name = repository_ctx.attr.bundle + + # Resolve version from bundle if specified, otherwise use explicit version + if bundle_name: + version = get_version_for_tool( + repository_ctx, + "wasi-sdk", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.version, + ) + log_bundle_usage(repository_ctx, "wasi-sdk", version, bundle_name) + else: + version = repository_ctx.attr.version + platform = _detect_host_platform(repository_ctx) # Download WASI SDK @@ -423,13 +437,17 @@ wasm_cc_toolchain_config = rule( wasi_sdk_repository = repository_rule( implementation = _wasi_sdk_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name. If set, version is read from checksums/toolchain_bundles.json", + default = "", + ), "strategy": attr.string( doc = "Strategy: 'download'", default = "download", values = ["download"], ), "version": attr.string( - doc = "WASI SDK version", + doc = "WASI SDK version. Ignored if bundle is specified.", default = "27", ), "url": attr.string( diff --git a/toolchains/wasm_toolchain.bzl b/toolchains/wasm_toolchain.bzl index 81f0f20b..487400e4 100644 --- a/toolchains/wasm_toolchain.bzl +++ b/toolchains/wasm_toolchain.bzl @@ -1,6 +1,7 @@ """WebAssembly toolchain definitions with enhanced tool management""" load("//checksums:registry.bzl", "get_tool_info", "validate_tool_compatibility") +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") load("//toolchains:diagnostics.bzl", "create_retry_wrapper", "format_diagnostic_error", "log_diagnostic_info", "validate_system_tool") load("//toolchains:monitoring.bzl", "add_build_telemetry", "create_health_check") load("//toolchains:tool_cache.bzl", "cache_tool", "clean_expired_cache", "retrieve_cached_tool", "validate_tool_functionality") @@ -125,7 +126,19 @@ def _wasm_toolchain_repository_impl(repository_ctx): strategy = repository_ctx.attr.strategy platform = _detect_host_platform(repository_ctx) - version = repository_ctx.attr.version + bundle_name = repository_ctx.attr.bundle + + # Resolve version from bundle or use explicit version attribute + if bundle_name: + version = get_version_for_tool( + repository_ctx, + "wasm-tools", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.version, + ) + log_bundle_usage(repository_ctx, "wasm-tools", version, bundle_name) + else: + version = repository_ctx.attr.version # Log diagnostic information log_diagnostic_info(repository_ctx, "wasm-tools", platform, version, strategy) @@ -498,7 +511,19 @@ def _download_wasm_tools(repository_ctx): def _download_wac(repository_ctx): """Download wac only""" platform = _detect_host_platform(repository_ctx) - wac_version = get_tool_version("wac") # From tool_versions.bzl + bundle_name = repository_ctx.attr.bundle + + # Get version from bundle if specified, otherwise from tool_versions.bzl + if bundle_name: + wac_version = get_version_for_tool( + repository_ctx, + "wac", + bundle_name = bundle_name, + fallback_version = get_tool_version("wac"), + ) + log_bundle_usage(repository_ctx, "wac", wac_version, bundle_name) + else: + wac_version = get_tool_version("wac") # From tool_versions.bzl # Get checksum and platform info from tool_versions.bzl tool_info = get_tool_info("wac", wac_version, platform) @@ -520,7 +545,19 @@ def _download_wac(repository_ctx): def _download_wit_bindgen(repository_ctx): """Download wit-bindgen only""" platform = _detect_host_platform(repository_ctx) - wit_bindgen_version = get_tool_version("wit-bindgen") # From tool_versions.bzl + bundle_name = repository_ctx.attr.bundle + + # Get version from bundle if specified, otherwise from tool_versions.bzl + if bundle_name: + wit_bindgen_version = get_version_for_tool( + repository_ctx, + "wit-bindgen", + bundle_name = bundle_name, + fallback_version = get_tool_version("wit-bindgen"), + ) + log_bundle_usage(repository_ctx, "wit-bindgen", wit_bindgen_version, bundle_name) + else: + wit_bindgen_version = get_tool_version("wit-bindgen") # From tool_versions.bzl # Get checksum and platform info from tool_versions.bzl tool_info = get_tool_info("wit-bindgen", wit_bindgen_version, platform) @@ -724,13 +761,17 @@ toolchain( wasm_toolchain_repository = repository_rule( implementation = _wasm_toolchain_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name from checksums/toolchain_bundles.json (e.g., 'stable-2025-12', 'minimal'). If set, version is read from bundle.", + default = "", + ), "strategy": attr.string( doc = "Tool acquisition strategy: 'download' only (other strategies removed in dependency management cleanup)", default = "download", values = ["download"], ), "version": attr.string( - doc = "Version to use (for download/build strategies)", + doc = "Version to use (for download/build strategies). Ignored if bundle is specified.", default = "1.235.0", ), "git_commit": attr.string( diff --git a/toolchains/wasmtime_toolchain.bzl b/toolchains/wasmtime_toolchain.bzl index c2231b60..24878805 100644 --- a/toolchains/wasmtime_toolchain.bzl +++ b/toolchains/wasmtime_toolchain.bzl @@ -1,6 +1,7 @@ """Wasmtime toolchain definitions for WebAssembly component runtime""" load("//checksums:registry.bzl", "get_tool_info") +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") def _wasmtime_toolchain_impl(ctx): """Implementation of wasmtime_toolchain rule""" @@ -51,7 +52,19 @@ def _wasmtime_repository_impl(repository_ctx): strategy = repository_ctx.attr.strategy platform = _detect_host_platform(repository_ctx) - version = repository_ctx.attr.version + bundle_name = repository_ctx.attr.bundle + + # Resolve version from bundle if specified, otherwise use explicit version + if bundle_name: + version = get_version_for_tool( + repository_ctx, + "wasmtime", + bundle_name = bundle_name, + fallback_version = repository_ctx.attr.version, + ) + log_bundle_usage(repository_ctx, "wasmtime", version, bundle_name) + else: + version = repository_ctx.attr.version print("Setting up wasmtime {} for platform {} using strategy {}".format( version, @@ -149,12 +162,16 @@ def _setup_downloaded_wasmtime(repository_ctx, platform, version): wasmtime_repository = repository_rule( implementation = _wasmtime_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name. If set, version is read from checksums/toolchain_bundles.json", + default = "", + ), "strategy": attr.string( doc = "Installation strategy: 'download'", default = "download", ), "version": attr.string( - doc = "Wasmtime version to install", + doc = "Wasmtime version to install. Ignored if bundle is specified.", default = "35.0.0", # Latest version from our registry ), }, diff --git a/toolchains/wizer_toolchain.bzl b/toolchains/wizer_toolchain.bzl index 6a2d50d3..6d1768e9 100644 --- a/toolchains/wizer_toolchain.bzl +++ b/toolchains/wizer_toolchain.bzl @@ -1,6 +1,7 @@ """Wizer WebAssembly pre-initialization toolchain definitions""" load("//checksums:registry.bzl", "get_tool_info") +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") WIZER_VERSIONS = { "10.0.0": { @@ -85,9 +86,21 @@ def _get_wizer_download_info(platform, version): def _wizer_toolchain_repository_impl(ctx): """Implementation of wizer_toolchain_repository repository rule""" - version = ctx.attr.version strategy = ctx.attr.strategy platform = _detect_host_platform(ctx) + bundle_name = ctx.attr.bundle + + # Resolve version from bundle if specified, otherwise use explicit version + if bundle_name: + version = get_version_for_tool( + ctx, + "wizer", + bundle_name = bundle_name, + fallback_version = ctx.attr.version, + ) + log_bundle_usage(ctx, "wizer", version, bundle_name) + else: + version = ctx.attr.version if version not in WIZER_VERSIONS: fail("Unsupported Wizer version: {}. Supported versions: {}".format( @@ -287,9 +300,13 @@ toolchain( wizer_toolchain_repository = repository_rule( implementation = _wizer_toolchain_repository_impl, attrs = { + "bundle": attr.string( + default = "", + doc = "Toolchain bundle name. If set, version is read from checksums/toolchain_bundles.json", + ), "version": attr.string( default = "9.0.0", - doc = "Wizer version to install", + doc = "Wizer version to install. Ignored if bundle is specified.", ), "strategy": attr.string( default = "source", diff --git a/toolchains/wkg_toolchain.bzl b/toolchains/wkg_toolchain.bzl index 23d63f17..c6706c0c 100644 --- a/toolchains/wkg_toolchain.bzl +++ b/toolchains/wkg_toolchain.bzl @@ -1,5 +1,7 @@ """WebAssembly Package Tools (wkg) toolchain definitions""" +load("//toolchains:bundle.bzl", "get_version_for_tool", "log_bundle_usage") + # Platform-specific wkg binary information WKG_PLATFORMS = { "darwin_amd64": struct( @@ -66,7 +68,19 @@ def _wkg_toolchain_repository_impl(ctx): """Repository rule implementation for wkg toolchain""" strategy = ctx.attr.strategy - version = ctx.attr.version + bundle_name = ctx.attr.bundle + + # Resolve version from bundle if specified, otherwise use explicit version + if bundle_name: + version = get_version_for_tool( + ctx, + "wkg", + bundle_name = bundle_name, + fallback_version = ctx.attr.version, + ) + log_bundle_usage(ctx, "wkg", version, bundle_name) + else: + version = ctx.attr.version if strategy == "download": platform = _detect_platform(ctx) @@ -200,13 +214,17 @@ toolchain( wkg_toolchain_repository = repository_rule( implementation = _wkg_toolchain_repository_impl, attrs = { + "bundle": attr.string( + doc = "Toolchain bundle name. If set, version is read from checksums/toolchain_bundles.json", + default = "", + ), "strategy": attr.string( doc = "Tool acquisition strategy: 'download', 'build', or 'source'", default = "download", values = ["download", "build", "source"], ), "version": attr.string( - doc = "Version to download/build", + doc = "Version to download/build. Ignored if bundle is specified.", default = "0.11.0", ), "url": attr.string( diff --git a/wasm/extensions.bzl b/wasm/extensions.bzl index 1623929f..cd11788b 100644 --- a/wasm/extensions.bzl +++ b/wasm/extensions.bzl @@ -10,6 +10,125 @@ load("//toolchains:wizer_toolchain.bzl", "wizer_toolchain_repository") load("//toolchains:wkg_toolchain.bzl", "wkg_toolchain_repository") load("//wit:wasi_deps.bzl", "wasi_wit_dependencies") +# ============================================================================= +# UNIFIED BUNDLE CONFIGURATION +# ============================================================================= +# Use wasm_component_bundle.configure() to set up all toolchains from a +# pre-validated version bundle. This is the recommended approach. +# +# Example usage in MODULE.bazel: +# wasm_bundle = use_extension("@rules_wasm_component//wasm:extensions.bzl", "wasm_component_bundle") +# wasm_bundle.configure(bundle = "stable-2025-12") +# use_repo(wasm_bundle, "wasm_tools_toolchains", "wasmtime_toolchain", ...) +# ============================================================================= + +def _wasm_component_bundle_impl(module_ctx): + """Implementation of unified bundle configuration extension. + + This sets up all toolchains using versions from a pre-validated bundle, + ensuring compatibility between tools. + """ + bundle_name = "" + + # Get bundle configuration from tags + for mod in module_ctx.modules: + for tag in mod.tags.configure: + if tag.bundle: + bundle_name = tag.bundle + + # Create all toolchain repositories with bundle parameter + # The bundle parameter tells each repository to read versions from + # checksums/toolchain_bundles.json instead of using hardcoded defaults + + # Core WASM tools (wasm-tools, wac, wit-bindgen) + wasm_toolchain_repository( + name = "wasm_tools_toolchains", + bundle = bundle_name, + strategy = "download", + ) + + # Wasmtime runtime + wasmtime_repository( + name = "wasmtime_toolchain", + bundle = bundle_name, + strategy = "download", + ) + + # Wizer pre-initialization + wizer_toolchain_repository( + name = "wizer_toolchain", + bundle = bundle_name, + strategy = "download", + ) + + # WKG package manager + wkg_toolchain_repository( + name = "wkg_toolchain", + bundle = bundle_name, + strategy = "download", + ) + + # TinyGo for Go components + tinygo_toolchain_repository( + name = "tinygo_toolchain", + bundle = bundle_name, + ) + + # WASI SDK for C/C++ components + wasi_sdk_repository( + name = "wasi_sdk", + bundle = bundle_name, + strategy = "download", + ) + + # C/C++ component toolchain + cpp_component_toolchain_repository( + name = "cpp_component_toolchain", + bundle = bundle_name, + strategy = "download", + ) + + # JCO for JavaScript components + jco_toolchain_repository( + name = "jco_toolchain", + bundle = bundle_name, + ) + + # WASI WIT definitions + wasi_wit_dependencies() + +# Unified bundle configuration extension +wasm_component_bundle = module_extension( + implementation = _wasm_component_bundle_impl, + tag_classes = { + "configure": tag_class( + attrs = { + "bundle": attr.string( + doc = """Toolchain bundle name from checksums/toolchain_bundles.json. + +Available bundles: +- stable-2025-12: Full toolchain with all languages (default) +- minimal: Just wasm-tools, wit-bindgen, wasmtime for Rust-only builds +- composition: Tools for component composition workflows (adds wac, wkg) + +Using a bundle ensures all tool versions are compatible with each other.""", + default = "stable-2025-12", + ), + }, + ), + }, + doc = """Unified WebAssembly toolchain configuration using version bundles. + +This extension sets up all WASM toolchains using pre-validated version +combinations from checksums/toolchain_bundles.json. + +Example: + wasm_bundle = use_extension("@rules_wasm_component//wasm:extensions.bzl", "wasm_component_bundle") + wasm_bundle.configure(bundle = "stable-2025-12") + use_repo(wasm_bundle, "wasm_tools_toolchains", "wasmtime_toolchain", "wizer_toolchain", ...) +""", +) + def _wasm_toolchain_extension_impl(module_ctx): """Implementation of wasm_toolchain module extension"""