-
Notifications
You must be signed in to change notification settings - Fork 38
Building Ruby with Bazel
Here we document the various ways that we bridge Bazel and RulesRuby together.
In your WORKSPACE
file bring Ruby Toolchain with:
# file: //WORKSPACE
workspace(name = "my_awesome_workspace")
# We need this to load ruby_rules
load(
"@bazel_tools//tools/build_defs/repo:git.bzl",
"git_repository",
)
git_repository(
name = "bazelruby_ruby_rules",
branch = "develop", # for now is the primary branch
remote = "https://github.com/bazelruby/rules_ruby.git",
)
load(
"@bazelruby_ruby_rules//ruby:deps.bzl",
"ruby_register_toolchains",
"ruby_rules_dependencies",
)
Next, in the WORKSPACE
file we must call the following two functions:
ruby_rules_dependencies()
# version here can ONLY be one of: "host", "2.6.5" and "2.6.3"
ruby_register_toolchains(version = "2.6.5")
Note that this is where you'd declare your Ruby version, which is very important:
-
If you selected a numeric version, AND your host Ruby interpreter matches that version, then the toolchain switches to the "host" mode, using your pre-installed Ruby. This is a performance compromise, because this allows you to skip building Ruby interpreter which takes a long time.
-
If the versions are not a match, or if there is no Ruby installed, the toolchain downloads and compiles Ruby Interpreter (but only one of the two mentioned versions for now), and uses that SDK moving forward, with a couple of caveats:
-
When you wipe Bazel's Cache you will, most likely, have to wait for Bazel to rebuild Ruby Interpreter again.
-
Currently only two versions of Ruby are supported: 2.6.3 and 2.6.5.
-
Third party dependencies can only be loaded by a repository rule according to Bazel design, thus exists ruby_bundle
repository rule that is made to do just that.
Ideally, in a large Ruby mono-repo, all ruby projects will be sharing one single top-level Gemfile
and a corresponding Gemfile.lock
, as well as the .ruby-version
. Check those in at the top level, and then you can reference individual gems or the entire gem set from your projects.
The ruby_bundle
rule can only be used in WORKSPACE
file, but it can be specified more than once — to create several non-overlapping bundles of gems. This can be useful to ease the migration path, but ultimately you wont benefit from Bazel's speed and caching as much if each project relies on its own bundle, as compared to sharing a single Gem bundle defined up top.
Here is the rule that registers that:
load("@bazelruby_ruby_rules//ruby:defs.bzl", "ruby_bundle")
ruby_bundle(
name = "main_bundle",
gemfile = "//:Gemfile",
gemfile_lock = "//:Gemfile.lock",
bundler_version = "2.1.2",
visibility = ["//visibility:public"],
)
This will install Bundler version 2.1.2, and then install all of the gems in that bundle.
Your individual Ruby projects won't need every single dependency from this file, so you would register a dependency on an external gem in your BUILD file by referencing this top-level bundle
label, and then selecting a subset of gems under it.
We'll look into it in the next section.
Bazel package is any directory with a BUILD file in it. A Ruby BUILD file may look something like this:
# file: //foo/BUILD
package(default_visibility = ["//:__subpackages__"])
load(
"@bazelruby_ruby_rules//ruby:defs.bzl",
"ruby_binary",
"ruby_library",
"ruby_rspec",
)
ruby_library(
name = "lib",
srcs = glob(["lib/**/*.rb", "bin/foo"]),
includes = ["lib"], # this is the directory added to $LOAD_PATH, but it must be
# declared as absolute in relation to the top level workspace.
deps = [
"@main_bundle//:awesome_print", # these gems must be present in the top level Gemfile
"@main_bundle//:tty-ui", # but then you can include them here like so.
"@main_bundle//:foo-bar",
],
)
ruby_binary(
name = "bin",
srcs = ["bin/foo"],
main = "bin/foo",
args = [
"-f",
"config.ru"
"--logging=enabled"
],
env = {
"MALLOC_ARENA_MAX": 2 # set the environment
},
deps = [
"//foo:lib", # we depend on the library created above
"@main_bundle//:thor", # and some gems, some of which may be different.
"@main_bundle//:awesome_print",
"@main_bundle//:tty-ui",
"@main_bundle//:foo-bar",
],
)
So effectively each build target has to declare explicit dependencies on all sources, executables as well as the bundled gems one by one.
This may get automated or auto-generated soon, but for now this mimics closely to how Bazel NPM rules function.
In addition to the standard ruby_test
rule (which works just like the ruby_binary
rule, but is interpreted by Bazel as a test result), there is a specialized macro ruby_rspec
which instantiates ruby_rspec_test
rule. This macro allows you to run rspecs on a folder or a set of files with minimal number of lines of code.
**Make sure you have rspec
and rspec-its
gem defined in your Gemfile.lock
.
# BUILD.bazel
load(
"@bazelruby_ruby_rules//ruby:defs.bzl",
"ruby_binary",
"ruby_library",
"ruby_rspec",
"ruby_test",
)
filegroup(
name = "sources",
srcs = glob([
"lib/**/*.rb",
"app/**/*.rb",
]),
data = glob([
"config/**/*",
]),
)
filegroup(
name = "specs",
srcs = glob([
"spec/**/*.rb",
]),
data = glob([
"spec/fixtures/**/*.yml"
)],
)
ruby_rspec(
name = "rspec",
srcs = [
":sources",
":specs",
],
rspec_args = {
"--format": "progress", # this is how we can override rspec's default documentation format
},
specs = ["spec"], # this will run rspec on the entire folder.
deps = [
"@bundle//:awesome_print",
"@bundle//:colored2", # NOTE: `rspec` and `rspec-its` gems are automatically added to the dependency list.
],
)
Yes, today many things may break. You might encounter issues with:
- gems requiring native extensions compilation
- gems loading stuff from other gem's sources
- Ruby programs that use
__dir__
instead of__FILE__
for requiring source files.
More updates will be added here as they available. Thanks!
We are still ironing out the kinks on how to deal with the gems in general and in particular bundled gems.
There may be breaking changes in the future.
All contents is distributed under the Apache 2.0 License, and is © 2017-2021 bazel ruby authors.
To join:
-
subscribe to our mailing list
-
signup and introduce yourself on the #ruby channel in the public Slack