Understanding the Starter Project
The create_claro_project.sh
script generated several files:
Fig 1:
demo
|-- .bazelrc
|-- .bazelversion
|-- MODULE.bazel
|-- README.md
`-- example
|-- BUILD
|-- demo.claro
`-- input.txt
1 directory, 7 files
Let's take a brief tour through each file to get a high level understanding of what's going on.
If you're already familiar with Bazel, jump ahead to writing your first Claro program.
MODULE.bazel
See: Official Bazel reference - This file marks the root of your Bazel project.
Fig 2:
module(name = "example-claro-module")
bazel_dep(name = "claro-lang", version = "0.1.509")
module(name = "example-claro-module")
This is the one place where you'll see the term "module" overloaded to refer to Bazel's concept of
Modules relating to Bazel's external package
management solution. So, the name you pick for your top-level module(name = "...")
declaration should be something
that you would be ok with using to publicly present your project to downstream users if you chose to publish your
project to the Bazel Central Registry later on.
bazel_dep(name = "claro-lang", version = "0.1.409")
This file is where you will declare your external dependencies for Bazel to resolve at build time. Every Claro project
will need to declare an external dependency on the claro-lang
project to get access to the Build Rules (e.g.
claro_binary()
and claro_module()
) as well as the compiler itself. Keeping your installation of Claro up-to-date is
as simple as bumping the version number listed here.
Claro has been published to the BCR at https://registry.bazel.build/modules/claro-lang. Check for new releases there to make sure that you're using the latest and greatest.
.bazelversion
See: Official Bazelisk reference - This file configures Bazelisk to use the declared Bazel version.
Fig 3:
6.4.0
Claro depends on Bzlmod which was introduced in Bazel version 6, so you'll need to use at least version 6.
.bazelrc
See: Official Bazel reference - This file is used to configure optional Bazel flags.
Fig 4:
common --enable_bzlmod
common --java_runtime_version=remotejdk_11
build --java_language_version=11
build --tool_java_language_version=11
common --enable_bzlmod
This configures Bazel to opt in to enabling the Bzlmod, external package manager. This will be necessary in all Claro projects to at least enable Bazel to resolve your dependency on the Claro compiler.
common --java_runtime_version=remotejdk_11
This configures Bazel to download a remote version of the JVM to execute compiled Claro programs. Technically, you can opt in to using a local Java install, but keeping this flag as is ensures that you're running a JVM version that Claro's actually been tested against.
BUILD
See: Official Bazel reference - BUILD files are the fundamental building block of a Bazel project. Here you'll define "build targets" representing components of your program and their dependencies.
Fig 5:
load("@claro-lang//:rules.bzl", "claro_binary")
claro_binary(
name = "demo_bin",
main_file = "demo.claro",
resources = {
"Input": "input.txt",
}
)
load("@claro-lang//:rules.bzl", "claro_binary")
This loads (a.k.a. "imports") the claro_binary()
Build Rule from the rules.bzl
Bazel extension file located in the root directory
of the claro-lang
project. After this load
, you're able to define claro_binary()
targets in this BUILD file by
calling it just as you would a function in any other programming language (albeit with mandatory named parameters).
claro_binary(...)
As mentioned above, this declares a build target that represents an executable Claro program (*_binary()
is the
conventional naming of executable build targets in the Bazel ecosystem).
name = "demo_bin"
All Bazel build targets include a mandatory name = "..."
parameter - in combination with the full path from the
project root, this specific build target can be uniquely referenced as //example:demo_bin
. Using this name, you can
execute Bazel build/run commands from the command line.
You can build the target to have Bazel invoke the Claro compiler to verify that your program is valid and if so generate the executable program artifacts that can be invoked separately:
bazel build //example:demo_bin
During local development you can directly build and run the target by using the below command which will trigger Bazel to build the target and then upon success invoke the built executable program automatically:
bazel run //example:demo_bin
main_file = "demo.claro"
Claro programs begin execution by running top-level statements of a given "main file" top-down, rather than looking for
some special main
function.
resources = { "Input": "input.txt", }
This declares that this program should bundle the file input.txt
into the final compiled Jar file so that it's
available at runtime no matter where the program is run. It makes this resource file available as resources::Input
in
the compiled program. Find more details about resources in the Reference Guide.
input.txt
Just a resource file read by the demo program.
Fig 6:
look ma, no hands!
demo.claro
The main Claro file that contains the code to be executed.
Fig 7:
resources::Input
|> files::readOrPanic(^)
|> strings::trim(^)
|> strings::toUpperCase(^)
|> wrapInBox(^)
|> print(^);
function wrapInBox(s: string) -> string {
var line = strings::repeated("-", len(s) + 4);
return "{line}\n| {s} |\n{line}";
}
This program just reads in the contents of the input.txt
resource file, trims extra
whitespace, converts it to all caps, wraps it in a box of "-" characters, and prints it to stdout.
Note the calls to functions like files::readOrPanic
and strings::trim
are calling into functions declared in dep
modules. In this case there's no explicit mention of those dependencies in the claro_binary(...)
target declaration
because files
and strings
are modules in the
stdlib so no explicit
dependency is necessary.