What is Bazel?

bazel

#1

What is Bazel???

Introduction

Currently I am working on some project using Tensorflow API in C++. Before starting, I thought this was not going to be so tough. Yet, as soon as began, It turned out that tons of thousands of warnings/errors awaited. Basically most of them were related with a build tool provided by Google. So for the sake of efficiency, why not start learning Bazel, which is the build tool we are using now.

Official Document So in order to enhance my understanding, in this post, I would like to walk you through this tutorial.

Git Repo for this tutorial!

  • Estimated completion time: 30 minutes.

What you will learn

At the end of this post, you will be able to

  1. Build a target
  2. Visualize the project’s dependencies
  3. Split the project into multiple targets and packages
  4. Control target visibility across packages
  5. Reference targets through labels

Contents

  • Before you begin
    • Install Bazel
    • Get the sample project
  • Build with Bazel
    • Set up the workspace
    • Understand the BUILD file
    • Build the project
    • Review the dependency graph
  • Refine your Bazel build
    • Specify multiple build targets
    • Use multiple packages
  • Further reading

Before you begin

Install Bazel In case, you have not obtained the bazel itself yet, please try the command below.

$ git clone https://github.com/bazelbuild/examples/

And the directory architecture is set like below.

Get the sample project As you can see, this tutorial is split into 3 stages.

  1. building a single target residing in a single package.
  2. splitting your project into multiple targets but keep it in a single package.
  3. splitting your project into multiple packages and build it with multiple targets.

Build with Bazel

Set up the workspace Before you can build a project, you need to set up its workspace. A workspace is a directory that holds your project’s source files and Bazel’s build outputs. It also contains files that Bazel recognizes as special:

  • WORKSPACE file, which defines the directory and its contents as Bazel workspaces, and serves as the root of the projects structures.
  • BUILD files under some directory, which tell Bazel how to build different parts of the project.(we are going to elaborate this later)

All in all, to designate the project as Bazel-ish, we need to put the blank file named WORKSPACE in that directory.

  • Bear in mind that once Bazel builds the project, all inputs and dependencies must be in the same workspace. Files residing in different workspaces are independent of one another unless linked, which is beyond the scope of this tutorial.

Understand the BUILD file Take a look at the BUILD file in the cpp-tutorial/stage1/main directory. BUILD file is containing several rules telling Bazel how to build the projects. And each rule is called a target.


cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

In this script, the rule(target) of name is hello-world target, and srcs specifies the source file(s) from which Bazel builds the target. The rule tells Bazel to build a self-contained executable binary from the hello-world.cc source file with no dependencies.

Build the project

$ bazel build //main:hello-world

Notice: //main means the location of our BUILD file indicating the root directory of the project, and hello-world is the name of target defined before!

OMG… I got stuck with this error

Problem with java installation: couldn't find/access rt.jar in /Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home

But I could solve the problem by following command. $ brew upgrade bazel

Anyway, once Bazel builds the project, you would receive this output Screen Shot 2018-03-31 at 15.08.03.png Congrats!! You have just finished building your Bazel project! Now bazel places the outputs of building on the directory under the bazel-bin. So if you execute this command on your terminal, your first programmed builded by Bazel says “Hello World”. bazel-bin/main/hello-world

Review the dependency graph So far, You have made your own workspace and prepared the BUILD file, then run the bazel to build the project. I guess you want more, but before diving into deeper, let us take a time to consider the output of this build a bit. A successful build has all dependencies explicitly stated in BUILD file. Bazel follows those statements to create the project dependency graph. Let’s visualise our sample project dependencies.

$ bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph

While you get these output, no need to read it, instead, go to this page and paste them in the text area! GraphViz

As you can see, the project composes a single target that builds a single source file. Screen Shot 2018-03-31 at 15.19.19.png

Refine your Bazel build

Even though sometime a single target should be fine, mostly you may want to have a larger project than what we have done so far.

So let’s move on to the second stage! Specify multiple build targets Browse through the content in BUILD of cpp-tutorial/stage2/main.

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

With this file, bazel firstly works on hello-greet then move on to hello-world. You might wonder about deps attribute though, it tells bazel that hello-greet library is required to build the hello-world binary.

Let’s build the second project!

$ bazel build //main:hello-world

Output should be like this, Screen Shot 2018-03-31 at 15.35.52.png Congrats again! Let’s test our fresh programme.

$ bazel-bin/main/hello-world

Output should be, Hello World.

Same?? yes, because if you look at the hello-world.cc, it gives string variable to hello-greet.cc which returns hello + string.

If you now modify hello-greet.cc and rebuild the project, Bazel will only recompile that file. So why not try?

I put one more element to be returned. hello-greet.cc

#include "hello-greet.h"
#include <string>

std::string get_greet(const std::string& who) {
  return "Hello " + who + "from Japan!";
}

And let’s build the project again! Screen Shot 2018-03-31 at 15.42.51.png This time, bazel detects the point modified and the file, then builds only it! That is why the build time faster than before!! And let’s see how the dependency graph is! Run the command and paste the output on Graphviz

bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph

Screen Shot 2018-03-31 at 15.45.30.png You’ve now built the project with two targets. The hello-world target builds one source file and depends on one other target (//main:hello-greet), which builds two additional source files.

Use multiple packages

We have experienced building methods for a single and multi targets. Now it is the time to go more practical. This time, we are going to go through the case where the packages are split into 2. Screen Shot 2018-03-31 at 15.49.04.png Notice now we have 2 sub-directories containing BUILD respectively.

lib/BUILD

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

main/BUILD

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

There is a new keyword here, visibility in lib/BUILD file. we make the //lib:hello-time target in lib/BUILD explicitly visible to targets in main/BUILD using the visibility attribute. This is because by default targets are only visible to other targets in the same BUILD file.

Before building, let me explains the dependency graph first since I believe that the clear picture on your mind enhances the understanding. Screen Shot 2018-03-31 at 15.56.00.png

As you can see, the hello-world target in the main package depends on the hello-time target in the lib package (hence the target label //lib:hello-time) - Bazel knows this through the deps attribute. Take a look at the dependency graph.

Now let’s build the final project.

$ bazel build //main:hello-world

And feel free to try visualising the dependencies! However, I would like to off the track a bit and tweak lib/BUILD a bit.

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
)

Now I have removed the visibility keyword, and let’s see what will happen. *sorry the error log was too long, so the image gets shrunk.

As you can see, it is saying that

Target '//lib:hello-time' is not visible from target '//main:hello-world'

This is exactly what I wanted! So without visibility defined in BUILD file of its dependent directories, Bazel is not able to build.

Tips

What is .bazelrc? check this part .bazelrc, the Bazel configuration file, the --bazelrc=file option, and the --config=value option

from this link: https://docs.bazel.build/versions/master/user-manual.html

Common Error Unexpected error reading .blazerc file ‘/Users/norio.kosaka/Desktop/tensorflow/.tf_configure.bazelrc’

Solution: as it describes here, just remove .bazelrc!!

Further Readings

https://docs.bazel.build/versions/master/external.html https://docs.bazel.build/versions/master/be/overview.html https://docs.bazel.build/versions/master/tutorial/java.html

Thank you for your patience!!