Skip to content
Merged
21 changes: 21 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ 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",
)
zls_dev.toolchain(
zig_version = "0.15.2",
zls_version = "0.15.1",
)
use_repo(zls_dev, "zls_toolchains")

register_toolchains(
"@zls_toolchains//:all",
dev_dependency = True,
)

bazel_dep(name = "toolchains_buildbuddy", version = "0.0.4", dev_dependency = True)

buildbuddy = use_extension(
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,48 @@ 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
Comment thread
cerisier marked this conversation as resolved.

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.

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
Expand Down
10 changes: 10 additions & 0 deletions util/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
100 changes: 100 additions & 0 deletions util/update_zls_versions.py
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions zig/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ filegroup(
"//zig/settings:all_files",
"//zig/target:all_files",
"//zig/translate-c:all_files",
"//zig/zls:all_files",
],
visibility = ["//:__pkg__"],
)
1 change: 1 addition & 0 deletions zig/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions zig/private/common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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
],
)

Expand Down Expand Up @@ -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
],
)

Expand Down
4 changes: 4 additions & 0 deletions zig/private/common/zig_build.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions zig/private/providers/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ bzl_library(
deps = [
"//zig/private:cc_helper",
"@rules_cc//cc/common",
"@rules_cc//cc/private/rules_impl:objc_common", # keep
],
)

Expand Down
100 changes: 100 additions & 0 deletions zig/tests/zls-completion/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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",
main = "main.zig",
)

zls_completion(
name = "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",
)
12 changes: 12 additions & 0 deletions zig/tests/zls-completion/app.zig
Original file line number Diff line number Diff line change
@@ -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();
}
5 changes: 5 additions & 0 deletions zig/tests/zls-completion/binary_with_main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const app = @import("app");

pub fn main() void {
_ = app.value();
}
Loading
Loading