-
Notifications
You must be signed in to change notification settings - Fork 1
Lesson 10: Toolchains
In Lesson 9, we defined a macro capable of rendering GraphViz output into a .png or a .json file.
Since we've been using dot from the command line across these lessons, we had it ready to install; however, what if a user didn't have the tool available?
In that case, they wouldn't be able to perform bazelisk build //... from a fresh clone of the repository.
Many repos indicate tooling dependencies like this within their READMEs; however, in Bazel, we can make this dependency first-class.
For this lesson, we'll take inspiration from Tim Jäger's Integrating Babashka with Bazel Tutorial, and integrate Babashka into our repository.
Babashka is a command line tool to execute Clojure programs as scripts. These scripts run in a special interpreter that packages a few common libraries as first class dependencies. The tool can be installed through a variety of package managers, but today, we'll be installing it as a dependency of the repository instead of a dependency of the machine building the repository.
Like the rules_python and rules_java dependencies we installed in Lesson 3, we'll be specifying a tooling dependency- so, we need to update MODULE.bazel.
However, unlike the previous tools we've installed, babashka is not a Bazel repository.
To install and use babashka, we'll need to install it a little differently.
Additionally, babashka is sensitive to the CPU architecture and OS of the system it runs on.
This tutorial is being written on a Mac with an M3 processor, so the installation will start with that assumption.
Now, we'll update MODULE.bazel to install babashka v1.12.209 from the official GitHub release.
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "babashka_osx",
build_file_content = """exports_files(["bb"])""",
sha256 = "b02f906697b1ede016e96a3b340ea40e52380eef52307abc8a114210bfe89c44",
type = "tar.gz",
url = "https://github.com/babashka/babashka/releases/download/v1.12.209/babashka-1.12.209-macos-amd64.tar.gz",
)The http_archive rule we're declaring is a repo rule: meaning it is used to define new repositories.
For example, we when installed rules_java, we saw that we could query an interact with targets whose labels began with @rules_java//.
The http_archive call above is defining the contents of @babashka_osx// through the name attribute.
The other attributes are:
-
url: The actual URL of the content we're trying to install. In this case, the content is compressed in a tarball which contains a single filebb -
type: The type of archive or compression we're installing. This attribute is optional, but makes the compression type more explicit for future work. -
sha256: The SHA256 checksum for the content at the URL. This is optional, but should always be included. Bazel will intentionally fail builds if the checksum values are ever mismatched with the real content. This prevents both accidental corruption caused by the installation, and is a helpful tool to prevent supply-chain hijacking attacks. -
build_file_content: As perviously stated, babashka is not a Bazel repository. To make the contents of the repository visible to Bazel, we need aBUILDfile present. This allows us to inline anexports_filescall, like we saw in Lesson 6, to surface thebbexecutable to consumers.
Now that we've defined the repository, we should be able to query it:
bazelisk query "@babashka_osx//...:*"
Starting local Bazel server (8.3.1) and connecting to it...
@babashka_osx//:BUILD.bazel
@babashka_osx//:bbNow that we have a babashka executable available, let's use it to write a few rules.
Let's start by building a new package for our code:
mkdir -p toolchains/babashka
touch toolchains/babashka/BUILDSince babashka is primarily a scripting tool, let's define a rule analogous to Bazel's genrule.
Following the patterns we've seen in rules_java and rules_python, we'll prefix our rule name with bb_ to indicate that it defines a babashka target.
touch toolchains/babashka/bb_genrule.bzlWe'll now define our rule:
"""
Defines the bb_genrule rule, which is a babashka-powered analogue to Bazel's genrule
"""
def _bb_genrule_impl(ctx):
ctx.actions.run(
inputs = [ctx.file.script] + ctx.files.data,
outputs = [ctx.outputs.out],
executable = ctx.executable._bb,
arguments = [
ctx.file.script.path,
"""{{
:out-file "{out_file}"
}}""".format(
out_file = ctx.outputs.out.path,
),
],
)
bb_genrule = rule(
doc = "An alternative to the general genrule which uses babashka as the scripting language",
implementation = _bb_genrule_impl,
attrs = {
"script": attr.label(allow_single_file = [".clj"], mandatory = True),
"out": attr.output(mandatory = True),
"_bb": attr.label(
executable = True,
allow_single_file = True,
cfg = "exec",
default = "@babashka_osx//:bb",
),
},
)In the above, we'll notice that we're passing a private _bb attribute which is earmarked as executable.
The attribute points to the executable file we just installed as a dependency.
The action the build we perform calls the executable by referencing its path, since it will be located relative to the execution root, and will give the script a command line argument.
The command line argument structure is unique to babashka, as it's accepting arguments formatted as EDN.
We're using that argument structure to pass the file information that we'll be building as the artifact of this action.
Now, let's write some code to replicate the command-line tool pwd in a file named pwd.clj:
(ns pwd
(:require
[clojure.edn :as edn]
[clojure.java.io :as io]))
(let [{:keys [out-file]} (edn/read-string (first *command-line-args*))
directory (System/getProperty "user.dir")]
(spit (io/file out-file) {:directory directory}))Let's tie that code to a rule by updating toolchains/babashka/BUILD.
"""
Example usage of our custom babashka toolchain.
"""
load("//toolchains/babashka:bb_genrule.bzl", "bb_genrule")
bb_genrule(
name = "pwd",
out = "pwd.edn",
script = ":pwd.clj",
)Now, we'll build the target:
$ bazelisk build //toolchains/babashka:pwd
NFO: Analyzed target //toolchains/babashka:pwd (64 packages loaded, 579 targets configured).
INFO: Found 1 target...
Target //toolchains/babashka:pwd up-to-date:
bazel-bin/toolchains/babashka/pwd.edn
INFO: Elapsed time: 1.465s, Critical Path: 0.02s
INFO: 1 process: 1 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total actionAnd if we cat the result, we'll see where the execution root points to for our machine.
Before we move on, let's expand our rule to let us reference files produced from other targets.
Conventionally, these are passed in with a data attribute.
We'll then use the .path form we used to point to the babashka executable to hand our script the actual locations of the data on disk.
"""
Defines the bb_genrule rule, which is a babashka-powered analogue to Bazel's genrule
"""
def _bb_genrule_impl(ctx):
ctx.actions.run(
inputs = [ctx.file.script] + ctx.files.data,
outputs = [ctx.outputs.out],
executable = ctx.executable._bb,
arguments = [
ctx.file.script.path,
"""{{
:out-file "{out_file}"
:data [{data}]
}}""".format(
out_file = ctx.outputs.out.path,
data = " ".join(["\"{}\"".format(data.path) for data in ctx.files.data]),
),
],
)
bb_genrule = rule(
doc = "An alternative to the general genrule which uses babashka as the scripting language",
implementation = _bb_genrule_impl,
attrs = {
"script": attr.label(allow_single_file = [".clj"], mandatory = True),
"out": attr.output(mandatory = True),
"data": attr.label_list(allow_files = True),
"_bb": attr.label(
executable = True,
allow_single_file = True,
cfg = "exec",
default = "@babashka_osx//:bb",
),
},
)We can now refer to the data argument in our scripts.
Let's make a script which reads and echos data from other targets in reader.clj
(ns reader
(:require
[clojure.edn :as edn]
[clojure.java.io :as io]))
(let [{:keys [data out-file]} (edn/read-string (first *command-line-args*))
content (mapv slurp data)]
(spit (io/file out-file) {:content content}))We can use bb_genrule in our BUILD file to view the results of the script.
In this case, we'll read the output of the pwd rule we just wrote.
"""
Example usage of our custom babashka toolchain.
"""
load("//toolchains/babashka:bb_genrule.bzl", "bb_genrule")
bb_genrule(
name = "pwd",
out = "pwd.edn",
script = ":pwd.clj",
)
bb_genrule(
name = "reader",
out = "results.edn",
data = [
":pwd.edn",
],
script = ":reader.clj",
)Which is now buildable:
$ bazelisk build //toolchains/babashka:reader
INFO: Analyzed target //toolchains/babashka:reader (65 packages loaded, 582 targets configured).
INFO: Found 1 target...
Target //toolchains/babashka:reader up-to-date:
bazel-bin/toolchains/babashka/results.edn
INFO: Elapsed time: 7.549s, Critical Path: 0.02s
INFO: 1 process: 2 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total actionIf we open this file, we'll see the output of the pwd target saved in a collection under the :content key.
Now that we have a robust script we can use with bazelisk build, let's make a version we can execute with bazelisk run.
In Lesson 4, we used the java_binary and py_binary rules to build executables.
We ran those executables both with bazelisk run, and by directly invoking the executables from the command line.
Let's define a rule which does the same with babashaka in toolchains/babashka/bb_binary.bzl
"""
Defines the bb_binary rule, which makes babashka scripts executable via `bazelisk run`
"""
def _bb_binary_impl(ctx):
executable = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = executable,
is_executable = True,
content = """
set -x
exec {bb} {src} {arguments} "$@"
""".format(
bb = ctx.executable._bb.short_path,
src = ctx.file.src.path,
arguments = " ".join(ctx.attr.arguments),
),
)
return DefaultInfo(
executable = executable,
runfiles = ctx.runfiles(files = [ctx.executable._bb, ctx.file.src]),
)
bb_binary = rule(
doc = "Executes a babashka script",
implementation = _bb_binary_impl,
executable = True,
attrs = {
"src": attr.label(
allow_single_file = [".clj"],
mandatory = True,
),
"arguments": attr.string_list(),
"_bb": attr.label(
executable = True,
allow_single_file = True,
cfg = "exec",
default = "@babashka_osx//:bb",
),
},
)This is very similar to the bb_genrule we just wrote; however, this rule and its implementation both specify themselves as executable.
In the rule's implementation, we can see that we're providing the template for the shell script which will invoke babashka for us:
set -x
exec {bb} {src} {arguments} "$@"This will interpolate the babashka executable's path, our script file, and our command-line arguments together.
With this, we can define a basic "Hello" script in toolchains/babashka/hello.clj:
(ns hello)
(println "Hello!")Now we'll load the rule into our BUILD file and use it to define a target.
"""
Example usage of our custom babashka toolchain.
"""
load("//toolchains/babashka:bb_binary.bzl", "bb_binary")
load("//toolchains/babashka:bb_genrule.bzl", "bb_genrule")
bb_genrule(
name = "pwd",
out = "pwd.edn",
script = ":pwd.clj",
)
bb_genrule(
name = "reader",
out = "results.edn",
data = [
":pwd.edn",
],
script = ":reader.clj",
)
bb_binary(
name = "hello",
src = ":hello.clj",
)Now that we have an executable, we'll run it:
$ bazelisk run //toolchains/babashka:hello
INFO: Found 1 target...
Target //toolchains/babashka:hello up-to-date:
bazel-bin/toolchains/babashka/hello
INFO: Elapsed time: 7.235s, Critical Path: 0.01s
INFO: 2 processes: 4 action cache hit, 2 internal.
INFO: Build completed successfully, 2 total actions
INFO: Running command line: bazel-bin/toolchains/babashka/hello
++ exec ../+_repo_rules+babashka_osx/bb toolchains/babashka/hello.clj
Hello!Aside from the basic annotation to mark the script as an executable, Bazel leaves all of the implementation details up to us.
Now, to develop more parity with our Java and Python tooling, let's define a rule we can use with bazelisk test.
In Lesson 5, we learned how to execute test suites with bazelisk test to run the unit tests we defined for our standard library.
We'll continue to follow the conventions established with java_test and py_test by defining bb_test in toolchains/babashka/bb_test.bzl.
"""
Defines the bb_test rule, which makes babashka scripts testable via `bazelisk test`
"""
def _bb_test_impl(ctx):
executable = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = executable,
is_executable = True,
content = """
set -x
exec {bb} {src} {arguments} "$@"
""".format(
bb = ctx.executable._bb.short_path,
src = ctx.file.src.path,
arguments = " ".join(ctx.attr.arguments),
),
)
return DefaultInfo(
executable = executable,
runfiles = ctx.runfiles(files = [ctx.executable._bb, ctx.file.src]),
)
bb_test = rule(
doc = "Executes a babashka script as a test",
implementation = _bb_test_impl,
test = True,
attrs = {
"src": attr.label(
allow_single_file = [".clj"],
mandatory = True,
),
"arguments": attr.string_list(),
"_bb": attr.label(
executable = True,
allow_single_file = True,
cfg = "exec",
default = "@babashka_osx//:bb",
),
},
)To Bazel, tests are executables whose status code carries semantic meaning.
Much of this rule's implementation is the same as with bb_binary aside from the indication that we're defining a rule for defining tests.
Let's define a basic unit test in toolchains/babashka/example_test.clj
(ns example-test
(:require [clojure.test :refer :all]))
(deftest unit-test
(testing "A demonstrative unit test"
(is (= 1 1))))
(run-test unit-test)We'll load this rule to define a testable target:
"""
Example usage of our custom babashka toolchain.
"""
load("//toolchains/babashka:bb_binary.bzl", "bb_binary")
load("//toolchains/babashka:bb_genrule.bzl", "bb_genrule")
load("//toolchains/babashka:bb_test.bzl", "bb_test")
bb_genrule(
name = "pwd",
out = "pwd.edn",
script = ":pwd.clj",
)
bb_genrule(
name = "reader",
out = "results.edn",
data = [
":pwd.edn",
],
script = ":reader.clj",
)
bb_binary(
name = "hello",
src = ":hello.clj",
)
bb_test(
name = "example_test",
src = ":example_test.clj",
)Now, we'll execute our tests:
$ bazelisk test //toolchains/babashka:example_test
INFO: Analyzed target //toolchains/babashka:example_test (66 packages loaded, 591 targets configured).
INFO: Found 1 test target...
Target //toolchains/babashka:example_test up-to-date:
bazel-bin/toolchains/babashka/example_test
INFO: Elapsed time: 7.286s, Critical Path: 0.02s
INFO: 1 process: 6 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total action
//toolchains/babashka:example_test (cached) PASSED in 0.3s
Executed 1 out of 1 test: 1 test passes.
There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings command line option to see which ones these are.The output indicates the timeouts controlled by the size parameter we previously learned about are too large.
Since that is a default attribute for test rules, we can remove the warning by setting the attribute:
bb_test(
name = "example_test",
src = ":example_test.clj",
size = "small",
)And now we can re-execute the tests to see the warning removed:
$ bazelisk test //toolchains/babashka:example_test
INFO: Analyzed target //toolchains/babashka:example_test (66 packages loaded, 591 targets configured).
INFO: Found 1 test target...
Target //toolchains/babashka:example_test up-to-date:
bazel-bin/toolchains/babashka/example_test
INFO: Elapsed time: 7.286s, Critical Path: 0.02s
INFO: 1 process: 6 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total action
//toolchains/babashka:example_test (cached) PASSED in 0.3s
Executed 1 out of 1 test: 1 test passes.No that we have three rules built on top of the MacOS version of babashka, let's address the elephant in the room: How do we use the rules with other operating systems?
Because the babashka binary is platform dependent, we need some way to tell Bazel when it should use the OSX friendly version of babashka.
Thankfully, there is a first-class repo built to solve this problem.
Let's install it into MODULE.bazel:
bazel_dep(name = "platforms", version = "1.0.0")The platforms repo contains targets that can be used to specify different CPU and OS architectures.
To see what targets are available, let's query that repository.
bazelisk query "@platforms//..." --output=graph > visualizations/src/platforms_graph.gv
dot -Tpng < visualizations/src/platforms_graph.gv > visualizations/out/platforms_graph.pngWhich renders as:
These labels will help us define the constraints we'll use to tell Bazel when to use the appropriate executable.
To do that, we'll first declare repositories for a Windows and a Linux compatible version of babashka in MODULE.bazel:
http_archive(
name = "babashka_osx",
build_file_content = """exports_files(["bb"])""",
sha256 = "b02f906697b1ede016e96a3b340ea40e52380eef52307abc8a114210bfe89c44",
type = "tar.gz",
url = "https://github.com/babashka/babashka/releases/download/v1.12.209/babashka-1.12.209-macos-amd64.tar.gz",
)
http_archive(
name = "babashka_linux",
build_file_content = """exports_files(["bb"])""",
sha256 = "177c18b6ca00c1708070007a53777ef4c9ffe0bbaa2c66c1426312db645eeba9",
type = "tar.gz",
url = "https://github.com/babashka/babashka/releases/download/v1.12.209/babashka-1.12.209-linux-aarch64-static.tar.gz",
)
http_archive(
name = "babashka_windows",
build_file_content = """exports_files(["bb"])""",
sha256 = "bc9f2ecae2ffa198e0ca06a194ca6a58c11daf94c123af8429ee3d1f24017bf0",
type = "zip",
url = "https://github.com/babashka/babashka/releases/download/v1.12.209/babashka-1.12.209-windows-amd64.zip",
)We can query these repositories to see that they also contain the appropriate BUILD file and reference to the bb executable:
$ bazelisk query "@babashka_linux//...:*"
@babashka_linux//:BUILD.bazel
@babashka_linux//:bb
$ bazelisk query "@babashka_windows//...:*"
@babashka_windows//:BUILD.bazel
@babashka_windows//:bbNow we need to define a rule that provides a clean API to each of these binaries.
We'll define that API in toolchains/babashka/bb_toolchain.bzl
"""
Provides the correct version of babashka to our bb rules so they can execute across systems
"""
def _bb_toolchain(ctx):
return platform_common.ToolchainInfo(
bb = ctx.executable.bb,
)
bb_toolchain = rule(
doc = "Provides the correct architecture-dependent version of babashka",
implementation = _bb_toolchain,
attrs = {
"bb": attr.label(
executable = True,
allow_single_file = True,
cfg = "exec",
),
},
)This defines a bb_toolchain rule so we can provide a name to a ToolchainInfo instance- allowing us to connect the executable as a dependency of the rules dependant upon it.
We now need to update our BUILD file to link the three executables to names, and to then build toolchains to connect those executables to host system dependencies:
"""
Example usage of our custom babashka toolchain.
"""
load("//toolchains/babashka:bb_binary.bzl", "bb_binary")
load("//toolchains/babashka:bb_genrule.bzl", "bb_genrule")
load("//toolchains/babashka:bb_test.bzl", "bb_test")
load("//toolchains/babashka:bb_toolchain.bzl", "bb_toolchain")
bb_genrule(
name = "pwd",
out = "pwd.edn",
script = ":pwd.clj",
)
bb_genrule(
name = "reader",
out = "results.edn",
data = [
":pwd.edn",
],
script = ":reader.clj",
)
bb_binary(
name = "hello",
src = ":hello.clj",
)
bb_test(
name = "example_test",
src = ":example_test.clj",
size = "small",
)
toolchain_type(
name = "babashka_toolchain",
visibility = ["//visibility:public"],
)
bb_toolchain(
name = "bb_osx",
bb = "@babashka_osx//:bb",
)
toolchain(
name = "bb_osx_toolchain",
exec_compatible_with = [
"@platforms//os:macos",
],
toolchain = ":bb_osx",
toolchain_type = ":babashka_toolchain",
)
bb_toolchain(
name = "bb_linux",
bb = "@babashka_linux//:bb",
)
toolchain(
name = "bb_linux_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
],
toolchain = ":bb_linux",
toolchain_type = ":babashka_toolchain",
)
bb_toolchain(
name = "bb_windows",
bb = "@babashka_windows//:bb",
)
toolchain(
name = "bb_windows_toolchain",
exec_compatible_with = [
"@platforms//os:windows",
],
toolchain = ":bb_windows",
toolchain_type = ":babashka_toolchain",
)The toolchain rule allows us to specify execution compatibilities of our different binaries, defined with bb_toolchain, to the targets defined in the platforms repository.
Before we can use these Toolchains across the bite-sized-bazel repository, we need to perform one additional step- registration.
This is done in MODULE.bazel
register_toolchains(
"//toolchains/babashka:bb_osx_toolchain",
"//toolchains/babashka:bb_linux_toolchain",
"//toolchains/babashka:bb_windows_toolchain",
)Toolchains, like other Bazel targets, can also be given aliases.
For convenience, let's add an alias to the top-level BUILD file.
alias(
name = "babashka_toolchain",
actual = "//toolchains/babashka:babashka_toolchain",
visibility = ["//visibility:public"],
)Now we need to update each of our rules to use the babashka executable provided as a dependency from the toolchain instead of the executable we previously depended upon via @babashka_osx//:bb
We'll add a toolchain dependency on //:babashka_toolchain, and update the references to the babashka executable to refer to that dependency:
In bb_binary:
"""
Defines the bb_binary rule, which makes babashka scripts executable via `bazelisk run`
"""
def _bb_binary_impl(ctx):
toolchain = ctx.toolchains["//:babashka_toolchain"]
executable = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = executable,
is_executable = True,
content = """
set -x
exec {bb} {src} {arguments} "$@"
""".format(
bb = toolchain.bb.short_path,
src = ctx.file.src.path,
arguments = " ".join(ctx.attr.arguments),
),
)
return DefaultInfo(
executable = executable,
runfiles = ctx.runfiles(files = [toolchain.bb, ctx.file.src]),
)
bb_binary = rule(
doc = "Executes a babashka script",
implementation = _bb_binary_impl,
executable = True,
attrs = {
"src": attr.label(
allow_single_file = [".clj"],
mandatory = True,
),
"arguments": attr.string_list(),
},
toolchains = ["//:babashka_toolchain"],
)In bb_genrule:
"""
Defines the bb_genrule rule, which is a babashka-powered analogue to Bazel's genrule
"""
def _bb_genrule_impl(ctx):
toolchain = ctx.toolchains["//:babashka_toolchain"]
ctx.actions.run(
inputs = [ctx.file.script] + ctx.files.data,
outputs = [ctx.outputs.out],
executable = toolchain.bb,
arguments = [
ctx.file.script.path,
"""{{
:out-file "{out_file}"
:data [{data}]
}}""".format(
out_file = ctx.outputs.out.path,
data = " ".join(["\"{}\"".format(data.path) for data in ctx.files.data]),
),
],
)
bb_genrule = rule(
doc = "An alternative to the general genrule which uses babashka as the scripting language",
implementation = _bb_genrule_impl,
attrs = {
"script": attr.label(allow_single_file = [".clj"], mandatory = True),
"out": attr.output(mandatory = True),
"data": attr.label_list(allow_files = True),
},
toolchains = ["//:babashka_toolchain"],
)And lastly in bb_test:
"""
Defines the bb_test rule, which makes babashka scripts testable via `bazelisk test`
"""
def _bb_test_impl(ctx):
toolchain = ctx.toolchains["//:babashka_toolchain"]
executable = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = executable,
is_executable = True,
content = """
set -x
exec {bb} {src} {arguments} "$@"
""".format(
bb = toolchain.bb.short_path,
src = ctx.file.src.path,
arguments = " ".join(ctx.attr.arguments),
),
)
return DefaultInfo(
executable = executable,
runfiles = ctx.runfiles(files = [toolchain.bb, ctx.file.src]),
)
bb_test = rule(
doc = "Executes a babashka script as a test",
implementation = _bb_test_impl,
test = True,
attrs = {
"src": attr.label(
allow_single_file = [".clj"],
mandatory = True,
),
"arguments": attr.string_list(),
},
toolchains = ["//:babashka_toolchain"],
)We can re-use each of our previous targets to validate that the changes we've made continue to work.
Lastly, to celebrate a working toolchain for babashka, let's extend the greeter package to contain a Clojure variant.
For the basic greeting, we'll add a greeter/hello.clj:
(ns hello)
(let [concated-args (apply str *command-line-args*)]
(println (str "Hello, " concated-args)))Babashka, like Python, ships with a JSON content library embedded to write the JSON version of the greeter.
We'll do that in greeter/hello_json.clj:
(ns hello-json
(:require [cheshire.core :as json]))
(let [concated-args (apply str *command-line-args*)
greeting (str "Hello, " concated-args)]
(println (json/generate-string {:greeting greeting})))Now we need to define the appropriate tags and targets to leverage this code:
In tags.bzl:
"""
Stores definitions for common tags.
"""
# Language Specific Tags
java_tag = "java"
python_tag = "python"
babashka_tag = "babashka"In greeter/BUILD:
"""
Greet people in multiple languages!
"""
load("@rules_java//java:defs.bzl", "java_binary")
load("@rules_python//python:py_binary.bzl", "py_binary")
load("//:tags.bzl", "babashka_tag", "java_tag", "python_tag")
load("//rules:greeter.bzl", "greeter")
load("//rules:json_greeter.bzl", "json_greeter")
load("//toolchains/babashka:bb_binary.bzl", "bb_binary")
py_binary(
name = "python",
srcs = [
"hello.py",
],
main = "hello.py",
tags = [python_tag],
deps = [
"//libraries/stdlib/python:strings",
],
)
py_binary(
name = "python_json",
srcs = [
"hellojson.py",
],
main = "hellojson.py",
tags = [python_tag],
deps = [
"//libraries/stdlib/python:strings",
"@pypi//json_lineage:pkg",
],
)
java_binary(
name = "java",
srcs = [
"Hello.java",
],
main_class = "Hello",
tags = [java_tag],
deps = [
"//libraries/stdlib/java:strings",
],
)
java_binary(
name = "java_json",
srcs = [
"HelloJson.java",
],
main_class = "HelloJson",
tags = [java_tag],
deps = [
"//libraries/stdlib/java:strings",
"@maven//:com_google_code_gson_gson",
],
)
genquery(
name = "java_json_deps",
expression = "deps(//greeter:java_json)",
scope = [":java_json"],
tags = [java_tag],
)
genquery(
name = "python_json_deps",
expression = "deps(//greeter:python_json)",
scope = [":python_json"],
tags = [python_tag],
)
greeter(
name = "bazel",
username = "Nick",
)
json_greeter(
name = "bazel_json",
username = "Nick",
)
bb_binary(
name = "babashka",
src = ":hello.clj",
tags = [babashka_tag],
)
bb_binary(
name = "babashka_json",
src = ":hello_json.clj",
tags = [babashka_tag],
)Now, let's run our greeters:
$ bazelisk run //greeter:babashka -- Nick
INFO: Found 1 target...
Target //greeter:babashka up-to-date:
bazel-bin/greeter/babashka
INFO: Elapsed time: 7.624s, Critical Path: 0.01s
INFO: 1 process: 5 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/greeter/babashka <args omitted>
++ exec ../+_repo_rules+babashka_osx/bb greeter/hello.clj Nick
Hello, Nick
$ bazelisk run //greeter:babashka_json -- Nick
INFO: Analyzed target //greeter:babashka_json (0 packages loaded, 2 targets configured).
INFO: Found 1 target...
Target //greeter:babashka_json up-to-date:
bazel-bin/greeter/babashka_json
INFO: Elapsed time: 0.120s, Critical Path: 0.00s
INFO: 1 process: 5 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/greeter/babashka_json <args omitted>
++ exec ../+_repo_rules+babashka_osx/bb greeter/hello_json.clj Nick
{"greeting":"Hello, Nick"}We've added support for an entirely new programming language, so let's see the impact on the target graph:
bazelisk query "//..." --output=graph > visualizations/src/toolchain_graph.gv
dot -Tpng < visualizations/src/toolchain_graph.gv > visualizations/out/toolchain_graph.pngWhich renders as:
Most importantly, we can see each of our architecture specific executables rolling up into our toolchain. Like the dependencies we build between libraries and executables, we're using Bazel to define the tool relationships internal into the functionality we provide to consumers. When operating with Bazel, everything is built on top of a dependency graph- and we're able to use a small, focused set of consistent tools to inspect that graph. We'll continue to rely upon these attributes moving forward, but for now, we'll call this lesson complete.
To compare your progress, you can view these changes on GitHub.
Previous - Lesson 9: Symbolic Macros | Next - Lesson 11: Advanced Toolchains
This repository, documentation, and all included code is licensed under the MIT License. See the LICENSE file for more details.
Please consider supporting this repository.
Return to the repository here.
- Home
- Glossary
- Further Reading
- Lessons
- Lesson 1: Installing Bazel
- Lesson 2: Creating Packages
- Lesson 3: Building Artifacts
- Lesson 4: Dependency Management
- Lesson 5: Executing Tests
- Lesson 6: General Rules
- Lesson 7: Authoring Rules
- Lesson 8: Build Tools
- Lesson 9: Symbolic Macros
- Lesson 10: Toolchains
- Lesson 11: Advanced Toolchains
- Lesson 12: Stardoc
- Lesson 13: Aspects
- Lesson 14: Modifying The Source Tree
- Repository States

