@ManekiNekko

Supercharge your builds with Bazel

 This is an introduction to Bazel.

Bazel might not solve all of your issues! But it's worth trying.

DISCLAIMER

So, What! Another build tool? Why?

Wassim Chegham

#Always_Bet_On_JavaScript

Microsoft

Senior Developer Advocate

Node.js Foundation Invited Member

Angular team Member Alumni

Angular Universal team Alumni

@ManekiNekko

Bazel Web Team contributor

I love open source...

https://wassim.dev

hexa.run

angular.run

xLayers.dev

nitr.ooo

ngx.tools

@ManekiNekko

We have a problem...

@ManekiNekko

"The JavaScript build process is broken"

@ManekiNekko

"The build time is proportional to the numbers of files."

@ManekiNekko

"Why is it taking ages to rebuild my changes?"

@ManekiNekko

"It builds on my machine but why the CI/CD is failing?"

@ManekiNekko

So, What if...

@ManekiNekko

"The build time is proportional to the numbers of changes."

@ManekiNekko

"My changes take 2 seconds* to rebuild!"

* on average!

@ManekiNekko

"My code builds the same, EVERYWHERE."

@ManekiNekko

Bazel!

Bazel is a polyglot, highly distributed build and test orchestration tool.

@ManekiNekko

Bazel doesn't replace your build tools. It gives them superpowers!

@ManekiNekko

Bazel benefits

Incremental

Deterministic

Hermetic

Composable

Industrial grade

@ManekiNekko

Universally Fullstack

Android, C & C++, C#, D, Docker, Go, Groovy, Kotlin, iOS, Java, JavaScript, Jsonnet, Objective, OCaml, C, Proto Buffers, Python, Rust, Sass, Scala, Shell, TypeScript, etc...

@ManekiNekko

They're using Bazel

@ManekiNekko

Let's dive into Bazel concepts.

1. Workspaces

2. Packages

3. Targets

4. Labels

5. Rules

6. Actions

A folder containing the source files of your project (or sub-projects) and has a file named                .

@ManekiNekko

1. The Bazel workspace

WORKSPACE

@rules_nodejs// on master and ⬢ v13.7.0
➜ find ./ -iname "WORKSPACE"
.//WORKSPACE
.//examples/angular/WORKSPACE
.//examples/kotlin/WORKSPACE
.//examples/angular_view_engine/WORKSPACE
.//examples/nestjs/WORKSPACE
.//packages/labs/src/WORKSPACE
.//packages/typescript/src/WORKSPACE
.//packages/karma/src/WORKSPACE
.//packages/rollup/src/WORKSPACE
.//packages/terser/src/WORKSPACE
.//packages/jasmine/src/WORKSPACE
.//packages/protractor/src/WORKSPACE
.//e2e/jasmine/WORKSPACE
...

@ManekiNekko

1. The                file

WORKSPACE

Defines, loads and setups all the dependencies of the current workspace (external repositories).

@ManekiNekko

1. The                file

WORKSPACE

workspace(name = "angular_bazel_example")

# This rule is built-into Bazel but we need to load it first to download more rules
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

# Fetch rule implementation as an archive
http_archive(
    name = "build_bazel_rules_nodejs",
    sha256 = "9473b207f1c5a61b603442cbfeeea8aaf2aa62870673fce2a1c52087f6ff4dc9",
    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.2.4/rules_nodejs-1.2.4.tar.gz"],
)

# Load rule definition
load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")

# Invoke rule
npm_install(
    name = "npm",
    package_json = "//:package.json",
    package_lock_json = "//:package-lock.json"
)

# Fetch rule implementation from a GIT repo
git_repository(
    name = "io_bazel_rules_k8s",
    commit = "d72491924369868a13532af9f15cb766e5943855",
    remote = "https://github.com/bazelbuild/rules_k8s.git",
)

# Load rule definition
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_defaults")

# Invoke rule
k8s_repositories()

@ManekiNekko

1. A                file example

WORKSPACE

1. workspaces

2. Packages

3. Targets

4. Labels

5. Rules

6. Actions

