diff --git a/docs/rules.md b/docs/rules.md index afb0a076..b698e3b0 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -23,6 +23,27 @@ zig_binary(name, deps< | srcs | Other source files required to build the target. | List of labels | optional | [] | + + +## zig_library + +
+zig_library(name, deps, main, srcs)
+
+ + + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | Packages or libraries required to build the target. | List of labels | optional | [] | +| main | The main source file. | Label | required | | +| srcs | Other source files required to build the target. | List of labels | optional | [] | + + ## zig_package diff --git a/e2e/workspace/simple-library/BUILD.bazel b/e2e/workspace/simple-library/BUILD.bazel new file mode 100644 index 00000000..3f162224 --- /dev/null +++ b/e2e/workspace/simple-library/BUILD.bazel @@ -0,0 +1,23 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@rules_zig//zig:defs.bzl", "zig_library") + +zig_library( + name = "library", + main = "main.zig", +) + +genrule( + name = "library-symbol", + srcs = [":library"], + outs = ["library-symbol.txt"], + cmd = "$(NM) -j --defined-only $(SRCS) | grep sayHello > $(OUTS)", + toolchains = ["@bazel_tools//tools/cpp:current_cc_toolchain"], +) + +build_test( + name = "library_build_test", + targets = [ + ":library", + ":library-symbol", + ], +) diff --git a/e2e/workspace/simple-library/main.zig b/e2e/workspace/simple-library/main.zig new file mode 100644 index 00000000..c215a20e --- /dev/null +++ b/e2e/workspace/simple-library/main.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +export fn sayHello() void { + std.io.getStdOut().writeAll( + "Hello World!\n", + ) catch unreachable; +} diff --git a/zig/BUILD.bazel b/zig/BUILD.bazel index af6f2d61..5b3f2a0b 100644 --- a/zig/BUILD.bazel +++ b/zig/BUILD.bazel @@ -29,6 +29,7 @@ bzl_library( visibility = ["//visibility:public"], deps = [ "//zig/private:zig_binary", + "//zig/private:zig_library", "//zig/private:zig_package", ], ) diff --git a/zig/defs.bzl b/zig/defs.bzl index a10850b7..8104f184 100644 --- a/zig/defs.bzl +++ b/zig/defs.bzl @@ -1,7 +1,9 @@ "Public API re-exports" load("//zig/private:zig_binary.bzl", _zig_binary = "zig_binary") +load("//zig/private:zig_library.bzl", _zig_library = "zig_library") load("//zig/private:zig_package.bzl", _zig_package = "zig_package") zig_binary = _zig_binary +zig_library = _zig_library zig_package = _zig_package diff --git a/zig/private/BUILD.bazel b/zig/private/BUILD.bazel index 17a658d0..0fac081b 100644 --- a/zig/private/BUILD.bazel +++ b/zig/private/BUILD.bazel @@ -1,39 +1,45 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") bzl_library( - name = "toolchains_repo", - srcs = ["toolchains_repo.bzl"], + name = "zig_binary", + srcs = ["zig_binary.bzl"], visibility = ["//zig:__subpackages__"], + deps = [ + "//zig/private/common:filetypes", + "//zig/private/common:zig_cache", + "//zig/private/providers:zig_package_info", + ], ) bzl_library( - name = "versions", - srcs = ["versions.bzl"], + name = "zig_package", + srcs = ["zig_package.bzl"], visibility = ["//zig:__subpackages__"], + deps = [ + "//zig/private/common:filetypes", + "//zig/private/providers:zig_package_info", + ], ) bzl_library( - name = "zig_binary", - srcs = ["zig_binary.bzl"], + name = "zig_library", + srcs = ["zig_library.bzl"], visibility = ["//zig:__subpackages__"], deps = [ - ":filetypes", - "@bazel_skylib//lib:paths", + "//zig/private/common:filetypes", + "//zig/private/common:zig_cache", + "//zig/private/providers:zig_package_info", ], ) bzl_library( - name = "zig_package", - srcs = ["zig_package.bzl"], + name = "toolchains_repo", + srcs = ["toolchains_repo.bzl"], visibility = ["//zig:__subpackages__"], - deps = [ - ":filetypes", - "//zig/private/providers:zig_package_info", - ], ) bzl_library( - name = "filetypes", - srcs = ["filetypes.bzl"], + name = "versions", + srcs = ["versions.bzl"], visibility = ["//zig:__subpackages__"], ) diff --git a/zig/private/common/BUILD.bazel b/zig/private/common/BUILD.bazel new file mode 100644 index 00000000..d9b63d5e --- /dev/null +++ b/zig/private/common/BUILD.bazel @@ -0,0 +1,14 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "zig_cache", + srcs = ["zig_cache.bzl"], + visibility = ["//zig:__subpackages__"], + deps = ["@bazel_skylib//lib:paths"], +) + +bzl_library( + name = "filetypes", + srcs = ["filetypes.bzl"], + visibility = ["//zig:__subpackages__"], +) diff --git a/zig/private/filetypes.bzl b/zig/private/common/filetypes.bzl similarity index 100% rename from zig/private/filetypes.bzl rename to zig/private/common/filetypes.bzl diff --git a/zig/private/common/zig_cache.bzl b/zig/private/common/zig_cache.bzl new file mode 100644 index 00000000..44cbdced --- /dev/null +++ b/zig/private/common/zig_cache.bzl @@ -0,0 +1,26 @@ +"""Defines utilities to handle the Zig compiler cache.""" + +load("@bazel_skylib//lib:paths.bzl", "paths") + +def zig_cache_output(*, actions, name, outputs, args): + """Handle the Zig compiler cache. + + Declares directory outputs for the local and global Zig compiler cache. + Appends both to the given outputs list, and arguments object. + + Args: + actions: `ctx.actions`. + name: String, A unique name to distinguish this cache from others. + outputs: List; mutable, Append the declared outputs to this list. + args: Args; mutable, Append the Zig cache flags to this object. + """ + + # TODO[AH] Persist or share at least the global cache somehow. + local_cache = actions.declare_directory(paths.join(".zig-cache", "local", name)) + global_cache = actions.declare_directory(paths.join(".zig-cache", "global", name)) + + outputs.append(local_cache) + outputs.append(global_cache) + + args.add_all(["--cache-dir", local_cache.path]) + args.add_all(["--global-cache-dir", global_cache.path]) diff --git a/zig/private/providers/zig_package_info.bzl b/zig/private/providers/zig_package_info.bzl index 486e946f..605fe019 100644 --- a/zig/private/providers/zig_package_info.bzl +++ b/zig/private/providers/zig_package_info.bzl @@ -16,18 +16,17 @@ ZigPackageInfo = provider( doc = DOC, ) -def add_package_flags(args, package): - """Generate the Zig compiler flags to depend on the given package. - - Generates `--pkg-begin` and `--pkg-end` flags required to build a target - that depends on this package. +def zig_package_dependencies(*, deps, inputs, args): + """Collect inputs and flags for Zig package dependencies. Args: - args: The Args object to extend with the required flags. - package: The package to generate flags for. + deps: List of Target, Considers the targets that have a ZigPackageInfo provider. + inputs: List of depset of File; mutable, Append the needed inputs to this list. + args: Args; mutable, Append the needed Zig compiler flags to this object. """ - args.add_all(package.flags) - -def get_package_files(package): - """Generate a `depset` of the files required to depend on the package.""" - return package.all_srcs + for dep in deps: + if not ZigPackageInfo in dep: + continue + package = dep[ZigPackageInfo] + inputs.append(package.all_srcs) + args.add_all(package.flags) diff --git a/zig/private/zig_binary.bzl b/zig/private/zig_binary.bzl index d30abd89..276c118c 100644 --- a/zig/private/zig_binary.bzl +++ b/zig/private/zig_binary.bzl @@ -1,12 +1,11 @@ """Implementation of the zig_binary rule.""" -load("@bazel_skylib//lib:paths.bzl", "paths") -load("//zig/private:filetypes.bzl", "ZIG_SOURCE_EXTENSIONS") +load("//zig/private/common:filetypes.bzl", "ZIG_SOURCE_EXTENSIONS") +load("//zig/private/common:zig_cache.bzl", "zig_cache_output") load( "//zig/private/providers:zig_package_info.bzl", "ZigPackageInfo", - "add_package_flags", - "get_package_files", + "zig_package_dependencies", ) DOC = """\ @@ -33,33 +32,38 @@ ATTRS = { def _zig_binary_impl(ctx): ziginfo = ctx.toolchains["//zig:toolchain_type"].ziginfo - # TODO[AH] Append `.exe` extension on Windows. - output = ctx.actions.declare_file(ctx.label.name) + outputs = [] - local_cache = ctx.actions.declare_directory(paths.join(".zig-cache", "local", ctx.label.name)) - global_cache = ctx.actions.declare_directory(paths.join(".zig-cache", "global", ctx.label.name)) - - direct_inputs = [ctx.file.main] + ctx.files.srcs + direct_inputs = [] transitive_inputs = [] args = ctx.actions.args() args.use_param_file("@%s") + # TODO[AH] Append `.exe` extension on Windows. + output = ctx.actions.declare_file(ctx.label.name) + outputs.append(output) args.add(output, format = "-femit-bin=%s") + + direct_inputs.append(ctx.file.main) + direct_inputs.extend(ctx.files.srcs) args.add(ctx.file.main) - for dep in ctx.attr.deps: - if ZigPackageInfo in dep: - package = dep[ZigPackageInfo] - transitive_inputs.append(get_package_files(package)) - add_package_flags(args, package) + zig_package_dependencies( + deps = ctx.attr.deps, + inputs = transitive_inputs, + args = args, + ) - # TODO[AH] Persist or share at least the global cache somehow. - args.add_all(["--cache-dir", local_cache.path]) - args.add_all(["--global-cache-dir", global_cache.path]) + zig_cache_output( + actions = ctx.actions, + name = ctx.label.name, + outputs = outputs, + args = args, + ) ctx.actions.run( - outputs = [output, local_cache, global_cache], + outputs = outputs, inputs = depset(direct = direct_inputs, transitive = transitive_inputs), executable = ziginfo.target_tool_path, tools = ziginfo.tool_files, diff --git a/zig/private/zig_library.bzl b/zig/private/zig_library.bzl new file mode 100644 index 00000000..51cef3dc --- /dev/null +++ b/zig/private/zig_library.bzl @@ -0,0 +1,88 @@ +"""Implementation of the zig_library rule.""" + +load("//zig/private/common:filetypes.bzl", "ZIG_SOURCE_EXTENSIONS") +load("//zig/private/common:zig_cache.bzl", "zig_cache_output") +load( + "//zig/private/providers:zig_package_info.bzl", + "ZigPackageInfo", + "zig_package_dependencies", +) + +DOC = """\ +""" + +ATTRS = { + "main": attr.label( + allow_single_file = ZIG_SOURCE_EXTENSIONS, + doc = "The main source file.", + mandatory = True, + ), + "srcs": attr.label_list( + allow_files = ZIG_SOURCE_EXTENSIONS, + doc = "Other source files required to build the target.", + mandatory = False, + ), + "deps": attr.label_list( + doc = "Packages or libraries required to build the target.", + mandatory = False, + providers = [ZigPackageInfo], + ), +} + +def _zig_library_impl(ctx): + ziginfo = ctx.toolchains["//zig:toolchain_type"].ziginfo + + outputs = [] + + direct_inputs = [] + transitive_inputs = [] + + args = ctx.actions.args() + args.use_param_file("@%s") + + # TODO[AH] Set `.lib` extension on Windows. + static = ctx.actions.declare_file(ctx.label.name + ".a") + outputs.append(static) + args.add(static, format = "-femit-bin=%s") + # TODO[AH] Support dynamic library output. + + direct_inputs.append(ctx.file.main) + direct_inputs.extend(ctx.files.srcs) + args.add(ctx.file.main) + + zig_package_dependencies( + deps = ctx.attr.deps, + inputs = transitive_inputs, + args = args, + ) + + zig_cache_output( + actions = ctx.actions, + name = ctx.label.name, + outputs = outputs, + args = args, + ) + + ctx.actions.run( + outputs = outputs, + inputs = depset(direct = direct_inputs, transitive = transitive_inputs), + executable = ziginfo.target_tool_path, + tools = ziginfo.tool_files, + arguments = ["build-lib", args], + mnemonic = "ZigBuildLib", + progress_message = "Building %{input} as Zig library %{output}", + execution_requirements = {tag: "" for tag in ctx.attr.tags}, + ) + + default = DefaultInfo( + files = depset([static]), + ) + + return [default] + +zig_library = rule( + _zig_library_impl, + attrs = ATTRS, + doc = DOC, + toolchains = ["//zig:toolchain_type"], +) diff --git a/zig/private/zig_package.bzl b/zig/private/zig_package.bzl index 2fbe0526..f3b7c7d1 100644 --- a/zig/private/zig_package.bzl +++ b/zig/private/zig_package.bzl @@ -1,7 +1,7 @@ """Implementation of the zig_package rule.""" +load("//zig/private/common:filetypes.bzl", "ZIG_SOURCE_EXTENSIONS") load("//zig/private/providers:zig_package_info.bzl", "ZigPackageInfo") -load("//zig/private:filetypes.bzl", "ZIG_SOURCE_EXTENSIONS") DOC = """\ """ diff --git a/zig/tests/package_info_test.bzl b/zig/tests/package_info_test.bzl index df1f9779..ca9d0753 100644 --- a/zig/tests/package_info_test.bzl +++ b/zig/tests/package_info_test.bzl @@ -6,8 +6,7 @@ load("@bazel_skylib//lib:sets.bzl", "sets") load( "//zig/private/providers:zig_package_info.bzl", "ZigPackageInfo", - "add_package_flags", - "get_package_files", + "zig_package_dependencies", ) def _mock_args(): @@ -31,23 +30,30 @@ def _mock_args(): def _single_package_test_impl(ctx): env = unittest.begin(ctx) + transitive_inputs = [] + args = _mock_args() + + zig_package_dependencies( + deps = [ctx.attr.pkg], + inputs = transitive_inputs, + args = args, + ) + package = ctx.attr.pkg[ZigPackageInfo] - args = _mock_args() - add_package_flags(args, package) asserts.equals( env, ["--pkg-begin", package.name, package.main.path, "--pkg-end"], args.get_args(), - "add_package_flags should generate the expected arguments.", + "zig_package_dependencies should generate the expected arguments.", ) - files = get_package_files(package) + inputs = depset(transitive = transitive_inputs) asserts.set_equals( env, sets.make([package.main] + package.srcs), - sets.make(files.to_list()), - "get_package_files should capture all package files.", + sets.make(inputs.to_list()), + "zig_package_dependencies should capture all package files.", ) return unittest.end(env) @@ -62,13 +68,20 @@ _single_package_test = unittest.make( def _nested_packages_test_impl(ctx): env = unittest.begin(ctx) + transitive_inputs = [] + args = _mock_args() + + zig_package_dependencies( + deps = [dep for dep in ctx.attr.pkgs if dep.label.name == "a"], + inputs = transitive_inputs, + args = args, + ) + pkgs = { pkg.label.name: pkg[ZigPackageInfo] for pkg in ctx.attr.pkgs } - args = _mock_args() - add_package_flags(args, pkgs["a"]) asserts.equals( env, [ @@ -122,10 +135,10 @@ def _nested_packages_test_impl(ctx): "--pkg-end", ], args.get_args(), - "add_package_flags should unfold the transitive dependency graph onto the command-line.", + "zig_package_dependencies should unfold the transitive dependency graph onto the command-line.", ) - files = get_package_files(pkgs["a"]) + inputs = depset(transitive = transitive_inputs) asserts.set_equals( env, sets.make([ @@ -133,8 +146,8 @@ def _nested_packages_test_impl(ctx): for pkg in pkgs.values() for src in [pkg.main] + pkg.srcs ]), - sets.make(files.to_list()), - "get_package_files should capture all package files.", + sets.make(inputs.to_list()), + "zig_package_dependencies should capture all package files.", ) return unittest.end(env)