rules_nixpkgs
use
This is a run-down of tips and tricks for the use of rules_nixpkgs
, a rule set for bringing in nix packages for use in bazel
.
I got involved with the use of rules_nixpkgs
as a result of my work on hermetic, ephemeral and reproducible builds. My additions to the process are in the repository bazel_local_nix
, which allows an ephemeral nix installation, only in service of the current bazel compilation.
Per-project .bazelrc
settings
Here’s the .bazelrc
file I place in the root directory of my projects:
# Add your local user settings to file `user.bazelrc`
try-import %workspace%/user.bazelrc
# Add project-wide settings below.
common --host_platform=@rules_nixpkgs_core//platforms:host
common --incompatible_enable_cc_toolchain_resolution
# Temporarily, to include WORKSPACE.bzlmod in definitions.
common --enable_workspace
Here’s a rundown of the settings:
try-import %workspace%/user.bazelrc
I find it useful to allow injecting a user-specific file user.bazelrc
to the project root for various purposes. One example purpose is to test settings without.
I add user.bazelrc
to the project’s .gitignore
, so that each user can have their own private settings where needed. Of course, ideally we want to have as few settings there as possible.
common --host_platform=@rules_nixpkgs_core//platforms:host
This setting forces the use of the toolchains configured by rules_nixpkgs
in compilation.
common --incompatible_enable_cc_toolchain_resolution
This setting uses Bazel’s new toolchain resolution mechanism which is based on the use of platforms, and the starlark-based C++ compilation rules from rules_cc
, instead of the built-in C++ compilation rules.
common --enable_workspace
This setting keeps the WORKSPACE
files enabled even in builds using Bazel version 8 and higher. Without this setting, workspace files (WORKSPACE
, or WORKSPACE.bzlmod
) will be ignored. This will make it harder to use any rules or dependencies which have not been migrated to support the bzlmod repo configuration.
MODULE.bazel
file
module(
name = "raylib",
version = "0.0.1",
)
bazel_dep(name = "googletest", version = "1.16.0", repo_name = "gtest")
# This is a lie, this bazel dep is not hermetic.
#bazel_dep(name = "glfw", version = "3.3.9")
# This is a lie too.
#bazel_dep(name = "glew", version = "2.2.0")
bazel_dep(name = "glm", version = "1.0.0.bcr.1")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_cc", version = "0.1.1")
bazel_dep(name = "rules_rust", version = "0.59.2")
bazel_dep(name = "rules_sh", version = "0.5.0")
bazel_dep(name = "rules_nodejs", version = "5.8.3")
bazel_dep(name = "rules_foreign_cc", version = "0.14.0")
bazel_dep(name = "platforms", version = "0.0.11")
bazel_dep(name = "rules_nixpkgs_core", version = "0.13.0")
bazel_dep(name = "rules_nixpkgs_cc", version = "0.13.0")
# Why do I need all these? This should not be required I think.
bazel_dep(name = "rules_nixpkgs_rust", version = "0.13.0")
bazel_dep(name = "rules_nixpkgs_python", version = "0.13.0")
bazel_dep(name = "rules_nixpkgs_posix", version = "0.13.0")
bazel_dep(name = "rules_nixpkgs_nodejs", version = "0.13.0")
bazel_dep(name = "rules_nixpkgs_java", version = "0.13.0")
archive_override(
module_name = "rules_nixpkgs_cc",
urls = ["https://github.com/tweag/rules_nixpkgs/releases/download/v0.13.0/rules_nixpkgs-0.13.0.tar.gz"],
strip_prefix = "rules_nixpkgs-0.13.0/toolchains/cc",
integrity = "sha256-MCcfe9OA5OIOTXEywySUbE/bwx6+C7tmOKD2GjfnQ5c=",
)
archive_override(
module_name = "rules_nixpkgs_rust",
urls = ["https://github.com/tweag/rules_nixpkgs/releases/download/v0.13.0/rules_nixpkgs-0.13.0.tar.gz"],
strip_prefix = "rules_nixpkgs-0.13.0/toolchains/rust",
integrity = "sha256-MCcfe9OA5OIOTXEywySUbE/bwx6+C7tmOKD2GjfnQ5c=",
)
archive_override(
module_name = "rules_nixpkgs_python",
urls = ["https://github.com/tweag/rules_nixpkgs/releases/download/v0.13.0/rules_nixpkgs-0.13.0.tar.gz"],
strip_prefix = "rules_nixpkgs-0.13.0/toolchains/python",
integrity = "sha256-MCcfe9OA5OIOTXEywySUbE/bwx6+C7tmOKD2GjfnQ5c=",
)
archive_override(
module_name = "rules_nixpkgs_posix",
urls = ["https://github.com/tweag/rules_nixpkgs/releases/download/v0.13.0/rules_nixpkgs-0.13.0.tar.gz"],
strip_prefix = "rules_nixpkgs-0.13.0/toolchains/posix",
integrity = "sha256-MCcfe9OA5OIOTXEywySUbE/bwx6+C7tmOKD2GjfnQ5c=",
)
archive_override(
module_name = "rules_nixpkgs_nodejs",
urls = ["https://github.com/tweag/rules_nixpkgs/releases/download/v0.13.0/rules_nixpkgs-0.13.0.tar.gz"],
strip_prefix = "rules_nixpkgs-0.13.0/toolchains/nodejs",
integrity = "sha256-MCcfe9OA5OIOTXEywySUbE/bwx6+C7tmOKD2GjfnQ5c=",
)
archive_override(
module_name = "rules_nixpkgs_java",
urls = ["https://github.com/tweag/rules_nixpkgs/releases/download/v0.13.0/rules_nixpkgs-0.13.0.tar.gz"],
strip_prefix = "rules_nixpkgs-0.13.0/toolchains/java",
integrity = "sha256-MCcfe9OA5OIOTXEywySUbE/bwx6+C7tmOKD2GjfnQ5c=",
)
local = use_extension(":extensions.bzl", "raylib")
use_repo(local, "raylib_src", "bazel_local_nix", "nix_portable", "io_tweag_rules_nixpkgs")
nix_repo = use_extension(
"@rules_nixpkgs_core//extensions:repository.bzl", "nix_repo")
nix_repo.github(
name = "nixpkgs",
org = "NixOS",
repo = "nixpkgs",
commit = "ff0dbd94265ac470dda06a657d5fe49de93b4599",
sha256 = "1bf0f88ee9181dd993a38d73cb120d0435e8411ea9e95b58475d4426c0948e98",
)
nix_repo.file(
name = "file_nixpkgs",
file = "//:flake.nix",
file_deps = [
"//:flake.lock",
],
)
nix_repo.expr(
name = "nixpkgs_content",
expr = "import ./flake.nix",
file_deps = [
"//:flake.nix",
"//:flake.lock",
],
)
use_repo(
nix_repo,
"nixpkgs",
"file_nixpkgs",
"nixpkgs_content",
)
WORKSPACE.bzlmod
This file is present to allow using rules_nixpkgs
rules that do not seem to work with bazel modules. It is not clear to me from the description of rules_nixpkgs
whether they are supposed to operate under bazel modules.
Here’s an example fragment of WORKSPACE.bzlmod
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@rules_nixpkgs_cc//:cc.bzl", "nixpkgs_cc_configure")
nixpkgs_cc_configure(
name = "nixpkgs_config_cc",
attribute_path = "clang_18",
repository = "@nixpkgs",
cc_std = "c++20",
)
load("@rules_cc//cc:repositories.bzl",
"rules_cc_dependencies",
"rules_cc_toolchains",
)
# Register the nixpkgs based C++ toolchain.
rules_cc_dependencies()
rules_cc_toolchains()
load(
"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl",
"nixpkgs_package",
)
# To be continued below!
Bringing in libraries
Here’s an example of bringing in libX11
from rules_nixpkgs
. This requires some finesse. Let’s take a look at the entire thing, then take some time to explain what is going on here. Note, I’m writing this for you just as much as for me, as I’m bound to forget how this is supposed to work. And frankly, I found the documentation a bit hard to understand and learn from.
# Continued WORKSPACE.bzlmod
nixpkgs_package(
name = "x11.dev",
attribute_path = "xorg.libX11.dev",
build_file_content = """\
load("@rules_cc//cc:defs.bzl", "cc_library")
filegroup(
name = "include",
srcs = glob(["include/**/*.h"]),
visibility = ["//visibility:public"],
)
""",
repository = "@nixpkgs",
)
nixpkgs_package(
name = "x11",
attribute_path = "xorg.libX11",
repository = "@nixpkgs",
build_file_content = """\
load("@rules_cc//cc:defs.bzl", "cc_library")
filegroup(
name = "include",
srcs = glob(["include/**/*.h"]),
visibility = ["//visibility:public"],
)
filegroup(
name = "libs",
srcs = glob(["lib/**/*.so.*"])
)
cc_library(
name = "lib_",
srcs = [":libs"],
hdrs = ["@x11.dev//:include"],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)
""",
)
The above fragment from the file WORKSPACE.bzlmod
converts the contents of two nix packages (not sure if this is the correct terminology) into cc_library
rules that can then be used in bazel builds.
What I don’t like in the above setup is that the library files themselves are separated from the include files. This is a result of how nix sets up its packages. As a result, you have to add two deps entries into a cc_library
once everything is set up:
cc_library(
name = "my_library",
srcs = [
# ...
],
deps = [
# The libraries - in this case just the shared libraries.
"@x11//:lib_",
# The header files.
"@x11.dev//:lib_",
],
Here’s an annotated declaration of a nixpkgs package set that brings in the shared libraries from the packages xorg.libX11
and xorg.libX11.dev
. These magic strings seem to be called “tags” and you can find them in the nix package search. Note the package name (xorg.libX11
) as well as the outputs
, which are one of out
, dev
and man
. it seems that, for example, the dev
output generates the xorg.libX11.dev
. I suspect this makes more sense to someone who knows the nix language. I, however, am only learning it to the extent that I can get by, and I use AI assistants to explain to me the things I need to know.
Bringing a library in may end up being quite an ordeal due to the somewhat arbitrary differences between libraries.
nixpkgs_package(
# This will be the name of the repository generated by this rule.
name = "x11",
# See below for this.
attribute_path = "xorg.libX11",
# This is the source of the nix packages, it is declared in
# MODULE.bazel.
repository = "@nixpkgs",
# This is the `BUILD` file inserted into the package. The references
# in the `srcs = ...` stanzas below correspond to whatever is in the
# package at `/nix/store/<hash>-package`.
build_file_content = """\
load("@rules_cc//cc:defs.bzl", "cc_library")
filegroup(
name = "include",
srcs = glob(["include/**/*.h"]),
visibility = ["//visibility:public"],
)
filegroup(
name = "libs",
srcs = glob(["lib/**/*.so.*"])
)
cc_library(
name = "lib_",
srcs = [":libs"],
hdrs = ["@x11.dev//:include"],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)
""",
)
nixpkgs_package(
name = "x11.dev",
attribute_path = "xorg.libX11.dev",
build_file_content = """\
load("@rules_cc//cc:defs.bzl", "cc_library")
filegroup(
name = "include",
srcs = glob(["include/**/*.h"]),
visibility = ["//visibility:public"],
)
""",
repository = "@nixpkgs",
)
Open questions
- How to avoid having separate header and library repos, such as
@x11
and@x11.dev
?