A package defines a set of instructions about how to build, test and run the current package.

@ManekiNekko

2. A Bazel package

The primary unit of code organization in a workspace. A folder containing a file named          or                   .  

@ManekiNekko

2. A Bazel package

BUILD

BUILD.bazel

@rules_nodejs// on master and ⬢ v13.7.0
➜ find ./ -iname "BUILD.bazel"
./internal/BUILD.bazel
./internal/linker/test/BUILD.bazel
./internal/linker/BUILD.bazel
./internal/pkg_npm/test/BUILD.bazel
./internal/pkg_npm/BUILD.bazel
./internal/golden_file_test/BUILD.bazel
./internal/providers/BUILD.bazel
./internal/js_library/BUILD.bazel
./internal/common/test/BUILD.bazel
./internal/common/BUILD.bazel
./internal/bazel_integration_test/BUILD.bazel
./internal/node/test/lib1/BUILD.bazel
./internal/node/test/BUILD.bazel
...

@ManekiNekko

1. The                   files

BUILD.bazel

package(default_visibility = ["//visibility:public"])
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary")
load("@io_bazel_rules_rust//wasm_bindgen:wasm_bindgen.bzl", "rust_wasm_bindgen")

rust_binary(
    name = "hello_world_rust",
    srcs = ["main.rs"],
    edition = "2018",
    deps = [
        "@io_bazel_rules_rust//wasm_bindgen/raze:wasm_bindgen"
    ]   
)

rust_wasm_bindgen(
    name = "hello_world_bindgen",
    wasm_file = ":hello_world_rust",
    bindgen_flags = ["--nodejs"]
)

@ManekiNekko

2. Package content

Keep packages as fine-grained as possible to improve parallelism!

@ManekiNekko

2. Package PRO TIP #1

1. workspaces

2. Packages

3. Targets

4. Labels

5. Rules

6. Actions

A set of directives defined in a Bazel package (                  ), describing how to build, test, and run the current package.

@ManekiNekko

3. Bazel targets

BUILD.bazel

package(default_visibility = ["//visibility:public"])
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary")
load("@io_bazel_rules_rust//wasm_bindgen:wasm_bindgen.bzl", "rust_wasm_bindgen")

rust_binary(
    name = "hello_world_rust",
    srcs = ["main.rs"],
    edition = "2018",
    deps = [
        "@io_bazel_rules_rust//wasm_bindgen/raze:wasm_bindgen"
    ]   
)

rust_wasm_bindgen(
    name = "hello_world_bindgen",
    wasm_file = ":hello_world_rust",
    bindgen_flags = ["--nodejs"]
)

@ManekiNekko

3. Bazel targets

1. workspaces

2. Packages

3. Targets

4. Labels

5. Rules

6. Actions

@angular\_bazel\_example

Workspace name

Package

Target

src/app/billing
:billing
//

Workspace Root

@ManekiNekko

4. Labels

A globally unique identifier of a target.

@angular\_bazel\_example//src/app/billing:billing
//src/app/billing:billing === //src/app/billing
//src/app/...:billing
//...

@ManekiNekko

4. Label examples

//src/...

1. workspaces

2. Packages

3. BUILD files

4. Labels

5. Rules

6. Actions

The relationship between inputs and outputs, and the steps to build the outputs.

@ManekiNekko

5. A Bazel rule

# //src/lib/shorten/BUILD.bazel

ts_library(
    name = "shorten",
    srcs = ["shorten.ts"],
    module_name = "@bazel/shorten",
    module_root = "shorten",
    deps = [
        "//some/dep",
        "@npm//other/dep",
    ],
)

# Output: 
# - shorten.d.ts
# - shorten.js

@ManekiNekko

5. Rule example

@angular/core/**/*.ts

./core.js

./core.d.ts

./bundles/core.umd.js

5. Rule Composability

@ManekiNekko

ts_library()

rollup_bundle()

1. workspaces

2. Packages

3. BUILD files

4. Labels

5. Rules

6. Actions

"Actions" describe the steps to generate a set of outputs from a set of inputs. The internal implementation of a "Rule".

