From 5e54c842a58af3629d0f52fae21b578d53b81e34 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 13 May 2026 20:12:49 +0200 Subject: [PATCH 01/13] feat: add zls toolchain support --- MODULE.bazel | 14 ++ README.md | 20 +++ util/BUILD.bazel | 10 ++ util/update_zls_versions.py | 100 +++++++++++ zig/BUILD.bazel | 1 + zig/tests/zls-completion/BUILD.bazel | 13 ++ zig/tests/zls-completion/main.zig | 4 + zig/zls/BUILD.bazel | 104 +++++++++++ zig/zls/defs.bzl | 5 + zig/zls/extensions.bzl | 5 + zig/zls/private/BUILD.bazel | 25 +++ zig/zls/private/bzlmod/BUILD.bazel | 23 +++ zig/zls/private/bzlmod/zls.bzl | 214 +++++++++++++++++++++++ zig/zls/private/repo/BUILD.bazel | 24 +++ zig/zls/private/repo/toolchains_repo.bzl | 85 +++++++++ zig/zls/private/repo/zls_repository.bzl | 78 +++++++++ zig/zls/private/resolved_toolchain.bzl | 24 +++ zig/zls/private/versions.json | 204 +++++++++++++++++++++ zig/zls/tests/BUILD.bazel | 25 +++ zig/zls/tests/bzlmod_zls_test.bzl | 141 +++++++++++++++ zig/zls/toolchain.bzl | 50 ++++++ zig/zls/workspace_printer.zig | 169 ++++++++++++++++++ zig/zls/workspace_printer_test.zig | 90 ++++++++++ zig/zls/zls_build_runner.zig | 14 ++ zig/zls/zls_completion.bzl | 105 +++++++++++ zig/zls/zls_runner.zig | 100 +++++++++++ zig/zls/zls_write_build_config.bzl | 173 ++++++++++++++++++ zig/zls/zls_write_runner_zig_src.bzl | 45 +++++ 28 files changed, 1865 insertions(+) create mode 100755 util/update_zls_versions.py create mode 100644 zig/tests/zls-completion/BUILD.bazel create mode 100644 zig/tests/zls-completion/main.zig create mode 100644 zig/zls/BUILD.bazel create mode 100644 zig/zls/defs.bzl create mode 100644 zig/zls/extensions.bzl create mode 100644 zig/zls/private/BUILD.bazel create mode 100644 zig/zls/private/bzlmod/BUILD.bazel create mode 100644 zig/zls/private/bzlmod/zls.bzl create mode 100644 zig/zls/private/repo/BUILD.bazel create mode 100644 zig/zls/private/repo/toolchains_repo.bzl create mode 100644 zig/zls/private/repo/zls_repository.bzl create mode 100644 zig/zls/private/resolved_toolchain.bzl create mode 100644 zig/zls/private/versions.json create mode 100644 zig/zls/tests/BUILD.bazel create mode 100644 zig/zls/tests/bzlmod_zls_test.bzl create mode 100644 zig/zls/toolchain.bzl create mode 100644 zig/zls/workspace_printer.zig create mode 100644 zig/zls/workspace_printer_test.zig create mode 100644 zig/zls/zls_build_runner.zig create mode 100644 zig/zls/zls_completion.bzl create mode 100644 zig/zls/zls_runner.zig create mode 100644 zig/zls/zls_write_build_config.bzl create mode 100644 zig/zls/zls_write_runner_zig_src.bzl diff --git a/MODULE.bazel b/MODULE.bazel index 9634bb6a..e360a100 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -43,6 +43,20 @@ zig_dev = use_extension( zig_dev.toolchain(zig_version = "0.16.0") zig_dev.toolchain(zig_version = "0.15.2") +zls_dev = use_extension( + "//zig/zls:extensions.bzl", + "zls", + dev_dependency = True, +) +zls_dev.index(file = "//zig/zls/private:versions.json") +zls_dev.toolchain( + zig_version = "0.16.0", + zls_version = "0.16.0", +) +use_repo(zls_dev, "zls_toolchains") + +register_toolchains("@zls_toolchains//:all") + bazel_dep(name = "toolchains_buildbuddy", version = "0.0.4", dev_dependency = True) buildbuddy = use_extension( diff --git a/README.md b/README.md index 98f5ee52..bce1844b 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,26 @@ them through Bazel by using the `--repo_env` flag. Examples can be found among the end-to-end tests under [`./e2e/workspace`](./e2e/workspace). +## ZLS + +ZLS toolchains are provided by a separate extension and selected by the active +Zig SDK version. + +```starlark +zls = use_extension("@rules_zig//zig/zls:extensions.bzl", "zls") +zls.index(file = "@rules_zig//zig/zls/private:versions.json") +zls.toolchain( + zig_version = "0.16.0", + zls_version = "0.16.0", +) +use_repo(zls, "zls_toolchains") +register_toolchains("@zls_toolchains//:all") +``` + +Use `zig_version` as the selector and `zls_version` as the artifact version. +They do not need to match, which allows a dev ZLS build to be tied to a stable +Zig SDK. + ## Reference Documentation Generated API documentation for the provided rules is available in diff --git a/util/BUILD.bazel b/util/BUILD.bazel index ecf9e564..aa98f066 100644 --- a/util/BUILD.bazel +++ b/util/BUILD.bazel @@ -82,3 +82,13 @@ py_binary( "//zig/private:versions.json", ], ) + +py_binary( + name = "update_zls_versions", + srcs = ["update_zls_versions.py"], + args = [ + "--output", + "$(rootpath //zig/zls/private:versions.json)", + ], + data = ["//zig/zls/private:versions.json"], +) diff --git a/util/update_zls_versions.py b/util/update_zls_versions.py new file mode 100755 index 00000000..96a1b3e8 --- /dev/null +++ b/util/update_zls_versions.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +import argparse +import json +import urllib.request + + +_ZLS_INDEX_URL = "https://builds.zigtools.org/index.json" + +_UNSUPPORTED_VERSIONS = [ +] + +_SUPPORTED_PLATFORMS = [ + "aarch64-linux", + "aarch64-macos", + "aarch64-windows", + "x86_64-linux", + "x86_64-macos", + "x86_64-windows"] + + +def fetch_zls_versions(url): + request = urllib.request.Request( + url, + headers={"User-Agent": "rules_zig update_zls_versions.py"}, + ) + with urllib.request.urlopen(request) as response: + if response.status != 200: + raise Exception(f"HTTP error: {response.status}") + data = response.read() + return json.loads(data.decode('utf-8')) + + +def _parse_semver(version_str): + """Split a semantic version into its components. + + Raises an error if the version is malformed. + + If the version contains no pre-release component, then a sentinel of + `0x10FFFF` is returned. The intent is that it sorts higher than any other + code-point, therefore making versions without pre-release component sort + higher than this with. + + If the version is the string `master` then it returns a maximum version + comprising `float("inf")` components and the pre-release sentinel. + + Returns: + (major, minor, patch, pre_release) + """ + max_component = float("inf") + max_prerelease = chr(0x10FFFF) # Highest valid code point in Unicode + + if version_str == "master": + return max_component, max_component, max_component, max_prerelease + + pre_version, *_ = version_str.split("+", maxsplit=1) + main_version, *pre_release = pre_version.split("-", maxsplit=1) + major, minor, patch = map(int, main_version.split(".")) + + pre_release_segment = pre_release[0] if pre_release else max_prerelease + + return major, minor, patch, pre_release_segment + + +def generate_json_content(data, unsupported_versions, supported_platforms): + content = {} + + for version, platforms in sorted(data.items(), key=lambda x: _parse_semver(x[0]), reverse=True): + if version in unsupported_versions or version == "master": + continue + + for platform, info in sorted(platforms.items()): + if platform not in supported_platforms or not isinstance(info, dict): + continue + + content.setdefault(version, {})[platform] = { + "tarball": info["tarball"], + "shasum": info["shasum"], + } + + return content + + +def main(): + parser = argparse.ArgumentParser(description="Generate JSON file for ZLS versions.") + parser.add_argument("--output", type=argparse.FileType('w'), default='-', help="Output file path or '-' for stdout.") + parser.add_argument("--url", default=_ZLS_INDEX_URL, help="URL to fetch ZLS versions JSON") + parser.add_argument("--unsupported-versions", nargs="*", default=_UNSUPPORTED_VERSIONS, help="List of unsupported ZLS versions") + parser.add_argument("--supported-platforms", nargs="*", default=_SUPPORTED_PLATFORMS, help="List of supported platforms") + args = parser.parse_args() + + zls_data = fetch_zls_versions(args.url) + json_content = generate_json_content(zls_data, set(args.unsupported_versions), set(args.supported_platforms)) + + json.dump(json_content, args.output, indent=2) + args.output.write("\n") + + +if __name__ == "__main__": + main() diff --git a/zig/BUILD.bazel b/zig/BUILD.bazel index 6dae3b9f..6e77cd79 100644 --- a/zig/BUILD.bazel +++ b/zig/BUILD.bazel @@ -91,6 +91,7 @@ filegroup( "//zig/settings:all_files", "//zig/target:all_files", "//zig/translate-c:all_files", + "//zig/zls:all_files", ], visibility = ["//:__pkg__"], ) diff --git a/zig/tests/zls-completion/BUILD.bazel b/zig/tests/zls-completion/BUILD.bazel new file mode 100644 index 00000000..968ac5e7 --- /dev/null +++ b/zig/tests/zls-completion/BUILD.bazel @@ -0,0 +1,13 @@ +load("//zig:defs.bzl", "zig_library") +load("//zig/zls:defs.bzl", "zls_completion") + +zig_library( + name = "lib", + main = "main.zig", +) + +zls_completion( + name = "completion", + testonly = True, + deps = [":lib"], +) diff --git a/zig/tests/zls-completion/main.zig b/zig/tests/zls-completion/main.zig new file mode 100644 index 00000000..1ebf7423 --- /dev/null +++ b/zig/tests/zls-completion/main.zig @@ -0,0 +1,4 @@ +pub fn value() i32 { + return 42; +} + diff --git a/zig/zls/BUILD.bazel b/zig/zls/BUILD.bazel new file mode 100644 index 00000000..b57244fa --- /dev/null +++ b/zig/zls/BUILD.bazel @@ -0,0 +1,104 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//zig/zls/private:resolved_toolchain.bzl", "resolved_toolchain") + +exports_files( + [ + "defs.bzl", + "extensions.bzl", + "toolchain.bzl", + "workspace_printer.zig", + "workspace_printer_test.zig", + "zls_build_runner.zig", + "zls_runner.zig", + ], + visibility = ["//visibility:public"], +) + +toolchain_type( + name = "toolchain_type", + visibility = ["//visibility:public"], +) + +resolved_toolchain( + name = "resolved_toolchain", + tags = ["manual"], + visibility = ["//visibility:public"], +) + +bzl_library( + name = "defs", + srcs = ["defs.bzl"], + visibility = ["//visibility:public"], + deps = [":zls_completion"], +) + +bzl_library( + name = "extensions", + srcs = ["extensions.bzl"], + visibility = ["//visibility:public"], + deps = ["//zig/zls/private/bzlmod:zls"], +) + +bzl_library( + name = "toolchain", + srcs = ["toolchain.bzl"], + visibility = ["//visibility:public"], +) + +bzl_library( + name = "zls_completion", + srcs = ["zls_completion.bzl"], + visibility = ["//visibility:public"], + deps = [ + ":zls_write_build_config", + ":zls_write_runner_zig_src", + "//zig:defs", + "@aspect_bazel_lib//lib:utils", + "@bazel_skylib//rules:expand_template", + ], +) + +bzl_library( + name = "zls_write_build_config", + srcs = ["zls_write_build_config.bzl"], + visibility = ["//visibility:public"], + deps = [ + "//zig/private:cc_helper", + "//zig/private/common:translate_c", + "//zig/private/common:zig_cache", + "//zig/private/common:zig_lib_dir", + "//zig/private/providers:zig_module_info", + "//zig/private/providers:zig_target_info", + "//zig/translate-c:toolchain", + "@rules_cc//cc:find_cc_toolchain_bzl", + "@rules_cc//cc/common", + ], +) + +bzl_library( + name = "zls_write_runner_zig_src", + srcs = ["zls_write_runner_zig_src.bzl"], + visibility = ["//visibility:public"], + deps = ["@aspect_bazel_lib//lib:paths"], +) + +# Execute `bazel run //util:update_filegroups` to update this target. +filegroup( + name = "all_files", + srcs = [ + ":BUILD.bazel", + ":defs.bzl", + ":extensions.bzl", + ":toolchain.bzl", + ":workspace_printer.zig", + ":workspace_printer_test.zig", + ":zls_build_runner.zig", + ":zls_completion.bzl", + ":zls_runner.zig", + ":zls_write_build_config.bzl", + ":zls_write_runner_zig_src.bzl", + "//zig/zls/private:all_files", + "//zig/zls/tests:all_files", + ], + visibility = ["//zig:__pkg__"], +) diff --git a/zig/zls/defs.bzl b/zig/zls/defs.bzl new file mode 100644 index 00000000..6bef3593 --- /dev/null +++ b/zig/zls/defs.bzl @@ -0,0 +1,5 @@ +"""Rules for ZLS integration.""" + +load("//zig/zls:zls_completion.bzl", _zls_completion = "zls_completion") + +zls_completion = _zls_completion diff --git a/zig/zls/extensions.bzl b/zig/zls/extensions.bzl new file mode 100644 index 00000000..61e02b12 --- /dev/null +++ b/zig/zls/extensions.bzl @@ -0,0 +1,5 @@ +"""Extensions for ZLS bzlmod support.""" + +load("//zig/zls/private/bzlmod:zls.bzl", _zls = "zls") + +zls = _zls diff --git a/zig/zls/private/BUILD.bazel b/zig/zls/private/BUILD.bazel new file mode 100644 index 00000000..701df5ab --- /dev/null +++ b/zig/zls/private/BUILD.bazel @@ -0,0 +1,25 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +exports_files( + ["versions.json"], + visibility = ["//visibility:public"], +) + +bzl_library( + name = "resolved_toolchain", + srcs = ["resolved_toolchain.bzl"], + visibility = ["//visibility:public"], +) + +# Execute `bazel run //util:update_filegroups` to update this target. +filegroup( + name = "all_files", + srcs = [ + ":BUILD.bazel", + ":resolved_toolchain.bzl", + ":versions.json", + "//zig/zls/private/bzlmod:all_files", + "//zig/zls/private/repo:all_files", + ], + visibility = ["//zig/zls:__pkg__"], +) diff --git a/zig/zls/private/bzlmod/BUILD.bazel b/zig/zls/private/bzlmod/BUILD.bazel new file mode 100644 index 00000000..4d9af1c1 --- /dev/null +++ b/zig/zls/private/bzlmod/BUILD.bazel @@ -0,0 +1,23 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "zls", + srcs = ["zls.bzl"], + visibility = ["//zig:__subpackages__"], + deps = [ + "//zig/private:platforms", + "//zig/zls/private/repo:toolchains_repo", + "//zig/zls/private/repo:zls_repository", + "@bazel_skylib//lib:sets", + ], +) + +# Execute `bazel run //util:update_filegroups` to update this target. +filegroup( + name = "all_files", + srcs = [ + ":BUILD.bazel", + ":zls.bzl", + ], + visibility = ["//zig/zls/private:__pkg__"], +) diff --git a/zig/zls/private/bzlmod/zls.bzl b/zig/zls/private/bzlmod/zls.bzl new file mode 100644 index 00000000..dcfa8a61 --- /dev/null +++ b/zig/zls/private/bzlmod/zls.bzl @@ -0,0 +1,214 @@ +"""Implementation of the `zls` module extension.""" + +load("@bazel_skylib//lib:sets.bzl", "sets") +load("//zig/private:platforms.bzl", "PLATFORMS") +load("//zig/zls/private/repo:toolchains_repo.bzl", "sanitize_version", "toolchains_repo") +load("//zig/zls/private/repo:zls_repository.bzl", "zls_repository") + +DOC = """\ +Installs ZLS toolchains. + +Each ZLS toolchain is explicitly tied to a Zig SDK version. The Zig SDK version +is only used for toolchain selection through @zig_toolchains//:version; it does +not need to match the ZLS artifact version. +""" + +_DEFAULT_NAME = "zls" + +zls_toolchain = tag_class( + attrs = { + "zig_version": attr.string( + doc = "The Zig SDK version that selects this ZLS toolchain.", + mandatory = True, + ), + "zls_version": attr.string( + doc = "The ZLS artifact version to download.", + mandatory = True, + ), + }, + doc = """\ +Fetch and define ZLS toolchain targets for the given ZLS artifact version, gated +by the given Zig SDK version. +""", +) + +zls_index = tag_class( + attrs = { + "file": attr.label(doc = "The ZLS version index JSON file.", mandatory = True), + }, + doc = """\ +Extend the set of known ZLS versions based on a ZLS version index. +""", +) + +zls_mirrors = tag_class( + attrs = { + "urls": attr.string_list(doc = "The mirrors base URLs.", mandatory = True), + }, +) + +TAG_CLASSES = { + "toolchain": zls_toolchain, + "index": zls_index, + "mirrors": zls_mirrors, +} + +def parse_zls_versions_json(json_string): + """Parse a ZLS versions index in JSON format. + + Args: + json_string: String, The version index in JSON format. + + Returns: + (err, data), maybe an error or a + `dict[version, dict[platform, struct(url, sha256)]]`. + """ + result = {} + + data = json.decode(json_string, default = None) + if data == None: + return "Invalid JSON format in ZLS version index.", None + + for version, platforms in data.items(): + if type(platforms) != "dict": + continue + + for platform, info in platforms.items(): + if type(info) != "dict" or not platform in PLATFORMS: + continue + + if not "tarball" in info: + return "Missing `tarball` field in ZLS version index.", None + + if not "shasum" in info: + return "Missing `shasum` field in ZLS version index.", None + + result.setdefault(version, {})[platform] = struct( + url = info["tarball"], + sha256 = info["shasum"], + ) + + return None, result + +def merge_version_specs(version_specs): + """Merge ZLS version indices. + + Args: + version_specs: sequence of `dict[version, dict[platform, struct(url, sha256)]]`. + + Returns: + `dict[version, dict[platform, struct(url, sha256)]]` + """ + result = {} + + for spec in version_specs: + for version, platforms in spec.items(): + for platform, info in platforms.items(): + result.setdefault(version, {})[platform] = info + + return result + +def handle_toolchain_tags(modules): + """Handle the zls module extension's toolchain tags. + + Args: + modules: sequence of module objects. + + Returns: + (err, mappings), maybe an error or a list of + `struct(zig_version, zls_version)` mappings. + """ + seen_zig_versions = sets.make() + mappings = [] + + for mod in modules: + for toolchain in mod.tags.toolchain: + if sets.contains(seen_zig_versions, toolchain.zig_version): + return (["You may only specify one ZLS toolchain for Zig SDK version '{}'.".format(toolchain.zig_version), toolchain], None) + + sets.insert(seen_zig_versions, toolchain.zig_version) + mappings.append(struct( + zig_version = toolchain.zig_version, + zls_version = toolchain.zls_version, + )) + + return None, mappings + +def _toolchain_extension(module_ctx): + mirrors = [] + version_specs = [] + for mod in module_ctx.modules: + for mirrors_tag in mod.tags.mirrors: + mirrors.extend(mirrors_tag.urls) + for index in mod.tags.index: + file_path = module_ctx.path(index.file) + file_content = module_ctx.read(file_path) + (err, parsed) = parse_zls_versions_json(file_content) + + if err != None: + fail(err, index) + + version_specs.append(parsed) + + known_versions = merge_version_specs(version_specs) + + (err, mappings) = handle_toolchain_tags(module_ctx.modules) + if err != None: + fail(*err) + + if len(mappings) == 0: + fail("The zls extension requires at least one zls.toolchain tag.") + + toolchain_names = [] + toolchain_labels = [] + toolchain_zig_versions = [] + toolchain_exec_lengths = [] + toolchain_exec_constraints = [] + created_repos = sets.make() + for mapping in mappings: + if mapping.zls_version not in known_versions: + fail("Unknown ZLS version '{}'. Add it to zls.index or choose a known version.".format(mapping.zls_version)) + + sanitized_zls_version = sanitize_version(mapping.zls_version) + for platform, meta in PLATFORMS.items(): + if platform not in known_versions[mapping.zls_version]: + continue + + repo_name = _DEFAULT_NAME + "_" + sanitized_zls_version + "_" + platform + toolchain_names.append("{}_for_{}_{}".format( + sanitized_zls_version, + sanitize_version(mapping.zig_version), + platform, + )) + toolchain_labels.append("@{}//:zls_toolchain".format(repo_name)) + toolchain_zig_versions.append(mapping.zig_version) + toolchain_exec_lengths.append(len(meta.compatible_with)) + toolchain_exec_constraints.extend(meta.compatible_with) + if not sets.contains(created_repos, repo_name): + sets.insert(created_repos, repo_name) + zls_repository( + name = repo_name, + url = known_versions[mapping.zls_version][platform].url, + mirrors = mirrors, + sha256 = known_versions[mapping.zls_version][platform].sha256, + zls_version = mapping.zls_version, + platform = platform, + ) + + if len(toolchain_names) == 0: + fail("No ZLS toolchains were generated. Check that the requested ZLS versions have entries for supported platforms.") + + toolchains_repo( + name = _DEFAULT_NAME + "_toolchains", + names = toolchain_names, + labels = toolchain_labels, + zig_versions = toolchain_zig_versions, + exec_lengths = toolchain_exec_lengths, + exec_constraints = toolchain_exec_constraints, + ) + +zls = module_extension( + implementation = _toolchain_extension, + doc = DOC, + tag_classes = TAG_CLASSES, +) diff --git a/zig/zls/private/repo/BUILD.bazel b/zig/zls/private/repo/BUILD.bazel new file mode 100644 index 00000000..47a5e331 --- /dev/null +++ b/zig/zls/private/repo/BUILD.bazel @@ -0,0 +1,24 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "toolchains_repo", + srcs = ["toolchains_repo.bzl"], + visibility = ["//visibility:public"], +) + +bzl_library( + name = "zls_repository", + srcs = ["zls_repository.bzl"], + visibility = ["//visibility:public"], +) + +# Execute `bazel run //util:update_filegroups` to update this target. +filegroup( + name = "all_files", + srcs = [ + ":BUILD.bazel", + ":toolchains_repo.bzl", + ":zls_repository.bzl", + ], + visibility = ["//zig/zls/private:__pkg__"], +) diff --git a/zig/zls/private/repo/toolchains_repo.bzl b/zig/zls/private/repo/toolchains_repo.bzl new file mode 100644 index 00000000..d4a28ce6 --- /dev/null +++ b/zig/zls/private/repo/toolchains_repo.bzl @@ -0,0 +1,85 @@ +"""Create a repository to hold ZLS toolchain targets.""" + +DOC = """\ +Create a repository that defines toolchain targets for all ZLS toolchains. + +Each generated toolchain is selected by both execution platform constraints and +the Zig SDK version selected through @zig_toolchains//:version. +""" + +ATTRS = { + "names": attr.string_list(doc = "The name suffixes to assign to the generated toolchain targets."), + "labels": attr.string_list(doc = "The labels to the ZLS toolchain implementation targets."), + "zig_versions": attr.string_list(doc = "The Zig SDK versions that select the corresponding ZLS toolchain targets."), + "exec_lengths": attr.int_list(doc = "The length of the slice of the `exec_constraints` attribute that corresponds to each toolchain target."), + "exec_constraints": attr.string_list(doc = "All toolchain execution platform constraints concatenated to a single list."), +} + +def sanitize_version(version): + """Replace illegal repository name characters in a version string.""" + return version.replace("+", "_P") + +def _calc_counter_digits(num): + return max(4, len(repr(num))) + +def _counter_prefix(count, *, width): + count_repr = repr(count) + prefix = "0" * (width - len(count_repr)) + return prefix + count_repr + +def _toolchains_repo_impl(repository_ctx): + len_expected = len(repository_ctx.attr.names) + len_equal = all([ + len_expected == len(getattr(repository_ctx.attr, attr)) + for attr in ["labels", "zig_versions", "exec_lengths"] + ]) + if not len_equal: + fail("Lengths of the attributes `names`, `labels`, `zig_versions`, `exec_lengths` must match.") + + len_exec_constraints = 0 + for exec_len in repository_ctx.attr.exec_lengths: + len_exec_constraints += exec_len + + if not len_exec_constraints == len(repository_ctx.attr.exec_constraints): + fail("Length of the `exec_constraints` attribute must match the sum of `exec_lengths`.") + + build_content = """\ +# Generated by zig/zls/private/repo/toolchains_repo.bzl +# +# These can be registered in the workspace file or passed to --extra_toolchains. +""" + + counter_digits = _calc_counter_digits(len(repository_ctx.attr.zig_versions)) + zipped = zip( + repository_ctx.attr.names, + repository_ctx.attr.labels, + repository_ctx.attr.zig_versions, + repository_ctx.attr.exec_lengths, + ) + exec_offset = 0 + for counter, (name, label, zig_version, exec_len) in enumerate(zipped): + compatible_with = repository_ctx.attr.exec_constraints[exec_offset:exec_offset + exec_len] + exec_offset += exec_len + build_content += """ +toolchain( + name = "{prefix}_{name}_toolchain", + exec_compatible_with = {compatible_with}, + target_settings = ["@zig_toolchains//:{zig_version}"], + toolchain = "{label}", + toolchain_type = "@rules_zig//zig/zls:toolchain_type", +) +""".format( + prefix = _counter_prefix(counter, width = counter_digits), + name = name, + compatible_with = compatible_with, + zig_version = zig_version, + label = label, + ) + + repository_ctx.file("BUILD.bazel", build_content) + +toolchains_repo = repository_rule( + _toolchains_repo_impl, + doc = DOC, + attrs = ATTRS, +) diff --git a/zig/zls/private/repo/zls_repository.bzl b/zig/zls/private/repo/zls_repository.bzl new file mode 100644 index 00000000..406ed126 --- /dev/null +++ b/zig/zls/private/repo/zls_repository.bzl @@ -0,0 +1,78 @@ +"""Implementation of the `zls_repository` repository rule.""" + +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "update_attrs") + +DOC = "Fetch and install a ZLS toolchain." + +ATTRS = { + "url": attr.string(mandatory = True, doc = "The URL to the ZLS release archive."), + "mirrors": attr.string_list(mandatory = True, doc = "List of URLs to ZLS release mirrors."), + "sha256": attr.string(mandatory = False, doc = "The expected SHA-256 of the downloaded artifact. Provide only one of `sha256` or `integrity`."), + "integrity": attr.string(mandatory = False, doc = "The expected checksum of the downloaded artifact in Subresource Integrity format. Provide only one of `sha256` or `integrity`."), + "zls_version": attr.string(mandatory = True, doc = "The ZLS version number."), + "platform": attr.string(mandatory = True, doc = "The platform that ZLS can execute on, e.g. `x86_64-linux` or `aarch64-macos`."), +} + +def _basename(path): + return path.rpartition("/")[-1] + +def _mirrors_urls(url, mirrors): + filename = _basename(url) + return [ + mirror + "/" + filename + for mirror in mirrors + ] + [url] + +def _get_integrity_args(*, sha256, integrity): + result = {} + + if sha256 and integrity: + fail("You may only specify one of `sha256` or `integrity`.") + elif sha256: + result["sha256"] = sha256 + elif integrity: + result["integrity"] = integrity + + return result + +def _zls_repository_impl(repository_ctx): + bin = "zls.exe" if repository_ctx.attr.platform.find("windows") != -1 else "zls" + + repository_ctx.file("BUILD.bazel", """\ +# Generated by zig/zls/private/repo/zls_repository.bzl + +load("@rules_zig//zig/zls:toolchain.bzl", "zls_toolchain") + +zls_toolchain( + name = "zls_toolchain", + bin = {bin}, + zls_version = {zls_version}, +) +""".format( + bin = repr(bin), + zls_version = repr(repository_ctx.attr.zls_version), + )) + + download_args = { + "url": _mirrors_urls( + url = repository_ctx.attr.url, + mirrors = repository_ctx.attr.mirrors, + ), + } + integrity = _get_integrity_args( + sha256 = repository_ctx.attr.sha256, + integrity = repository_ctx.attr.integrity, + ) + download_args.update(**integrity) + + download = repository_ctx.download_and_extract(**download_args) + + integrity_override = {} if integrity else {"integrity": download.integrity} + + return update_attrs(repository_ctx.attr, ATTRS.keys(), integrity_override) + +zls_repository = repository_rule( + _zls_repository_impl, + attrs = ATTRS, + doc = DOC, +) diff --git a/zig/zls/private/resolved_toolchain.bzl b/zig/zls/private/resolved_toolchain.bzl new file mode 100644 index 00000000..9b42cc55 --- /dev/null +++ b/zig/zls/private/resolved_toolchain.bzl @@ -0,0 +1,24 @@ +"""This module implements an alias rule to the resolved toolchain.""" + +DOC = """\ +Exposes a concrete toolchain which is the result of Bazel resolving the +toolchain for the execution or target platform. +Workaround for https://github.com/bazelbuild/bazel/issues/14009 +""" + +# Forward all the providers +def _resolved_toolchain_impl(ctx): + toolchain_info = ctx.toolchains["//zig/zls:toolchain_type"] + return [ + toolchain_info, + toolchain_info.default, + toolchain_info.zlstoolchaininfo, + ] + +# Copied from java_toolchain_alias +# https://cs.opensource.google/bazel/bazel/+/master:tools/jdk/java_toolchain_alias.bzl +resolved_toolchain = rule( + implementation = _resolved_toolchain_impl, + toolchains = ["//zig/zls:toolchain_type"], + doc = DOC, +) diff --git a/zig/zls/private/versions.json b/zig/zls/private/versions.json new file mode 100644 index 00000000..ebfa5f07 --- /dev/null +++ b/zig/zls/private/versions.json @@ -0,0 +1,204 @@ +{ + "0.16.0": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-aarch64-linux-0.16.0.tar.xz", + "shasum": "430cd293d201eb70ae2519dbc96c854bf8791b8df7fc9392e8d2dc9680a2bed7" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-aarch64-macos-0.16.0.tar.xz", + "shasum": "b93ec549f8558a7e85984a840e9276d274f1059b54ade4254296ef4982958359" + }, + "aarch64-windows": { + "tarball": "https://builds.zigtools.org/zls-aarch64-windows-0.16.0.zip", + "shasum": "ef4c5ccb93c80c9f023105c5f558ae8774ac6668d560ba6f92a2f87d95df2311" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-x86_64-linux-0.16.0.tar.xz", + "shasum": "ded6d562a0b86ee878b1ddf70ffab2797ce3cdca3b02d6077548f9d56dff96b6" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-x86_64-macos-0.16.0.tar.xz", + "shasum": "49f716ea96c1aadaecaa5d9c0a50874cbcf443dc42b825f1e7ee35499ad3eb96" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-x86_64-windows-0.16.0.zip", + "shasum": "35cbb7163224e8cf92d21099c1b1391f2aba927f25d389f021b13a21d40b96dd" + } + }, + "0.15.1": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-aarch64-linux-0.15.1.tar.xz", + "shasum": "a2daa860a0e0cd1410491ff9703c6aaca96defd833b88af6a9811d6ff04fc13b" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-aarch64-macos-0.15.1.tar.xz", + "shasum": "a6b3f1b10d77f37f3b9d962093f030334b083f48eb2607a4b3ccb72de2958133" + }, + "aarch64-windows": { + "tarball": "https://builds.zigtools.org/zls-aarch64-windows-0.15.1.zip", + "shasum": "46f7c224db4045e5948bbb0608313539343d46466030f8d253488a75b5aabd44" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-x86_64-linux-0.15.1.tar.xz", + "shasum": "3bb38f522cb23213e8c075ac6b170273fe49b4274b8c12b034cc496407400067" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-x86_64-macos-0.15.1.tar.xz", + "shasum": "b76aa724be3f69799f08063f84e93b8b5925f6bf6007f251a2fbea4f9fc244dd" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-x86_64-windows-0.15.1.zip", + "shasum": "41b20c8e2952c95385ce316c3e8000178a6deb4857893f2f6d8078b1453c0f54" + } + }, + "0.15.0": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-aarch64-linux-0.15.0.tar.xz", + "shasum": "2d1c91382dbbd7a34c3bd87da506e3c2ce6e6582612c2b371f7f97b46c5557d4" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-aarch64-macos-0.15.0.tar.xz", + "shasum": "76c7a23190f67e67970024065f689c2c49b3c7b0fc16876fb24ef199fb05fc2a" + }, + "aarch64-windows": { + "tarball": "https://builds.zigtools.org/zls-aarch64-windows-0.15.0.zip", + "shasum": "5765e0fe96674577d4104e1e832205622390b8c1753305be78b9966f8d2d8468" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-x86_64-linux-0.15.0.tar.xz", + "shasum": "508bfe3fd637d2a02f07f3fc7da8900351f407116b03685c5dae26b4f01a30de" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-x86_64-macos-0.15.0.tar.xz", + "shasum": "46c31838bfef5adcc7fee82428c3ec2b9abbfae38242639afea5f242ee133d93" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-x86_64-windows-0.15.0.zip", + "shasum": "5f5e6861d55e96cc9e480cf15fbccafd2c44dfe193d8f3f9d54489a50cd73d2d" + } + }, + "0.14.0": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-aarch64-0.14.0.tar.xz", + "shasum": "d85f4679af3961db149ead8a355eab4652c3e738eecaad69174cab5f1a1196cc" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-aarch64-0.14.0.tar.xz", + "shasum": "dfb627e1f9603583678f552d8035a12dce878215c0a507b32d6f1b9d074d6c4d" + }, + "aarch64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-aarch64-0.14.0.zip", + "shasum": "7a6d649bafe5d09334b095829b461de1ee7f09278e068b28b90f1566df710a38" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-x86_64-0.14.0.tar.xz", + "shasum": "661f8d402ba3dc9b04b6e9bc3026495be7b838d2f18d148db2bd98bd699c1360" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-x86_64-0.14.0.tar.xz", + "shasum": "baee69e4645deeccb42970b4a01f573592209dc1cf72e32893c59ca06af511dc" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-x86_64-0.14.0.zip", + "shasum": "10bb73102bab4d2fa9fd00ef48ad84ff2332b91e7fc449de751676367fe7dfd2" + } + }, + "0.13.0": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-aarch64-0.13.0.tar.xz", + "shasum": "8e258711168c2e3e7e81d6074663cfe291309b779928aaa4c66aed1affeba1aa" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-aarch64-0.13.0.tar.xz", + "shasum": "9848514524f5e5d33997ac280b7d92388407209d4b8d4be3866dc3cf30ca6ca8" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-x86_64-0.13.0.tar.xz", + "shasum": "ec4c1b45caf88e2bcb9ebb16c670603cc596e4f621b96184dfbe837b39cd8410" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-x86_64-0.13.0.tar.xz", + "shasum": "4b63854d6b76810abd2563706e7d768efc7111e44dd8b371d49198e627697a13" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-x86_64-0.13.0.zip", + "shasum": "d87ed0834df3c30feae976843f0c6640acd31af1f31c0917907f7bfebae5bd14" + } + }, + "0.12.0": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-aarch64-0.12.0.tar.xz", + "shasum": "ea81ee5c64c8b39aaf23c26d641e263470738d76bee945db9f7207bad10f6d6f" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-aarch64-0.12.0.tar.xz", + "shasum": "48892e8e75ebd8cbe1d82548e20094c4c9f7f1b81fdabe18b430f334d93dc76c" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-x86_64-0.12.0.tar.xz", + "shasum": "a1049798c9d3b14760f24de5c0a6b5a176abd404979828342b7319939563dfaa" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-x86_64-0.12.0.tar.xz", + "shasum": "6c6b24d2d57de6fcae8c44d8c484a359262b4a46339fe339a6fade433fc7c6b6" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-x86_64-0.12.0.zip", + "shasum": "3ff600660081c1867a83a800d22ad784849d1bee2e18bbe4495b95164e3de136" + } + }, + "0.11.0": { + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-aarch64-0.11.0.tar.xz", + "shasum": "43184d2d324b27d2f18b72818676b367e6633264a0f4d74d1249b8a0824d1e1c" + }, + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-aarch64-0.11.0.tar.xz", + "shasum": "5152757727a958e6991b09fee4fb1b89c42b0e1c19f6b866e3567a83a126851c" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-x86_64-0.11.0.tar.xz", + "shasum": "bd65d0cd79e83395b98035991b100821589b07ed8716fb2a44b1e234c9167f3f" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-x86_64-0.11.0.tar.xz", + "shasum": "8d3d83c8e1fc7a13d0c58624a9a0bdb289771c3714d01d7aace24277c95e70fb" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-x86_64-0.11.0.zip", + "shasum": "b14608a9541e89cbe8993ff22a6e3cf6248dd326cc5d42c4ee5469f2933e155b" + } + }, + "0.10.0": { + "aarch64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-aarch64-0.10.0.tar.xz", + "shasum": "543c9f7d8895ab12b8c0b860601513c54d354ffd558a439fed9152af74c65ce6" + }, + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-x86_64-0.10.0.tar.xz", + "shasum": "9a6cda8a9dc4b536f76439285541ad197eb30f67b0df47746411043c48091351" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-x86_64-0.10.0.tar.xz", + "shasum": "bebd917db44e8fff8daf5aab9f06dbee183dad1ce351bc6ecb264ccae710d951" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-x86_64-0.10.0.zip", + "shasum": "f9a29b8e5a743282112c53caa28de7f8534e4c83cf801011263202266fc5ff2e" + } + }, + "0.9.0": { + "x86_64-linux": { + "tarball": "https://builds.zigtools.org/zls-linux-x86_64-0.9.0.tar.xz", + "shasum": "0bb16e2e3a1c4dab22b1d6b25deeefd2212abcc2e88702a3f58705164703a7f8" + }, + "x86_64-macos": { + "tarball": "https://builds.zigtools.org/zls-macos-x86_64-0.9.0.tar.xz", + "shasum": "d8f2e8deda1751d7d46979b686784ebd5c843a9ba8f0bce69424351c4bfbea5f" + }, + "x86_64-windows": { + "tarball": "https://builds.zigtools.org/zls-windows-x86_64-0.9.0.zip", + "shasum": "0a99b39124c536fc277208b71c1ddb82a8ba29aa9de1df5a4e824d633420f62e" + } + } +} diff --git a/zig/zls/tests/BUILD.bazel b/zig/zls/tests/BUILD.bazel new file mode 100644 index 00000000..20f150fb --- /dev/null +++ b/zig/zls/tests/BUILD.bazel @@ -0,0 +1,25 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load(":bzlmod_zls_test.bzl", "bzlmod_zls_test_suite") + +bzlmod_zls_test_suite(name = "bzlmod_zls_test") + +bzl_library( + name = "bzlmod_zls_test_bzl", + srcs = ["bzlmod_zls_test.bzl"], + visibility = ["//visibility:private"], + deps = [ + "//zig/zls/private/bzlmod:zls", + "@bazel_skylib//lib:partial", + "@bazel_skylib//lib:unittest", + ], +) + +# Execute `bazel run //util:update_filegroups` to update this target. +filegroup( + name = "all_files", + srcs = [ + ":BUILD.bazel", + ":bzlmod_zls_test.bzl", + ], + visibility = ["//zig/zls:__pkg__"], +) diff --git a/zig/zls/tests/bzlmod_zls_test.bzl b/zig/zls/tests/bzlmod_zls_test.bzl new file mode 100644 index 00000000..4a2d224e --- /dev/null +++ b/zig/zls/tests/bzlmod_zls_test.bzl @@ -0,0 +1,141 @@ +"""Unit tests for ZLS module extension.""" + +load("@bazel_skylib//lib:partial.bzl", "partial") +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load( + "//zig/zls/private/bzlmod:zls.bzl", + "handle_toolchain_tags", + "merge_version_specs", + "parse_zls_versions_json", +) + +def _parse_zls_index_test_impl(ctx): + env = unittest.begin(ctx) + + content = """\ +{ + "0.16.0": { + "date": "2026-04-16", + "aarch64-linux": { + "tarball": "https://builds.zigtools.org/zls-aarch64-linux-0.16.0.tar.xz", + "shasum": "430cd293d201eb70ae2519dbc96c854bf8791b8df7fc9392e8d2dc9680a2bed7" + }, + "arm-linux": { + "tarball": "https://builds.zigtools.org/zls-arm-linux-0.16.0.tar.xz", + "shasum": "7cf8d11f914127809b89254ad97e4b96d84294370418954a49b78bd623d3c55e" + } + } +} +""" + expected = { + "0.16.0": { + "aarch64-linux": struct( + url = "https://builds.zigtools.org/zls-aarch64-linux-0.16.0.tar.xz", + sha256 = "430cd293d201eb70ae2519dbc96c854bf8791b8df7fc9392e8d2dc9680a2bed7", + ), + }, + } + result = parse_zls_versions_json(content) + asserts.equals(env, (None, expected), result) + + content = """\ +{ + "0.16.0": { + "aarch64-linux": { + "shasum": "430cd293d201eb70ae2519dbc96c854bf8791b8df7fc9392e8d2dc9680a2bed7" + } + } +} +""" + result = parse_zls_versions_json(content) + asserts.equals(env, "Missing `tarball` field in ZLS version index.", result[0]) + + return unittest.end(env) + +_parse_zls_index_test = unittest.make( + _parse_zls_index_test_impl, +) + +def _merge_version_specs_test_impl(ctx): + env = unittest.begin(ctx) + + result = merge_version_specs([ + { + "0.16.0": { + "aarch64-linux": struct(url = "url-1", sha256 = "sha-1"), + }, + }, + { + "0.16.0": { + "x86_64-linux": struct(url = "url-2", sha256 = "sha-2"), + }, + }, + ]) + + asserts.equals( + env, + { + "0.16.0": { + "aarch64-linux": struct(url = "url-1", sha256 = "sha-1"), + "x86_64-linux": struct(url = "url-2", sha256 = "sha-2"), + }, + }, + result, + ) + + return unittest.end(env) + +_merge_version_specs_test = unittest.make( + _merge_version_specs_test_impl, +) + +def _zls_toolchain_tags_test_impl(ctx): + env = unittest.begin(ctx) + + result = handle_toolchain_tags([ + struct( + tags = struct( + toolchain = [ + struct( + zig_version = "0.16.0", + zls_version = "0.17.0-dev.1+abc", + ), + ], + ), + ), + ]) + asserts.equals(env, None, result[0]) + asserts.equals(env, "0.16.0", result[1][0].zig_version) + asserts.equals(env, "0.17.0-dev.1+abc", result[1][0].zls_version) + + result = handle_toolchain_tags([ + struct( + tags = struct( + toolchain = [ + struct( + zig_version = "0.16.0", + zls_version = "0.16.0", + ), + struct( + zig_version = "0.16.0", + zls_version = "0.17.0-dev.1+abc", + ), + ], + ), + ), + ]) + asserts.equals(env, "You may only specify one ZLS toolchain for Zig SDK version '0.16.0'.", result[0][0]) + + return unittest.end(env) + +_zls_toolchain_tags_test = unittest.make( + _zls_toolchain_tags_test_impl, +) + +def bzlmod_zls_test_suite(name): + unittest.suite( + name, + partial.make(_parse_zls_index_test, size = "small"), + partial.make(_merge_version_specs_test, size = "small"), + partial.make(_zls_toolchain_tags_test, size = "small"), + ) diff --git a/zig/zls/toolchain.bzl b/zig/zls/toolchain.bzl new file mode 100644 index 00000000..2e3bc9eb --- /dev/null +++ b/zig/zls/toolchain.bzl @@ -0,0 +1,50 @@ +"""Rules to declare ZLS toolchains.""" + +ZlsToolchainInfo = provider( + doc = "Information about how to invoke a ZLS executable.", + fields = { + "bin": "File, The ZLS executable.", + "files_to_run": "FilesToRunProvider, The ZLS executable and its runfiles.", + "zls_version": "String, The ZLS version.", + }, +) + +def _zls_toolchain_impl(ctx): + bin = ctx.executable.bin + default = DefaultInfo( + files = depset([bin]), + runfiles = ctx.runfiles(files = [bin]), + ) + zlstoolchaininfo = ZlsToolchainInfo( + bin = bin, + files_to_run = ctx.attr.bin[DefaultInfo].files_to_run, + zls_version = ctx.attr.zls_version, + ) + toolchain_info = platform_common.ToolchainInfo( + default = default, + zlstoolchaininfo = zlstoolchaininfo, + ) + + return [ + default, + zlstoolchaininfo, + toolchain_info, + ] + +zls_toolchain = rule( + implementation = _zls_toolchain_impl, + attrs = { + "bin": attr.label( + doc = "The ZLS executable target or prebuilt executable file.", + allow_single_file = True, + cfg = "exec", + executable = True, + mandatory = True, + ), + "zls_version": attr.string( + doc = "The ZLS version.", + mandatory = True, + ), + }, + doc = "Defines a ZLS toolchain.", +) diff --git a/zig/zls/workspace_printer.zig b/zig/zls/workspace_printer.zig new file mode 100644 index 00000000..0bb6818b --- /dev/null +++ b/zig/zls/workspace_printer.zig @@ -0,0 +1,169 @@ +const std = @import("std"); + +const bazel_builtin = @import("bazel_builtin"); +const runfiles = @import("runfiles"); + +/// https://github.com/zigtools/zls/blob/master/src/build_runner/shared.zig#L6 (2026-03-10) +pub const BuildConfig = struct { + /// The `dependencies` in `build.zig.zon`. + dependencies: std.json.ArrayHashMap([]const u8), + /// The key is the `root_source_file`. + /// All modules with the same root source file are merged. This limitation may be lifted in the future. + modules: std.json.ArrayHashMap(Module), + /// List of all compilations units. + compilations: []Compile, + /// The names of all top level steps. + top_level_steps: []const []const u8, + available_options: std.json.ArrayHashMap(AvailableOption), + + pub const Module = struct { + import_table: std.json.ArrayHashMap([]const u8), + c_macros: []const []const u8, + include_dirs: []const []const u8, + }; + + pub const Compile = struct { + /// Key in `BuildConfig.modules`. + root_module: []const u8, + + // may contain additional information in the future like `target` or `link_libc`. + }; + + /// Equivalent to `std.Build.AvailableOption` which is not accessible because it non-pub. + pub const AvailableOption = @FieldType(@FieldType(std.Build, "available_options_map").KV, "value"); +}; + +fn processConfigLeaky(allocator: std.mem.Allocator, io: std.Io, config_string: []const u8) !BuildConfig { + var config = try std.json.parseFromSliceLeaky(BuildConfig, allocator, config_string, .{}); + try canonicalizeModulesLeaky(allocator, io, &config); + return config; +} + +// Neovim canonicalizes the path it sends to ZLS, we need to do the same as ZLS will match against the config we generate. +// We canonicalize the following module paths: +// +// { +// "modules": { +// "": { +// "import_table": { +// "": "" +// }, +// }, +// }, +// } +fn canonicalizeModulesLeaky(allocator: std.mem.Allocator, io: std.Io, config: *BuildConfig) !void { + var old_modules = config.modules.map.iterator(); + var new_modules: std.StringArrayHashMapUnmanaged(BuildConfig.Module) = .empty; + try new_modules.ensureTotalCapacity(allocator, old_modules.len); + + while (old_modules.next()) |entry| { + const module_name = canonicalizePath(allocator, io, entry.key_ptr.*); + var module: BuildConfig.Module = entry.value_ptr.*; + for (module.import_table.map.values()) |*imported_module_path| { + imported_module_path.* = canonicalizePath(allocator, io, imported_module_path.*); + } + new_modules.put(allocator, module_name, entry.value_ptr.*) catch return; + } + + for (config.compilations) |*compile| { + compile.root_module = canonicalizePath(allocator, io, compile.root_module); + } + + config.modules.map = new_modules; +} + +fn canonicalizePath(allocator: std.mem.Allocator, io: std.Io, path: []const u8) []const u8 { + return std.Io.Dir.realPathFileAbsoluteAlloc(io, path, allocator) catch path; +} + +fn readBuildConfig( + allocator: std.mem.Allocator, + io: std.Io, + init: std.process.Init, + arg_path: []const u8, +) ![]u8 { + if (std.fs.path.isAbsolutePosix(arg_path)) { + return std.Io.Dir.cwd().readFileAlloc(io, arg_path, allocator, .unlimited); + } + + var r_ = try runfiles.Runfiles.create(.{ + .allocator = allocator, + .io = io, + .environ_map = init.environ_map, + .argv = init.minimal.args, + }) orelse return error.RunfilesNotFound; + + var path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const r = r_.withSourceRepo(bazel_builtin.current_repository); + + if (try r.rlocation(arg_path, &path_buf)) |config_path| { + return std.Io.Dir.cwd().readFileAlloc(io, config_path, allocator, .unlimited); + } + + const stripped = if (std.mem.startsWith(u8, arg_path, "./")) arg_path[2..] else arg_path; + if (try r.rlocation(stripped, &path_buf)) |config_path| { + return std.Io.Dir.cwd().readFileAlloc(io, config_path, allocator, .unlimited); + } + + // Convenience for testing. + const output = std.Io.Dir.cwd().readFileAlloc(io, arg_path, allocator, .unlimited) catch |err| switch (err) { + error.FileNotFound => null, + else => return err, + }; + if (output) |content| { + return content; + } + + return error.BuildConfigNotFound; +} + +pub fn main(init: std.process.Init) !void { + const arena = init.arena; + const io = init.io; + const args = try init.minimal.args.toSlice(arena.allocator()); + + const build_workspace_directory = init.environ_map.get("BUILD_WORKSPACE_DIRECTORY").?; + const execution_root = if (init.environ_map.get("BAZEL_EXECUTION_ROOT")) |value| + value + else blk: { + const output_base_result = try std.process.run(arena.allocator(), init.io, .{ + .argv = &.{ + "bazel", + "info", + "execution_root", + }, + .cwd = .{ .path = build_workspace_directory }, + }); + break :blk std.mem.trimEnd(u8, output_base_result.stdout, "\n"); + }; + + var output = try readBuildConfig( + arena.allocator(), + io, + init, + args[1], + ); + output = try std.mem.replaceOwned( + u8, + arena.allocator(), + output, + "__BUILD_WORKSPACE_DIRECTORY__", + build_workspace_directory, + ); + output = try std.mem.replaceOwned( + u8, + arena.allocator(), + output, + "__BAZEL_EXECUTION_ROOT__", + execution_root, + ); + + var stdout_buf: [4096]u8 = undefined; + var writer = std.Io.File.stdout().writer(io, &stdout_buf); + if (processConfigLeaky(arena.allocator(), io, output)) |config| { + try std.json.Stringify.value(config, .{}, &writer.interface); + } else |_| { + try writer.interface.writeAll(output); + } + try writer.flush(); +} diff --git a/zig/zls/workspace_printer_test.zig b/zig/zls/workspace_printer_test.zig new file mode 100644 index 00000000..71e81adb --- /dev/null +++ b/zig/zls/workspace_printer_test.zig @@ -0,0 +1,90 @@ +const std = @import("std"); + +const bazel_builtin = @import("bazel_builtin"); +const runfiles = @import("runfiles"); + +const workspace_printer = @import("workspace_printer.zig"); + +fn requireEnv(name: [:0]const u8) ![]const u8 { + return std.mem.span(std.c.getenv(name) orelse return error.MissingEnvironmentVariable); +} + +fn workspaceDirFromConfigPath(config_path: []const u8, package_name: []const u8) ![]const u8 { + var workspace_dir = std.fs.path.dirname(config_path) orelse + return error.InvalidWorkspacePath; + if (package_name.len == 0) { + return workspace_dir; + } + + var it = std.mem.splitScalar(u8, package_name, '/'); + while (it.next()) |_| { + workspace_dir = std.fs.path.dirname(workspace_dir) orelse + return error.InvalidWorkspacePath; + } + return workspace_dir; +} + +test "completion print_build_config emits valid json" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + var r_ = try runfiles.Runfiles.create(.{ + .allocator = allocator, + .io = std.testing.io, + .directory = if (std.c.getenv("RUNFILES_DIR")) |value| std.mem.span(value) else null, + .manifest = if (std.c.getenv("RUNFILES_MANIFEST_FILE")) |value| std.mem.span(value) else null, + }) orelse return error.RunfilesNotFound; + defer r_.deinit(allocator); + + const r = r_.withSourceRepo(bazel_builtin.current_repository); + + const printer_rpath = try requireEnv("COMPLETION_PRINTER_RLOCATION"); + const config_rpath = try requireEnv("COMPLETION_BUILD_CONFIG_RLOCATION"); + const package_name = try requireEnv("COMPLETION_PACKAGE"); + + const printer_path = try r.rlocationAlloc(allocator, printer_rpath) orelse + return error.RLocationNotFound; + const config_path = try r.rlocationAlloc(allocator, config_rpath) orelse + return error.RLocationNotFound; + const workspace_dir = try workspaceDirFromConfigPath(config_path, package_name); + const execution_root = workspace_dir; + + var child_env_map: std.process.Environ.Map = .init(std.testing.allocator); + defer child_env_map.deinit(); + try child_env_map.put("BUILD_WORKSPACE_DIRECTORY", workspace_dir); + try child_env_map.put("BAZEL_EXECUTION_ROOT", execution_root); + + const result = try std.process.run(std.testing.allocator, std.testing.io, .{ + .argv = &.{ printer_path, config_path }, + .environ_map = &child_env_map, + }); + defer std.testing.allocator.free(result.stdout); + defer std.testing.allocator.free(result.stderr); + + if (result.stderr.len > 0) { + std.log.warn("workspace_printer stderr: {s}", .{result.stderr}); + } + + try std.testing.expectEqual(@as(std.process.Child.Term, .{ .exited = 0 }), result.term); + + const config = try std.json.parseFromSliceLeaky(workspace_printer.BuildConfig, allocator, result.stdout, .{}); + try std.testing.expect(config.modules.map.count() > 0); + + var modules = config.modules.map.iterator(); + while (modules.next()) |entry| { + try std.testing.expect(entry.key_ptr.*.len > 0); + + const module = entry.value_ptr.*; + for (module.import_table.map.values()) |import_path| { + try std.testing.expect(import_path.len > 0); + } + for (module.include_dirs) |include_dir| { + try std.testing.expect(include_dir.len > 0); + } + } + + for (config.compilations) |compilation| { + try std.testing.expect(config.modules.map.contains(compilation.root_module)); + } +} diff --git a/zig/zls/zls_build_runner.zig b/zig/zls/zls_build_runner.zig new file mode 100644 index 00000000..190dec92 --- /dev/null +++ b/zig/zls/zls_build_runner.zig @@ -0,0 +1,14 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const build_workspace_directory = init.environ_map.get("BUILD_WORKSPACE_DIRECTORY").?; + var child = try std.process.spawn(init.io, .{ + .argv = &.{ + "bazel", + "run", + "__TARGET__", + }, + .cwd = .{ .path = build_workspace_directory }, + }); + _ = try child.wait(init.io); +} diff --git a/zig/zls/zls_completion.bzl b/zig/zls/zls_completion.bzl new file mode 100644 index 00000000..63993024 --- /dev/null +++ b/zig/zls/zls_completion.bzl @@ -0,0 +1,105 @@ +"""Implementation of the zls_completion macro.""" + +load("@aspect_bazel_lib//lib:utils.bzl", "utils") +load("@bazel_skylib//rules:expand_template.bzl", "expand_template") +load("//zig:defs.bzl", "zig_binary", "zig_test") +load("//zig/zls:zls_write_build_config.bzl", "zls_write_build_config") +load("//zig/zls:zls_write_runner_zig_src.bzl", "zls_write_runner_zig_src") + +def zls_completion(name, deps, testonly = False, **kwargs): + """Entry point for ZLS completion. + + Args: + name: The name of the completion target. + deps: The List of Zig modules to include for completion. + testonly: Whether generated targets should be test-only. + **kwargs: Additional keyword arguments passed to the `zig_binary` + """ + + # Generate the ZLS BuildConfig file. + # It contains the list of Zig packages alongside their main Zig file paths. + build_config = name + ".build_config" + build_config_file = name + ".build_config.json" + zls_write_build_config( + name = build_config, + out = build_config_file, + deps = deps, + testonly = testonly, + ) + + # Create a target that will be invoked by ZLS using our customer build_runner. + build_config_printer = "{}.print_build_config".format(name) + zig_binary( + name = build_config_printer, + main = Label("//zig/zls:workspace_printer.zig"), + data = [ + ":{}".format(build_config), + ":{}".format(build_config_file), + ], + args = [ + "$(rlocationpath :{})".format(build_config_file), + ], + deps = [ + Label("//zig/runfiles"), + ], + visibility = ["//visibility:private"], + testonly = testonly, + ) + + # Used to verify that the build config printer works as expected. + # Prevent regressions. + zig_test( + name = "{}_test".format(build_config_printer), + size = "small", + main = Label("//zig/zls:workspace_printer_test.zig"), + srcs = [Label("//zig/zls:workspace_printer.zig")], + data = deps + [ + ":{}".format(build_config), + ":{}".format(build_config_file), + ":{}".format(build_config_printer), + ], + env = { + "COMPLETION_BUILD_CONFIG_RLOCATION": "$(rlocationpath :{})".format(build_config_file), + "COMPLETION_PACKAGE": native.package_name(), + "COMPLETION_PRINTER_RLOCATION": "$(rlocationpath :{})".format(build_config_printer), + }, + deps = [ + Label("//zig/runfiles"), + ], + testonly = testonly, + ) + + # Generate the Zig build runner that will be used by ZLS to query the build config. + expand_template( + name = name + ".build_runner", + out = name + ".build_runner.zig", + substitutions = { + "__TARGET__": str(utils.to_label(build_config_printer)), + }, + template = Label("//zig/zls:zls_build_runner.zig"), + testonly = testonly, + ) + + # Generate the Zig source file for the ZLS runner binary which embeds the + # rlocationpath of all runtime dependencies of the ZLS runner binary. + zls_write_runner_zig_src( + name = name + ".runner", + out = name + ".runner.zig", + build_runner = ":" + name + ".build_runner.zig", + testonly = testonly, + ) + + zig_binary( + name = name, + main = name + ".runner", + data = [ + Label("//zig:resolved_toolchain"), + Label("//zig/zls:resolved_toolchain"), + name + ".build_runner", + ], + deps = [ + Label("//zig/runfiles"), + ], + testonly = testonly, + **kwargs + ) diff --git a/zig/zls/zls_runner.zig b/zig/zls/zls_runner.zig new file mode 100644 index 00000000..d62889c5 --- /dev/null +++ b/zig/zls/zls_runner.zig @@ -0,0 +1,100 @@ +/// Custom ZLS launcher. +/// +/// Sets up paths to ZLS dependencies with Bazel runfiles. +/// +/// This file is used as a template by `zls_write_runner_zig_src.bzl`. +const std = @import("std"); +const fs = std.fs; + +const bazel_builtin = @import("bazel_builtin"); +const runfiles = @import("runfiles"); + +fn getRandomFilename(io: std.Io, buf: []u8, extension: []const u8) ![]const u8 { + const now = std.Io.Clock.real.now(io); + return std.fmt.bufPrint(buf, "/tmp/{d}{s}", .{ now.nanoseconds, extension }) catch @panic("OOM"); +} + +const Config = struct { + /// Override the Zig library path. Will be automatically resolved using the 'zig_exe_path'. + zig_lib_path: ?[]const u8 = null, + + /// Specify the path to the Zig executable (not the directory). If unset, zig is looked up in `PATH`. e.g. `/path/to/zig-templeos-armless-1.0.0/zig`. + zig_exe_path: ?[]const u8 = null, + + /// Specify a custom build runner to resolve build system information. + build_runner_path: ?[]const u8 = null, + + /// Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`. + global_cache_path: ?[]const u8 = null, +}; + +pub fn main(init: std.process.Init) !void { + const arena = init.arena; + const io = init.io; + + var r_ = try runfiles.Runfiles.create(.{ .allocator = arena.allocator(), .io = io, .environ_map = init.environ_map, .argv = init.minimal.args }) orelse + return error.RunfilesNotFound; + const r = r_.withSourceRepo(bazel_builtin.current_repository); + + const zls_bin_rpath = "__ZLS_BIN_RPATH__"; + var zls_bin_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const zls_bin_path = try r.rlocation(zls_bin_rpath, &zls_bin_path_buf) orelse + return error.RLocationNotFound; + + const zig_exe_rpath = "__ZIG_EXE_RPATH__"; + var zig_exe_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const zig_exe_path = try r.rlocation(zig_exe_rpath, &zig_exe_path_buf) orelse + return error.RLocationNotFound; + + const zig_lib_path = "__ZIG_LIB_RPATH__"; + var zig_lib_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const zig_lib_computed_path = try r.rlocation(zig_lib_path, &zig_lib_path_buf) orelse + return error.RLocationNotFound; + + const zls_build_runner_rpath = "__ZLS_BUILD_RUNNER_RPATH__"; + var zls_build_runner_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const zls_build_runner_path = try r.rlocation(zls_build_runner_rpath, &zls_build_runner_path_buf) orelse + return error.RLocationNotFound; + + const global_cache_path = "__GLOBAL_CACHE_PATH__"; + + const config: Config = .{ + .zig_exe_path = zig_exe_path, + .zig_lib_path = zig_lib_computed_path, + .build_runner_path = zls_build_runner_path, + .global_cache_path = global_cache_path, + }; + + var buf: [std.Io.Dir.max_name_bytes]u8 = undefined; + const tmp_file_path = try getRandomFilename(io, &buf, ".json"); + + { + var tmp_file = try std.Io.Dir.createFileAbsolute(io, tmp_file_path, .{}); + defer tmp_file.close(io); + + var out_buf: [4096]u8 = undefined; + var tmp_file_writer = tmp_file.writer(io, &out_buf); + + const formatter = std.json.fmt(config, .{ .whitespace = .indent_2 }); + + try tmp_file_writer.interface.print("{f}", .{formatter}); + try tmp_file_writer.interface.flush(); + } + + const args = init.minimal.args.toSlice(arena.allocator()) catch return error.ArgsAllocFailed; + + const exec_args_len = args.len - 1 + 3; // Skip args[0] and add "zls" + --config-path + tmp_file_path + var exec_args = try arena.allocator().alloc([]const u8, exec_args_len); + + exec_args[0] = zls_bin_path; // "zls" + + @memcpy(exec_args[1 .. exec_args.len - 2], args[1..]); + @memcpy(exec_args[exec_args.len - 2 ..], &[_][]const u8{ + "--config-path", + tmp_file_path, + }); + var child = try std.process.spawn(io, .{ + .argv = exec_args, + }); + _ = try child.wait(io); +} diff --git a/zig/zls/zls_write_build_config.bzl b/zig/zls/zls_write_build_config.bzl new file mode 100644 index 00000000..6e18f0f4 --- /dev/null +++ b/zig/zls/zls_write_build_config.bzl @@ -0,0 +1,173 @@ +"""Generates the ZLS build config file.""" + +load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//zig/private:cc_helper.bzl", "need_translate_c") +load("//zig/private/common:translate_c.bzl", "zig_translate_c") +load("//zig/private/common:zig_cache.bzl", "zig_cache_output") +load("//zig/private/common:zig_lib_dir.bzl", "zig_lib_dir") +load("//zig/private/providers:zig_module_info.bzl", "ZigModuleInfo", "zig_module_info") +load( + "//zig/private/providers:zig_target_info.bzl", + "zig_target_platform", +) + +def _is_zig_target(kind): + if not kind.startswith("zig_"): + return False + for end in ("_binary", "_static_library", "_shared_library", "_test"): + if kind.endswith(end): + return True + return False + +def _zls_construct_zig_module_info_impl(target, ctx): + """Aspect that constructs ZigModuleInfo for zig_binary and zig_library rules.""" + if ZigModuleInfo in target: + return [] + + if _is_zig_target(ctx.rule.kind) == False: + return [] + + cdeps = [] + zdeps = [] + for dep in ctx.rule.attr.deps: + if ZigModuleInfo in dep: + zdeps.append(dep[ZigModuleInfo]) + elif CcInfo in dep: + cdeps.append(dep[CcInfo]) + + root_module = None + if ctx.rule.attr.main: + root_module = zig_module_info( + name = ctx.rule.attr.name, + canonical_name = target.label.name, + main = ctx.rule.file.main, + srcs = ctx.rule.files.srcs, + extra_srcs = ctx.rule.files.extra_srcs, + deps = zdeps, # [bazel_builtin_module(ctx)], + cdeps = cdeps, + zigopts = [], + ) + else: + root_module = zdeps[0] + + return [ + root_module, + ] + +zls_construct_zig_module_info = aspect( + implementation = _zls_construct_zig_module_info_impl, +) + +def format_main_file(main): + prefix = "__BUILD_WORKSPACE_DIRECTORY__/" + if (main.startswith("bazel-out/") or main.startswith("external/")): + prefix = "__BAZEL_EXECUTION_ROOT__/" + return prefix + main + +def _zls_write_build_config_impl(ctx): + zigtoolchaininfo = ctx.toolchains["//zig:toolchain_type"].zigtoolchaininfo + zigtargetinfo = ctx.toolchains["//zig/target:toolchain_type"].zigtargetinfo + translate_c_toolchain = ctx.toolchains["//zig/translate-c:toolchain_type"] + translatectoolchaininfo = translate_c_toolchain.translatectoolchaininfo if translate_c_toolchain else None + + c_module_contexts = [] + c_module_inputs = [] + cc_info = cc_common.merge_cc_infos(direct_cc_infos = [dep[ZigModuleInfo].cc_info for dep in ctx.attr.deps if dep[ZigModuleInfo].cc_info]) + if need_translate_c(cc_info): + global_args = ctx.actions.args() + global_args.use_param_file("@%s") + + zig_target_platform( + target = zigtargetinfo, + args = global_args, + ) + + zig_lib_dir( + zigtoolchaininfo = zigtoolchaininfo, + args = global_args, + ) + + zig_cache_output( + zigtoolchaininfo = zigtoolchaininfo, + args = global_args, + ) + + c_module = zig_translate_c( + ctx = ctx, + name = "c", + canonical_name = "c", + zigtoolchaininfo = zigtoolchaininfo, + global_args = global_args, + cc_infos = [cc_info], + translatectoolchaininfo = translatectoolchaininfo, + ) + + c_module_contexts = [depset( + direct = [c_module.module_context], + transitive = [c_module.transitive_module_contexts], + )] + c_module_inputs = [c_module.transitive_inputs] + + contexts = depset( + direct = [dep[ZigModuleInfo].module_context for dep in ctx.attr.deps], + transitive = [dep[ZigModuleInfo].transitive_module_contexts for dep in ctx.attr.deps] + c_module_contexts, + ) + + modules = { + mod.canonical_name: mod + for mod in contexts.to_list() + } + + output = { + "dependencies": {}, + "modules": { + format_main_file(mod.main): { + "import_table": { + dep.name: format_main_file(modules.get(dep.canonical_name, "").main) + for dep in mod.dependency_mappings + }, + "c_macros": [], + "include_dirs": [], + } + for mod in modules.values() + }, + "compilations": [], + "top_level_steps": [], + "available_options": {}, + } + + config = ctx.outputs.out + ctx.actions.write( + output = config, + content = json.encode(output), + ) + + return [ + DefaultInfo( + files = depset(direct = [config]), + # Not really runfiles but we need to pull the output of translate_c... + runfiles = ctx.runfiles(transitive_files = depset(transitive = c_module_inputs)), + ), + ] + +zls_write_build_config = rule( + implementation = _zls_write_build_config_impl, + attrs = { + "deps": attr.label_list( + providers = [ZigModuleInfo], + mandatory = True, + aspects = [zls_construct_zig_module_info], + ), + "out": attr.output( + mandatory = True, + ), + }, + toolchains = [ + "//zig:toolchain_type", + "//zig/target:toolchain_type", + config_common.toolchain_type("//zig/translate-c:toolchain_type", mandatory = False), + ] + use_cc_toolchain(mandatory = False), + fragments = ["cpp", "apple"], +) diff --git a/zig/zls/zls_write_runner_zig_src.bzl b/zig/zls/zls_write_runner_zig_src.bzl new file mode 100644 index 00000000..2207ea23 --- /dev/null +++ b/zig/zls/zls_write_runner_zig_src.bzl @@ -0,0 +1,45 @@ +"""Generates the ZLS runner source file.""" + +load("@aspect_bazel_lib//lib:paths.bzl", "to_rlocation_path") + +def _zls_write_runner_zig_src_impl(ctx): + zigtoolchaininfo = ctx.toolchains["//zig:toolchain_type"].zigtoolchaininfo + zlstoolchaininfo = ctx.toolchains["//zig/zls:toolchain_type"].zlstoolchaininfo + + zls_runner = ctx.outputs.out + ctx.actions.expand_template( + output = zls_runner, + template = ctx.file._runner_tpl, + substitutions = { + "__GLOBAL_CACHE_PATH__": zigtoolchaininfo.zig_cache, + "__ZIG_EXE_RPATH__": zigtoolchaininfo.zig_exe_rpath, + "__ZIG_LIB_RPATH__": zigtoolchaininfo.zig_lib_rpath, + "__ZLS_BIN_RPATH__": to_rlocation_path(ctx, zlstoolchaininfo.bin), + "__ZLS_BUILD_RUNNER_RPATH__": to_rlocation_path(ctx, ctx.file.build_runner), + }, + ) + + return [ + DefaultInfo(files = depset([zls_runner])), + ] + +zls_write_runner_zig_src = rule( + implementation = _zls_write_runner_zig_src_impl, + attrs = { + "out": attr.output( + mandatory = True, + ), + "build_runner": attr.label( + allow_single_file = True, + mandatory = True, + ), + "_runner_tpl": attr.label( + default = Label("//zig/zls:zls_runner.zig"), + allow_single_file = True, + ), + }, + toolchains = [ + "//zig:toolchain_type", + "//zig/zls:toolchain_type", + ], +) From 929e767688f17460e88fa91a984f0a03ae462056 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 22 Apr 2026 15:36:06 +0200 Subject: [PATCH 02/13] zig_build: propagate shared libraries in the runfiles --- zig/private/common/zig_build.bzl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zig/private/common/zig_build.bzl b/zig/private/common/zig_build.bzl index 37cafdf6..294fb14e 100644 --- a/zig/private/common/zig_build.bzl +++ b/zig/private/common/zig_build.bzl @@ -4,6 +4,7 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load("@build_bazel_rules_android//:cc_common_link.bzl", "cc_common_link") load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/common:cc_helper.bzl", "cc_helper") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") load("//zig/private:cc_helper.bzl", "find_cc_toolchain", "need_translate_c") load( @@ -752,6 +753,9 @@ def zig_build_impl(ctx, *, kind): else: fail("Unknown rule kind '{}'.".format(kind)) + if root_module.cc_info: + transitive_data.append(depset(cc_helper.get_dynamic_libraries_for_runtime(root_module.cc_info.linking_context, True))) + providers.extend([ DefaultInfo( executable = bin_output if bin_output_is_executable else None, From ea25033aa561c3ce0beae2864484aa1f8f080e19 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 09:19:43 +0000 Subject: [PATCH 03/13] Missing dev_dependecy --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index e360a100..dd092971 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -55,7 +55,7 @@ zls_dev.toolchain( ) use_repo(zls_dev, "zls_toolchains") -register_toolchains("@zls_toolchains//:all") +register_toolchains("@zls_toolchains//:all", dev_dependency = True) bazel_dep(name = "toolchains_buildbuddy", version = "0.0.4", dev_dependency = True) From 60dbbc03f0cea4894ffe896b268a017ad8cdd7d0 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 09:39:20 +0000 Subject: [PATCH 04/13] Fix docs --- MODULE.bazel | 5 ++++- zig/private/BUILD.bazel | 3 +++ zig/private/common/BUILD.bazel | 2 ++ zig/private/providers/BUILD.bazel | 1 + zig/zls/BUILD.bazel | 13 +++++++------ zig/zls/private/BUILD.bazel | 2 +- zig/zls/private/repo/BUILD.bazel | 13 +++++++------ zig/zls/tests/BUILD.bazel | 1 - 8 files changed, 25 insertions(+), 15 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index dd092971..7a697f04 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -55,7 +55,10 @@ zls_dev.toolchain( ) use_repo(zls_dev, "zls_toolchains") -register_toolchains("@zls_toolchains//:all", dev_dependency = True) +register_toolchains( + "@zls_toolchains//:all", + dev_dependency = True, +) bazel_dep(name = "toolchains_buildbuddy", version = "0.0.4", dev_dependency = True) diff --git a/zig/private/BUILD.bazel b/zig/private/BUILD.bazel index a7e5955c..786a862f 100644 --- a/zig/private/BUILD.bazel +++ b/zig/private/BUILD.bazel @@ -121,6 +121,7 @@ bzl_library( visibility = ["//zig:__subpackages__"], deps = [ "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) @@ -130,6 +131,7 @@ bzl_library( visibility = ["//zig:__subpackages__"], deps = [ "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) @@ -140,6 +142,7 @@ bzl_library( deps = [ "//zig/private/providers:zig_toolchain_info", "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) diff --git a/zig/private/common/BUILD.bazel b/zig/private/common/BUILD.bazel index 6d2f3632..9437d746 100644 --- a/zig/private/common/BUILD.bazel +++ b/zig/private/common/BUILD.bazel @@ -42,6 +42,7 @@ bzl_library( "@build_bazel_rules_android//:cc_common_link.bzl", "@rules_cc//cc:find_cc_toolchain_bzl", "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) @@ -103,6 +104,7 @@ bzl_library( "@apple_support//lib:apple_support", "@rules_cc//cc:action_names.bzl", "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) diff --git a/zig/private/providers/BUILD.bazel b/zig/private/providers/BUILD.bazel index 25c33b93..22076822 100644 --- a/zig/private/providers/BUILD.bazel +++ b/zig/private/providers/BUILD.bazel @@ -7,6 +7,7 @@ bzl_library( deps = [ "//zig/private:cc_helper", "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) diff --git a/zig/zls/BUILD.bazel b/zig/zls/BUILD.bazel index b57244fa..c601763c 100644 --- a/zig/zls/BUILD.bazel +++ b/zig/zls/BUILD.bazel @@ -39,12 +39,6 @@ bzl_library( deps = ["//zig/zls/private/bzlmod:zls"], ) -bzl_library( - name = "toolchain", - srcs = ["toolchain.bzl"], - visibility = ["//visibility:public"], -) - bzl_library( name = "zls_completion", srcs = ["zls_completion.bzl"], @@ -72,6 +66,7 @@ bzl_library( "//zig/translate-c:toolchain", "@rules_cc//cc:find_cc_toolchain_bzl", "@rules_cc//cc/common", + "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) @@ -82,6 +77,12 @@ bzl_library( deps = ["@aspect_bazel_lib//lib:paths"], ) +bzl_library( + name = "toolchain", + srcs = ["toolchain.bzl"], + visibility = ["//visibility:public"], +) + # Execute `bazel run //util:update_filegroups` to update this target. filegroup( name = "all_files", diff --git a/zig/zls/private/BUILD.bazel b/zig/zls/private/BUILD.bazel index 701df5ab..13737cb6 100644 --- a/zig/zls/private/BUILD.bazel +++ b/zig/zls/private/BUILD.bazel @@ -8,7 +8,7 @@ exports_files( bzl_library( name = "resolved_toolchain", srcs = ["resolved_toolchain.bzl"], - visibility = ["//visibility:public"], + visibility = ["//zig/zls:__subpackages__"], ) # Execute `bazel run //util:update_filegroups` to update this target. diff --git a/zig/zls/private/repo/BUILD.bazel b/zig/zls/private/repo/BUILD.bazel index 47a5e331..612b4ec3 100644 --- a/zig/zls/private/repo/BUILD.bazel +++ b/zig/zls/private/repo/BUILD.bazel @@ -1,15 +1,16 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") bzl_library( - name = "toolchains_repo", - srcs = ["toolchains_repo.bzl"], - visibility = ["//visibility:public"], + name = "zls_repository", + srcs = ["zls_repository.bzl"], + visibility = ["//zig/zls:__subpackages__"], + deps = ["@bazel_tools//tools/build_defs/repo:utils.bzl"], ) bzl_library( - name = "zls_repository", - srcs = ["zls_repository.bzl"], - visibility = ["//visibility:public"], + name = "toolchains_repo", + srcs = ["toolchains_repo.bzl"], + visibility = ["//zig/zls:__subpackages__"], ) # Execute `bazel run //util:update_filegroups` to update this target. diff --git a/zig/zls/tests/BUILD.bazel b/zig/zls/tests/BUILD.bazel index 20f150fb..ea3e0c88 100644 --- a/zig/zls/tests/BUILD.bazel +++ b/zig/zls/tests/BUILD.bazel @@ -5,7 +5,6 @@ bzlmod_zls_test_suite(name = "bzlmod_zls_test") bzl_library( name = "bzlmod_zls_test_bzl", - srcs = ["bzlmod_zls_test.bzl"], visibility = ["//visibility:private"], deps = [ "//zig/zls/private/bzlmod:zls", From f5f46f920b981329ff6566f67d329b8ff101ebce Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 11:05:57 +0000 Subject: [PATCH 05/13] fix --- zig/zls/zls_write_runner_zig_src.bzl | 29 +++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/zig/zls/zls_write_runner_zig_src.bzl b/zig/zls/zls_write_runner_zig_src.bzl index 2207ea23..b34de3d8 100644 --- a/zig/zls/zls_write_runner_zig_src.bzl +++ b/zig/zls/zls_write_runner_zig_src.bzl @@ -2,20 +2,35 @@ load("@aspect_bazel_lib//lib:paths.bzl", "to_rlocation_path") +def zig_toolchain_executable_rpath(ctx, zigtoolchaininfo): + if zigtoolchaininfo.zig_exe.file != None: + return to_rlocation_path(ctx, zigtoolchaininfo.zig_exe.file) + return zigtoolchaininfo.zig_exe.path + +def zig_toolchain_lib_rpath(ctx, zigtoolchaininfo): + if zigtoolchaininfo.zig_lib.file != None: + return to_rlocation_path(ctx, zigtoolchaininfo.zig_lib.file) + return zigtoolchaininfo.zig_lib.path + def _zls_write_runner_zig_src_impl(ctx): - zigtoolchaininfo = ctx.toolchains["//zig:toolchain_type"].zigtoolchaininfo - zlstoolchaininfo = ctx.toolchains["//zig/zls:toolchain_type"].zlstoolchaininfo + zigtoolchaininfo = ctx.toolchains["@rules_zig//zig:toolchain_type"].zigtoolchaininfo + zlstoolchaininfo = ctx.toolchains["@rules_zig//zig/zls:toolchain_type"].zlstoolchaininfo + + zig_exe_rpath = zig_toolchain_executable_rpath(ctx, zigtoolchaininfo) + zig_lib_rpath = zig_toolchain_lib_rpath(ctx, zigtoolchaininfo) + + prefix_suffix = "@" + "@" zls_runner = ctx.outputs.out ctx.actions.expand_template( output = zls_runner, template = ctx.file._runner_tpl, substitutions = { - "__GLOBAL_CACHE_PATH__": zigtoolchaininfo.zig_cache, - "__ZIG_EXE_RPATH__": zigtoolchaininfo.zig_exe_rpath, - "__ZIG_LIB_RPATH__": zigtoolchaininfo.zig_lib_rpath, - "__ZLS_BIN_RPATH__": to_rlocation_path(ctx, zlstoolchaininfo.bin), - "__ZLS_BUILD_RUNNER_RPATH__": to_rlocation_path(ctx, ctx.file.build_runner), + "{prefix_suffix}__ZIG_EXE_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): zig_exe_rpath, + "{prefix_suffix}__ZIG_LIB_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): zig_lib_rpath, + "{prefix_suffix}__ZLS_BIN_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): to_rlocation_path(ctx, zlstoolchaininfo.bin), + "{prefix_suffix}__ZLS_BUILD_RUNNER_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): to_rlocation_path(ctx, ctx.file.build_runner), + "{prefix_suffix}__GLOBAL_CACHE_PATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): zigtoolchaininfo.zig_cache, }, ) From a94f5064899e1e87ccf94645815b2d1e0d653ca6 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 11:13:31 +0000 Subject: [PATCH 06/13] Add missing explicit -lc --- zig/zls/zls_completion.bzl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zig/zls/zls_completion.bzl b/zig/zls/zls_completion.bzl index 63993024..037e811c 100644 --- a/zig/zls/zls_completion.bzl +++ b/zig/zls/zls_completion.bzl @@ -58,6 +58,9 @@ def zls_completion(name, deps, testonly = False, **kwargs): ":{}".format(build_config_file), ":{}".format(build_config_printer), ], + zigopts = [ + "-lc", + ], env = { "COMPLETION_BUILD_CONFIG_RLOCATION": "$(rlocationpath :{})".format(build_config_file), "COMPLETION_PACKAGE": native.package_name(), From 0a83874bc80d83c6ca8ef6202fd69206de8aadc9 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 11:35:36 +0000 Subject: [PATCH 07/13] test: add zls build config golden --- zig/tests/zls-completion/BUILD.bazel | 89 +++++++++++++++++- zig/tests/zls-completion/app.zig | 12 +++ zig/tests/zls-completion/binary_with_main.zig | 5 + .../golden_build_config.expected.json | 92 +++++++++++++++++++ zig/tests/zls-completion/leaf.zig | 1 + zig/tests/zls-completion/mid.zig | 5 + zig/tests/zls-completion/named.zig | 1 + zig/tests/zls-completion/nested/BUILD.bazel | 8 ++ zig/tests/zls-completion/nested/nested.zig | 1 + .../normalize_zls_build_config.py | 37 ++++++++ 10 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 zig/tests/zls-completion/app.zig create mode 100644 zig/tests/zls-completion/binary_with_main.zig create mode 100644 zig/tests/zls-completion/golden_build_config.expected.json create mode 100644 zig/tests/zls-completion/leaf.zig create mode 100644 zig/tests/zls-completion/mid.zig create mode 100644 zig/tests/zls-completion/named.zig create mode 100644 zig/tests/zls-completion/nested/BUILD.bazel create mode 100644 zig/tests/zls-completion/nested/nested.zig create mode 100644 zig/tests/zls-completion/normalize_zls_build_config.py diff --git a/zig/tests/zls-completion/BUILD.bazel b/zig/tests/zls-completion/BUILD.bazel index 968ac5e7..d1d82738 100644 --- a/zig/tests/zls-completion/BUILD.bazel +++ b/zig/tests/zls-completion/BUILD.bazel @@ -1,5 +1,9 @@ -load("//zig:defs.bzl", "zig_library") +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("@rules_python//python:defs.bzl", "py_binary") +load("//zig:defs.bzl", "zig_binary", "zig_library") load("//zig/zls:defs.bzl", "zls_completion") +load("//zig/zls:zls_write_build_config.bzl", "zls_write_build_config") zig_library( name = "lib", @@ -11,3 +15,86 @@ zls_completion( testonly = True, deps = [":lib"], ) + +zig_library( + name = "leaf", + import_name = "custom/leaf", + main = "leaf.zig", +) + +zig_library( + name = "named", + import_name = "custom/named", + main = "named.zig", +) + +zig_library( + name = "mid", + main = "mid.zig", + deps = [":leaf"], +) + +write_file( + name = "generated_src", + out = "generated.zig", + content = ["pub const generated_value = 7;"], +) + +zig_library( + name = "generated", + main = ":generated_src", +) + +zig_library( + name = "app", + main = "app.zig", + deps = [ + ":generated", + ":mid", + ":named", + "//zig/tests/zls-completion/nested", + ], +) + +zig_binary( + name = "binary_with_main", + main = "binary_with_main.zig", + deps = [":app"], +) + +zig_binary( + name = "binary_from_root_module", + deps = [":app"], +) + +zls_write_build_config( + name = "golden_build_config", + testonly = True, + out = "golden_build_config.json", + deps = [ + ":app", + ":binary_from_root_module", + ":binary_with_main", + ], +) + +py_binary( + name = "normalize_zls_build_config", + srcs = ["normalize_zls_build_config.py"], +) + +genrule( + name = "golden_build_config_normalized", + testonly = True, + srcs = [":golden_build_config"], + outs = ["golden_build_config.normalized.json"], + cmd = "$(execpath :normalize_zls_build_config) $(location :golden_build_config) $@", + tools = [":normalize_zls_build_config"], +) + +diff_test( + name = "golden_build_config_test", + size = "small", + file1 = "golden_build_config.expected.json", + file2 = ":golden_build_config_normalized", +) diff --git a/zig/tests/zls-completion/app.zig b/zig/tests/zls-completion/app.zig new file mode 100644 index 00000000..835a42dc --- /dev/null +++ b/zig/tests/zls-completion/app.zig @@ -0,0 +1,12 @@ +const generated = @import("generated"); +const mid = @import("mid"); +const named = @import("custom/named"); +const nested = @import("nested/module"); + +pub fn value() i32 { + return generated.generated_value + mid.value() + @as(i32, @intFromBool(named.enabled)) + nested.value; +} + +pub fn main() void { + _ = value(); +} diff --git a/zig/tests/zls-completion/binary_with_main.zig b/zig/tests/zls-completion/binary_with_main.zig new file mode 100644 index 00000000..164e7231 --- /dev/null +++ b/zig/tests/zls-completion/binary_with_main.zig @@ -0,0 +1,5 @@ +const app = @import("app"); + +pub fn main() void { + _ = app.value(); +} diff --git a/zig/tests/zls-completion/golden_build_config.expected.json b/zig/tests/zls-completion/golden_build_config.expected.json new file mode 100644 index 00000000..1f5643c6 --- /dev/null +++ b/zig/tests/zls-completion/golden_build_config.expected.json @@ -0,0 +1,92 @@ +{ + "available_options": {}, + "compilations": [], + "dependencies": {}, + "modules": { + "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Capp.zig": { + "c_macros": [], + "import_table": {}, + "include_dirs": [] + }, + "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cgenerated.zig": { + "c_macros": [], + "import_table": {}, + "include_dirs": [] + }, + "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cleaf.zig": { + "c_macros": [], + "import_table": {}, + "include_dirs": [] + }, + "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cmid.zig": { + "c_macros": [], + "import_table": {}, + "include_dirs": [] + }, + "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cnamed.zig": { + "c_macros": [], + "import_table": {}, + "include_dirs": [] + }, + "__BAZEL_BIN__/zig/tests/zls-completion/generated.zig": { + "c_macros": [], + "import_table": { + "bazel_builtin": "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cgenerated.zig" + }, + "include_dirs": [] + }, + "__BAZEL_BIN__/zig/tests/zls-completion/nested/bazel_builtin_zig_Stests_Szls-completion_Snested_Cnested.zig": { + "c_macros": [], + "import_table": {}, + "include_dirs": [] + }, + "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/app.zig": { + "c_macros": [], + "import_table": { + "bazel_builtin": "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Capp.zig", + "custom/named": "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/named.zig", + "generated": "__BAZEL_BIN__/zig/tests/zls-completion/generated.zig", + "mid": "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/mid.zig", + "nested/module": "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/nested/nested.zig" + }, + "include_dirs": [] + }, + "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/binary_with_main.zig": { + "c_macros": [], + "import_table": { + "app": "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/app.zig" + }, + "include_dirs": [] + }, + "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/leaf.zig": { + "c_macros": [], + "import_table": { + "bazel_builtin": "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cleaf.zig" + }, + "include_dirs": [] + }, + "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/mid.zig": { + "c_macros": [], + "import_table": { + "bazel_builtin": "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cmid.zig", + "custom/leaf": "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/leaf.zig" + }, + "include_dirs": [] + }, + "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/named.zig": { + "c_macros": [], + "import_table": { + "bazel_builtin": "__BAZEL_BIN__/zig/tests/zls-completion/bazel_builtin_zig_Stests_Szls-completion_Cnamed.zig" + }, + "include_dirs": [] + }, + "__BUILD_WORKSPACE_DIRECTORY__/zig/tests/zls-completion/nested/nested.zig": { + "c_macros": [], + "import_table": { + "bazel_builtin": "__BAZEL_BIN__/zig/tests/zls-completion/nested/bazel_builtin_zig_Stests_Szls-completion_Snested_Cnested.zig" + }, + "include_dirs": [] + } + }, + "top_level_steps": [] +} diff --git a/zig/tests/zls-completion/leaf.zig b/zig/tests/zls-completion/leaf.zig new file mode 100644 index 00000000..643df7c7 --- /dev/null +++ b/zig/tests/zls-completion/leaf.zig @@ -0,0 +1 @@ +pub const value = 29; diff --git a/zig/tests/zls-completion/mid.zig b/zig/tests/zls-completion/mid.zig new file mode 100644 index 00000000..83de07e9 --- /dev/null +++ b/zig/tests/zls-completion/mid.zig @@ -0,0 +1,5 @@ +const leaf = @import("custom/leaf"); + +pub fn value() i32 { + return leaf.value + 5; +} diff --git a/zig/tests/zls-completion/named.zig b/zig/tests/zls-completion/named.zig new file mode 100644 index 00000000..7ee662d1 --- /dev/null +++ b/zig/tests/zls-completion/named.zig @@ -0,0 +1 @@ +pub const enabled = true; diff --git a/zig/tests/zls-completion/nested/BUILD.bazel b/zig/tests/zls-completion/nested/BUILD.bazel new file mode 100644 index 00000000..9f54ef76 --- /dev/null +++ b/zig/tests/zls-completion/nested/BUILD.bazel @@ -0,0 +1,8 @@ +load("//zig:defs.bzl", "zig_library") + +zig_library( + name = "nested", + import_name = "nested/module", + main = "nested.zig", + visibility = ["//visibility:public"], +) diff --git a/zig/tests/zls-completion/nested/nested.zig b/zig/tests/zls-completion/nested/nested.zig new file mode 100644 index 00000000..70e1f9d8 --- /dev/null +++ b/zig/tests/zls-completion/nested/nested.zig @@ -0,0 +1 @@ +pub const value = 1; diff --git a/zig/tests/zls-completion/normalize_zls_build_config.py b/zig/tests/zls-completion/normalize_zls_build_config.py new file mode 100644 index 00000000..01259038 --- /dev/null +++ b/zig/tests/zls-completion/normalize_zls_build_config.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import json +import re +import sys + + +BAZEL_BIN_RE = re.compile(r"__BAZEL_EXECUTION_ROOT__/bazel-out/[^/]+/bin/") + + +def normalize(value): + if isinstance(value, dict): + return {normalize(key): normalize(item) for key, item in value.items()} + if isinstance(value, list): + return [normalize(item) for item in value] + if isinstance(value, str): + return BAZEL_BIN_RE.sub("__BAZEL_BIN__/", value) + return value + + +def main(argv): + if len(argv) != 3: + print("usage: normalize_zls_build_config.py ", file=sys.stderr) + return 2 + + with open(argv[1], encoding="utf-8") as input_file: + config = json.load(input_file) + + with open(argv[2], "w", encoding="utf-8") as output_file: + json.dump(normalize(config), output_file, indent=2, sort_keys=True) + output_file.write("\n") + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) From 13c6f74e6f64af02e98e49669268e52c571029c6 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 11:54:00 +0000 Subject: [PATCH 08/13] zls: map zig 0.15.2 toolchain --- MODULE.bazel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 7a697f04..4f477878 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -53,6 +53,10 @@ zls_dev.toolchain( zig_version = "0.16.0", zls_version = "0.16.0", ) +zls_dev.toolchain( + zig_version = "0.15.2", + zls_version = "0.15.1", +) use_repo(zls_dev, "zls_toolchains") register_toolchains( From a6ef9957da6deca6caf4a3177311a6a1c2c1787c Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Mon, 18 May 2026 12:09:05 +0000 Subject: [PATCH 09/13] zls: support zig 0.15 runners --- zig/zls/workspace_printer.zig | 160 +++++++++++++++++++++++++-- zig/zls/workspace_printer_test.zig | 53 +++++++-- zig/zls/zls_build_runner.zig | 23 +++- zig/zls/zls_runner.zig | 77 ++++++++++++- zig/zls/zls_write_runner_zig_src.bzl | 12 +- 5 files changed, 292 insertions(+), 33 deletions(-) diff --git a/zig/zls/workspace_printer.zig b/zig/zls/workspace_printer.zig index 0bb6818b..66c10d42 100644 --- a/zig/zls/workspace_printer.zig +++ b/zig/zls/workspace_printer.zig @@ -1,8 +1,11 @@ +const builtin = @import("builtin"); const std = @import("std"); const bazel_builtin = @import("bazel_builtin"); const runfiles = @import("runfiles"); +const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + /// https://github.com/zigtools/zls/blob/master/src/build_runner/shared.zig#L6 (2026-03-10) pub const BuildConfig = struct { /// The `dependencies` in `build.zig.zon`. @@ -33,9 +36,15 @@ pub const BuildConfig = struct { pub const AvailableOption = @FieldType(@FieldType(std.Build, "available_options_map").KV, "value"); }; -fn processConfigLeaky(allocator: std.mem.Allocator, io: std.Io, config_string: []const u8) !BuildConfig { +fn processConfigLeaky_pre_016(allocator: std.mem.Allocator, config_string: []const u8) !BuildConfig { + var config = try std.json.parseFromSliceLeaky(BuildConfig, allocator, config_string, .{}); + try canonicalizeModulesLeaky_pre_016(allocator, &config); + return config; +} + +fn processConfigLeaky_016(allocator: std.mem.Allocator, io: std.Io, config_string: []const u8) !BuildConfig { var config = try std.json.parseFromSliceLeaky(BuildConfig, allocator, config_string, .{}); - try canonicalizeModulesLeaky(allocator, io, &config); + try canonicalizeModulesLeaky_016(allocator, io, &config); return config; } @@ -51,32 +60,103 @@ fn processConfigLeaky(allocator: std.mem.Allocator, io: std.Io, config_string: [ // }, // }, // } -fn canonicalizeModulesLeaky(allocator: std.mem.Allocator, io: std.Io, config: *BuildConfig) !void { +fn canonicalizeModulesLeaky_pre_016(allocator: std.mem.Allocator, config: *BuildConfig) !void { var old_modules = config.modules.map.iterator(); var new_modules: std.StringArrayHashMapUnmanaged(BuildConfig.Module) = .empty; try new_modules.ensureTotalCapacity(allocator, old_modules.len); while (old_modules.next()) |entry| { - const module_name = canonicalizePath(allocator, io, entry.key_ptr.*); + const module_name = canonicalizePath_pre_016(allocator, entry.key_ptr.*); var module: BuildConfig.Module = entry.value_ptr.*; for (module.import_table.map.values()) |*imported_module_path| { - imported_module_path.* = canonicalizePath(allocator, io, imported_module_path.*); + imported_module_path.* = canonicalizePath_pre_016(allocator, imported_module_path.*); } - new_modules.put(allocator, module_name, entry.value_ptr.*) catch return; + try new_modules.put(allocator, module_name, module); } for (config.compilations) |*compile| { - compile.root_module = canonicalizePath(allocator, io, compile.root_module); + compile.root_module = canonicalizePath_pre_016(allocator, compile.root_module); } config.modules.map = new_modules; } -fn canonicalizePath(allocator: std.mem.Allocator, io: std.Io, path: []const u8) []const u8 { +fn canonicalizeModulesLeaky_016(allocator: std.mem.Allocator, io: std.Io, config: *BuildConfig) !void { + var old_modules = config.modules.map.iterator(); + var new_modules: std.StringArrayHashMapUnmanaged(BuildConfig.Module) = .empty; + try new_modules.ensureTotalCapacity(allocator, old_modules.len); + + while (old_modules.next()) |entry| { + const module_name = canonicalizePath_016(allocator, io, entry.key_ptr.*); + var module: BuildConfig.Module = entry.value_ptr.*; + for (module.import_table.map.values()) |*imported_module_path| { + imported_module_path.* = canonicalizePath_016(allocator, io, imported_module_path.*); + } + try new_modules.put(allocator, module_name, module); + } + + for (config.compilations) |*compile| { + compile.root_module = canonicalizePath_016(allocator, io, compile.root_module); + } + + config.modules.map = new_modules; +} + +fn canonicalizePath_pre_016(allocator: std.mem.Allocator, path: []const u8) []const u8 { + return std.fs.realpathAlloc(allocator, path) catch path; +} + +fn canonicalizePath_016(allocator: std.mem.Allocator, io: std.Io, path: []const u8) []const u8 { return std.Io.Dir.realPathFileAbsoluteAlloc(io, path, allocator) catch path; } -fn readBuildConfig( +fn readFileAlloc_pre_016(allocator: std.mem.Allocator, path: []const u8) ![]u8 { + const max_bytes = std.math.maxInt(usize); + if (std.fs.path.isAbsolutePosix(path)) { + const file = try std.fs.openFileAbsolute(path, .{}); + defer file.close(); + return try file.readToEndAlloc(allocator, max_bytes); + } + return try std.fs.cwd().readFileAlloc(allocator, path, max_bytes); +} + +fn readBuildConfig_pre_016( + allocator: std.mem.Allocator, + arg_path: []const u8, +) ![]u8 { + if (std.fs.path.isAbsolutePosix(arg_path)) { + return readFileAlloc_pre_016(allocator, arg_path); + } + + var r_ = try runfiles.Runfiles.create(.{ + .allocator = allocator, + }) orelse return error.RunfilesNotFound; + defer r_.deinit(allocator); + + const r = r_.withSourceRepo(bazel_builtin.current_repository); + + if (try r.rlocationAlloc(allocator, arg_path)) |config_path| { + return readFileAlloc_pre_016(allocator, config_path); + } + + const stripped = if (std.mem.startsWith(u8, arg_path, "./")) arg_path[2..] else arg_path; + if (try r.rlocationAlloc(allocator, stripped)) |config_path| { + return readFileAlloc_pre_016(allocator, config_path); + } + + // Convenience for testing. + const output = readFileAlloc_pre_016(allocator, arg_path) catch |err| switch (err) { + error.FileNotFound => null, + else => return err, + }; + if (output) |content| { + return content; + } + + return error.BuildConfigNotFound; +} + +fn readBuildConfig_016( allocator: std.mem.Allocator, io: std.Io, init: std.process.Init, @@ -117,7 +197,63 @@ fn readBuildConfig( return error.BuildConfigNotFound; } -pub fn main(init: std.process.Init) !void { +pub const main = if (is_zig_0_16_or_later) main_016 else main_pre_016; + +fn main_pre_016() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + const allocator = arena.allocator(); + const args = try std.process.argsAlloc(allocator); + if (args.len < 2) { + return error.MissingBuildConfigArgument; + } + + const build_workspace_directory = try std.process.getEnvVarOwned(allocator, "BUILD_WORKSPACE_DIRECTORY"); + const execution_root = std.process.getEnvVarOwned(allocator, "BAZEL_EXECUTION_ROOT") catch |err| switch (err) { + error.EnvironmentVariableNotFound => blk: { + const output_base_result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = &.{ + "bazel", + "info", + "execution_root", + }, + .cwd = build_workspace_directory, + }); + break :blk std.mem.trimEnd(u8, output_base_result.stdout, "\n"); + }, + else => |e| return e, + }; + + var output = try readBuildConfig_pre_016(allocator, args[1]); + output = try std.mem.replaceOwned( + u8, + allocator, + output, + "__BUILD_WORKSPACE_DIRECTORY__", + build_workspace_directory, + ); + output = try std.mem.replaceOwned( + u8, + allocator, + output, + "__BAZEL_EXECUTION_ROOT__", + execution_root, + ); + + var stdout_buf: [4096]u8 = undefined; + var writer = std.fs.File.stdout().writer(&stdout_buf); + const stdout = &writer.interface; + if (processConfigLeaky_pre_016(allocator, output)) |config| { + try std.json.Stringify.value(config, .{}, stdout); + } else |_| { + try stdout.writeAll(output); + } + try stdout.flush(); +} + +fn main_016(init: std.process.Init) !void { const arena = init.arena; const io = init.io; const args = try init.minimal.args.toSlice(arena.allocator()); @@ -137,7 +273,7 @@ pub fn main(init: std.process.Init) !void { break :blk std.mem.trimEnd(u8, output_base_result.stdout, "\n"); }; - var output = try readBuildConfig( + var output = try readBuildConfig_016( arena.allocator(), io, init, @@ -160,7 +296,7 @@ pub fn main(init: std.process.Init) !void { var stdout_buf: [4096]u8 = undefined; var writer = std.Io.File.stdout().writer(io, &stdout_buf); - if (processConfigLeaky(arena.allocator(), io, output)) |config| { + if (processConfigLeaky_016(arena.allocator(), io, output)) |config| { try std.json.Stringify.value(config, .{}, &writer.interface); } else |_| { try writer.interface.writeAll(output); diff --git a/zig/zls/workspace_printer_test.zig b/zig/zls/workspace_printer_test.zig index 71e81adb..eb59179a 100644 --- a/zig/zls/workspace_printer_test.zig +++ b/zig/zls/workspace_printer_test.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const bazel_builtin = @import("bazel_builtin"); @@ -5,6 +6,20 @@ const runfiles = @import("runfiles"); const workspace_printer = @import("workspace_printer.zig"); +const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + +const EnvMap = if (is_zig_0_16_or_later) + std.process.Environ.Map +else + std.process.EnvMap; + +fn exitedTerm(code: u8) std.process.Child.Term { + if (is_zig_0_16_or_later) { + return .{ .exited = code }; + } + return .{ .Exited = code }; +} + fn requireEnv(name: [:0]const u8) ![]const u8 { return std.mem.span(std.c.getenv(name) orelse return error.MissingEnvironmentVariable); } @@ -29,12 +44,19 @@ test "completion print_build_config emits valid json" { defer arena.deinit(); const allocator = arena.allocator(); - var r_ = try runfiles.Runfiles.create(.{ - .allocator = allocator, - .io = std.testing.io, - .directory = if (std.c.getenv("RUNFILES_DIR")) |value| std.mem.span(value) else null, - .manifest = if (std.c.getenv("RUNFILES_MANIFEST_FILE")) |value| std.mem.span(value) else null, - }) orelse return error.RunfilesNotFound; + var r_ = (if (is_zig_0_16_or_later) + try runfiles.Runfiles.create(.{ + .allocator = allocator, + .io = std.testing.io, + .directory = if (std.c.getenv("RUNFILES_DIR")) |value| std.mem.span(value) else null, + .manifest = if (std.c.getenv("RUNFILES_MANIFEST_FILE")) |value| std.mem.span(value) else null, + }) + else + try runfiles.Runfiles.create(.{ + .allocator = allocator, + .directory = if (std.c.getenv("RUNFILES_DIR")) |value| std.mem.span(value) else null, + .manifest = if (std.c.getenv("RUNFILES_MANIFEST_FILE")) |value| std.mem.span(value) else null, + })) orelse return error.RunfilesNotFound; defer r_.deinit(allocator); const r = r_.withSourceRepo(bazel_builtin.current_repository); @@ -50,15 +72,22 @@ test "completion print_build_config emits valid json" { const workspace_dir = try workspaceDirFromConfigPath(config_path, package_name); const execution_root = workspace_dir; - var child_env_map: std.process.Environ.Map = .init(std.testing.allocator); + var child_env_map: EnvMap = .init(std.testing.allocator); defer child_env_map.deinit(); try child_env_map.put("BUILD_WORKSPACE_DIRECTORY", workspace_dir); try child_env_map.put("BAZEL_EXECUTION_ROOT", execution_root); - const result = try std.process.run(std.testing.allocator, std.testing.io, .{ - .argv = &.{ printer_path, config_path }, - .environ_map = &child_env_map, - }); + const result = if (is_zig_0_16_or_later) + try std.process.run(std.testing.allocator, std.testing.io, .{ + .argv = &.{ printer_path, config_path }, + .environ_map = &child_env_map, + }) + else + try std.process.Child.run(.{ + .allocator = std.testing.allocator, + .argv = &.{ printer_path, config_path }, + .env_map = &child_env_map, + }); defer std.testing.allocator.free(result.stdout); defer std.testing.allocator.free(result.stderr); @@ -66,7 +95,7 @@ test "completion print_build_config emits valid json" { std.log.warn("workspace_printer stderr: {s}", .{result.stderr}); } - try std.testing.expectEqual(@as(std.process.Child.Term, .{ .exited = 0 }), result.term); + try std.testing.expectEqual(exitedTerm(0), result.term); const config = try std.json.parseFromSliceLeaky(workspace_printer.BuildConfig, allocator, result.stdout, .{}); try std.testing.expect(config.modules.map.count() > 0); diff --git a/zig/zls/zls_build_runner.zig b/zig/zls/zls_build_runner.zig index 190dec92..d0069dd4 100644 --- a/zig/zls/zls_build_runner.zig +++ b/zig/zls/zls_build_runner.zig @@ -1,6 +1,27 @@ +const builtin = @import("builtin"); const std = @import("std"); -pub fn main(init: std.process.Init) !void { +const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + +pub const main = if (is_zig_0_16_or_later) main_016 else main_pre_016; + +fn main_pre_016() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + const allocator = arena.allocator(); + const build_workspace_directory = try std.process.getEnvVarOwned(allocator, "BUILD_WORKSPACE_DIRECTORY"); + var child = std.process.Child.init(&.{ + "bazel", + "run", + "__TARGET__", + }, allocator); + child.cwd = build_workspace_directory; + try child.spawn(); + _ = try child.wait(); +} + +fn main_016(init: std.process.Init) !void { const build_workspace_directory = init.environ_map.get("BUILD_WORKSPACE_DIRECTORY").?; var child = try std.process.spawn(init.io, .{ .argv = &.{ diff --git a/zig/zls/zls_runner.zig b/zig/zls/zls_runner.zig index d62889c5..853de0e0 100644 --- a/zig/zls/zls_runner.zig +++ b/zig/zls/zls_runner.zig @@ -3,12 +3,20 @@ /// Sets up paths to ZLS dependencies with Bazel runfiles. /// /// This file is used as a template by `zls_write_runner_zig_src.bzl`. +const builtin = @import("builtin"); const std = @import("std"); const fs = std.fs; const bazel_builtin = @import("bazel_builtin"); const runfiles = @import("runfiles"); +const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + +fn getRandomFilename_pre_016(buf: []u8, extension: []const u8) ![]const u8 { + const now = std.time.nanoTimestamp(); + return std.fmt.bufPrint(buf, "/tmp/{d}{s}", .{ now, extension }) catch @panic("OOM"); +} + fn getRandomFilename(io: std.Io, buf: []u8, extension: []const u8) ![]const u8 { const now = std.Io.Clock.real.now(io); return std.fmt.bufPrint(buf, "/tmp/{d}{s}", .{ now.nanoseconds, extension }) catch @panic("OOM"); @@ -28,7 +36,74 @@ const Config = struct { global_cache_path: ?[]const u8 = null, }; -pub fn main(init: std.process.Init) !void { +pub const main = if (is_zig_0_16_or_later) main_016 else main_pre_016; + +fn main_pre_016() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + const allocator = arena.allocator(); + + var r_ = try runfiles.Runfiles.create(.{ .allocator = allocator }) orelse + return error.RunfilesNotFound; + defer r_.deinit(allocator); + const r = r_.withSourceRepo(bazel_builtin.current_repository); + + const zls_bin_rpath = "__ZLS_BIN_RPATH__"; + const zls_bin_path = try r.rlocationAlloc(allocator, zls_bin_rpath) orelse + return error.RLocationNotFound; + + const zig_exe_rpath = "__ZIG_EXE_RPATH__"; + const zig_exe_path = try r.rlocationAlloc(allocator, zig_exe_rpath) orelse + return error.RLocationNotFound; + + const zig_lib_path = "__ZIG_LIB_RPATH__"; + const zig_lib_computed_path = try r.rlocationAlloc(allocator, zig_lib_path) orelse + return error.RLocationNotFound; + + const zls_build_runner_rpath = "__ZLS_BUILD_RUNNER_RPATH__"; + const zls_build_runner_path = try r.rlocationAlloc(allocator, zls_build_runner_rpath) orelse + return error.RLocationNotFound; + + const global_cache_path = "__GLOBAL_CACHE_PATH__"; + + const config: Config = .{ + .zig_exe_path = zig_exe_path, + .zig_lib_path = zig_lib_computed_path, + .build_runner_path = zls_build_runner_path, + .global_cache_path = global_cache_path, + }; + + var buf: [fs.max_path_bytes]u8 = undefined; + const tmp_file_path = try getRandomFilename_pre_016(&buf, ".json"); + + { + var tmp_file = try fs.createFileAbsolute(tmp_file_path, .{}); + defer tmp_file.close(); + + var out_buf: [4096]u8 = undefined; + var tmp_file_writer = tmp_file.writer(&out_buf); + try std.json.Stringify.value(config, .{ .whitespace = .indent_2 }, &tmp_file_writer.interface); + try tmp_file_writer.interface.flush(); + } + + const args = try std.process.argsAlloc(allocator); + const exec_args_len = args.len - 1 + 3; // Skip args[0] and add "zls" + --config-path + tmp_file_path + var exec_args = try allocator.alloc([]const u8, exec_args_len); + + exec_args[0] = zls_bin_path; // "zls" + + @memcpy(exec_args[1 .. exec_args.len - 2], args[1..]); + @memcpy(exec_args[exec_args.len - 2 ..], &[_][]const u8{ + "--config-path", + tmp_file_path, + }); + var child = std.process.Child.init(exec_args, allocator); + try child.spawn(); + _ = try child.wait(); +} + +fn main_016(init: std.process.Init) !void { const arena = init.arena; const io = init.io; diff --git a/zig/zls/zls_write_runner_zig_src.bzl b/zig/zls/zls_write_runner_zig_src.bzl index b34de3d8..8603fae4 100644 --- a/zig/zls/zls_write_runner_zig_src.bzl +++ b/zig/zls/zls_write_runner_zig_src.bzl @@ -19,18 +19,16 @@ def _zls_write_runner_zig_src_impl(ctx): zig_exe_rpath = zig_toolchain_executable_rpath(ctx, zigtoolchaininfo) zig_lib_rpath = zig_toolchain_lib_rpath(ctx, zigtoolchaininfo) - prefix_suffix = "@" + "@" - zls_runner = ctx.outputs.out ctx.actions.expand_template( output = zls_runner, template = ctx.file._runner_tpl, substitutions = { - "{prefix_suffix}__ZIG_EXE_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): zig_exe_rpath, - "{prefix_suffix}__ZIG_LIB_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): zig_lib_rpath, - "{prefix_suffix}__ZLS_BIN_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): to_rlocation_path(ctx, zlstoolchaininfo.bin), - "{prefix_suffix}__ZLS_BUILD_RUNNER_RPATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): to_rlocation_path(ctx, ctx.file.build_runner), - "{prefix_suffix}__GLOBAL_CACHE_PATH__{prefix_suffix}".format(prefix_suffix = prefix_suffix): zigtoolchaininfo.zig_cache, + "__ZIG_EXE_RPATH__": zig_exe_rpath, + "__ZIG_LIB_RPATH__": zig_lib_rpath, + "__ZLS_BIN_RPATH__": to_rlocation_path(ctx, zlstoolchaininfo.bin), + "__ZLS_BUILD_RUNNER_RPATH__": to_rlocation_path(ctx, ctx.file.build_runner), + "__GLOBAL_CACHE_PATH__": zigtoolchaininfo.zig_cache, }, ) From 324b050aa95446dd11480d901e01c510558f4797 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 19:32:37 +0200 Subject: [PATCH 10/13] build: drop redundant objc_common deps [agent] --- zig/private/BUILD.bazel | 3 --- zig/zls/BUILD.bazel | 1 - 2 files changed, 4 deletions(-) diff --git a/zig/private/BUILD.bazel b/zig/private/BUILD.bazel index 786a862f..a7e5955c 100644 --- a/zig/private/BUILD.bazel +++ b/zig/private/BUILD.bazel @@ -121,7 +121,6 @@ bzl_library( visibility = ["//zig:__subpackages__"], deps = [ "@rules_cc//cc/common", - "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) @@ -131,7 +130,6 @@ bzl_library( visibility = ["//zig:__subpackages__"], deps = [ "@rules_cc//cc/common", - "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) @@ -142,7 +140,6 @@ bzl_library( deps = [ "//zig/private/providers:zig_toolchain_info", "@rules_cc//cc/common", - "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) diff --git a/zig/zls/BUILD.bazel b/zig/zls/BUILD.bazel index c601763c..e3da16fe 100644 --- a/zig/zls/BUILD.bazel +++ b/zig/zls/BUILD.bazel @@ -66,7 +66,6 @@ bzl_library( "//zig/translate-c:toolchain", "@rules_cc//cc:find_cc_toolchain_bzl", "@rules_cc//cc/common", - "@rules_cc//cc/private/rules_impl:objc_common", # keep ], ) From dda9ee9a6d5e17562b933eebebdb7e20a2389c03 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 19:32:44 +0200 Subject: [PATCH 11/13] zls: address review nits [agent] --- README.md | 2 ++ zig/defs.bzl | 1 + zig/zls/workspace_printer.zig | 2 +- zig/zls/zls_write_build_config.bzl | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bce1844b..31eaebf0 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ Examples can be found among the end-to-end tests under ZLS toolchains are provided by a separate extension and selected by the active Zig SDK version. +Use the `zls_completion` rule from `@rules_zig//zig/zls:defs.bzl` to define a +ZLS entry point for the Zig targets you want to expose to the language server. ```starlark zls = use_extension("@rules_zig//zig/zls:extensions.bzl", "zls") diff --git a/zig/defs.bzl b/zig/defs.bzl index ae2456da..cd3b9ca5 100644 --- a/zig/defs.bzl +++ b/zig/defs.bzl @@ -18,6 +18,7 @@ load("//zig/private:zig_shared_library.bzl", _zig_shared_library = "zig_shared_l load("//zig/private:zig_static_library.bzl", _zig_static_library = "zig_static_library") load("//zig/private:zig_test.bzl", _zig_test = "zig_test") +# Keep //zig/zls:zls_write_build_config.bzl _is_zig_target in sync with this rule list. zig_binary = _zig_binary zig_static_library = _zig_static_library zig_shared_library = _zig_shared_library diff --git a/zig/zls/workspace_printer.zig b/zig/zls/workspace_printer.zig index 66c10d42..7cb3a129 100644 --- a/zig/zls/workspace_printer.zig +++ b/zig/zls/workspace_printer.zig @@ -6,7 +6,7 @@ const runfiles = @import("runfiles"); const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; -/// https://github.com/zigtools/zls/blob/master/src/build_runner/shared.zig#L6 (2026-03-10) +/// https://github.com/zigtools/zls/blob/606a86543362c0072248d8f1ef4a64a5e3f51682/src/build_runner/shared.zig#L6 pub const BuildConfig = struct { /// The `dependencies` in `build.zig.zon`. dependencies: std.json.ArrayHashMap([]const u8), diff --git a/zig/zls/zls_write_build_config.bzl b/zig/zls/zls_write_build_config.bzl index 6e18f0f4..a44e0363 100644 --- a/zig/zls/zls_write_build_config.bzl +++ b/zig/zls/zls_write_build_config.bzl @@ -45,7 +45,7 @@ def _zls_construct_zig_module_info_impl(target, ctx): main = ctx.rule.file.main, srcs = ctx.rule.files.srcs, extra_srcs = ctx.rule.files.extra_srcs, - deps = zdeps, # [bazel_builtin_module(ctx)], + deps = zdeps, cdeps = cdeps, zigopts = [], ) From 5eb5307c9c9a6e3483a1ba8b3f742ec947b21498 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 19:35:58 +0200 Subject: [PATCH 12/13] docs: expand zls completion example [agent] --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31eaebf0..43487406 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,6 @@ Examples can be found among the end-to-end tests under ZLS toolchains are provided by a separate extension and selected by the active Zig SDK version. -Use the `zls_completion` rule from `@rules_zig//zig/zls:defs.bzl` to define a -ZLS entry point for the Zig targets you want to expose to the language server. ```starlark zls = use_extension("@rules_zig//zig/zls:extensions.bzl", "zls") @@ -148,6 +146,28 @@ Use `zig_version` as the selector and `zls_version` as the artifact version. They do not need to match, which allows a dev ZLS build to be tied to a stable Zig SDK. +Use the [`zls_completion`](./zig/zls/zls_completion.bzl) macro to define a ZLS +entry point for the Zig targets you want to expose to the language server. +For example, in `tools/BUILD.bazel`: + +```starlark +load("@rules_zig//zig/zls:defs.bzl", "zls_completion") + +zls_completion( + name = "zls", + deps = ["//src:app"], +) +``` + +Then point your editor's ZLS binary setting at a wrapper script that runs that +target. + +```bash +#!/usr/bin/env bash +cd "$(dirname "${BASH_SOURCE[0]}")/.." +exec bazel run -- //tools:zls "${@}" +``` + ## Reference Documentation Generated API documentation for the provided rules is available in From 538a0824f326a8098568053c8dba4ea786ec702d Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 20 May 2026 01:00:26 +0200 Subject: [PATCH 13/13] zls: configure runner cache behavior [agent] --- zig/zls/zls_runner.zig | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/zig/zls/zls_runner.zig b/zig/zls/zls_runner.zig index 853de0e0..45a7adcd 100644 --- a/zig/zls/zls_runner.zig +++ b/zig/zls/zls_runner.zig @@ -12,6 +12,11 @@ const runfiles = @import("runfiles"); const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; +const EnvMap = if (is_zig_0_16_or_later) + std.process.Environ.Map +else + std.process.EnvMap; + fn getRandomFilename_pre_016(buf: []u8, extension: []const u8) ![]const u8 { const now = std.time.nanoTimestamp(); return std.fmt.bufPrint(buf, "/tmp/{d}{s}", .{ now, extension }) catch @panic("OOM"); @@ -34,6 +39,9 @@ const Config = struct { /// Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`. global_cache_path: ?[]const u8 = null, + + /// Build-on-save expects a watch-capable Zig build runner protocol. + enable_build_on_save: ?bool = false, }; pub const main = if (is_zig_0_16_or_later) main_016 else main_pre_016; @@ -98,7 +106,13 @@ fn main_pre_016() !void { "--config-path", tmp_file_path, }); + var child_env_map = try std.process.getEnvMap(allocator); + defer child_env_map.deinit(); + try child_env_map.put("ZIG_GLOBAL_CACHE_DIR", global_cache_path); + try child_env_map.put("ZIG_LOCAL_CACHE_DIR", global_cache_path); + var child = std.process.Child.init(exec_args, allocator); + child.env_map = &child_env_map; try child.spawn(); _ = try child.wait(); } @@ -168,8 +182,17 @@ fn main_016(init: std.process.Init) !void { "--config-path", tmp_file_path, }); + var child_env_map: EnvMap = .init(arena.allocator()); + defer child_env_map.deinit(); + for (init.environ_map.keys(), init.environ_map.values()) |key, value| { + try child_env_map.put(key, value); + } + try child_env_map.put("ZIG_GLOBAL_CACHE_DIR", global_cache_path); + try child_env_map.put("ZIG_LOCAL_CACHE_DIR", global_cache_path); + var child = try std.process.spawn(io, .{ .argv = exec_args, + .environ_map = &child_env_map, }); _ = try child.wait(io); }