6. A Bazel action

@ManekiNekko

tsc -p tsconfig.json ...

@ManekiNekko

6. A Bazel action

ts_library()

action(x)=y

Let's write our own Rule...

Starlark Language - 101

def fizz_buzz(n):
  """Print Fizz Buzz numbers from 1 to n."""
  for i in range(1, n + 1):
    s = ""
    if i % 3 == 0:
      s += "Fizz"
    if i % 5 == 0:
      s += "Buzz"
    print(s if s else i)

fizz_buzz(20)

NO: class, import, while, yield, lambda and nested functions, try, raise, except, finally...

Starlark’s syntax is inspired by Python3

# index.bzl
def _empty_impl(ctx):
    print("This rule does nothing")

empty = rule(implementation = _empty_impl)
#-----------------------------------------

# BUILD.bazel
load("//:index.bzl", "empty")

empty(name = "nothing")
$ bazel build //:nothing

Anatomy of a Rule

$ ls
.
├── WORKSPACE
├── index.bzl
└── BUILD.bazel

Let's transpile TypeScript files

tsc file-1.ts file-2.ts  \
    --target es6 \
    --outFile main.bundle.js
# WORKSPACE
workspace(name = "tsc_binary_example")

# BUILD.bazel
load("//:index.bzl", "tsc_binary")

tsc_binary(
    name = "bazel_tsc",
    srcs = [
        ":file-1.ts",
        ":file-2.ts"
    ],
)

# index.bzl
# See next slide...

tsc_binary

$ tree 
.
├── WORKSPACE
├── BUILD
├── file-1.ts
├── file-2.ts
└── index.bzl
script_template = """\
#!/bin/bash
tsc {ts_files} --target es6 --outFile {out_file}
"""

def _tsc_binary_impl(ctx):
    output = ctx.actions.declare_file("%s.sh" % ctx.label.name)
    script_content = script_template.format(
        out_file = "%s.bundle.js" % output.short_path,
        ts_files = " ".join([f.path for f in ctx.files.srcs]),
    )
   
    # tsc file-1.ts file-2.ts --target es6 --outFile bazel_tsc.sh.bundle.js
    ctx.actions.write(output, script_content, is_executable = True)

    runfiles = ctx.runfiles(files = ctx.files.srcs)
    return [DefaultInfo(executable = output, runfiles = runfiles)]

tsc_binary = rule(
    implementation = _tsc_binary_impl,
    attrs = {
        "srcs": attr.label_list(
            allow_files = True,
            doc = "Input TypeScript files",
        ),
    },
    executable = True,
)

Rule implementation

tsc_binary

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

Bazel Execution Graph

@ManekiNekko

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

Bazel Execution Graph

@ManekiNekko

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

Bazel Execution Graph

@ManekiNekko

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

Bazel Execution Graph

@ManekiNekko

Bazel Cache Graph

@ManekiNekko

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

ACTION

Bazel Cache Graph

@ManekiNekko

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

Bazel Cache Graph

@ManekiNekko

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

Bazel Cache Graph

@ManekiNekko

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

Bazel Cache Graph

@ManekiNekko

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

SHA1

Bazel Local Cache & Execution

code

local BAZEL's cache

local BAZEL execution

@ManekiNekko

code

Remote BAZEL's cache

remote BAZEL execution

Dynamic BAZEL execution

Remote Cache* & Execution*

@ManekiNekko

Bazel Query Language

bazel query 'deps(//...)' --output graph | dot -Tpng > docs/graph.png

@ManekiNekko

Demo: Compile RUST to WASM

Demo: Build an AZURE Function

YOUR TURN! Try Bazel today!

dev.to/wassimchegham

dev.to/angular

dev.to/bazel

Supercharge your builds with Bazel

By Wassim Chegham

Supercharge your builds with Bazel

This is a full introduction to Bazel, the orchestration build and test tool. We will cover the concepts of Bazel such as Workspaces, Packages, and Labels, as well as writing custom rules. We will demo how to build a WASM code written in Rust and deploy it on Azure Functions.

  • 7,704