External dependencies
You can make dependencies from the Nix package repository available to ll_*
targets in a hermetic, reproducible manner.
A word of caution
The external dependency mechanism in rules_ll lets you import external
dependencies, but it globally affects all ll_* compile commands.
Any change to an external dependency invalidates all caches and causes a full rebuild of all toolchains.
Example
The rules_ll flake-parts module exposes an actionEnv attribute which you can
use to set custom Bazel --action-env attributes in your generated
.bazelrc.ll. Technically you can use the .bazelrc.ll generation logic to
generate arbitrary .bazelrc.* fragments. If you intend to use it with
rules_lls Bazel toolchains you should use the rules_ll.lib.action-env
function which lets you specify LL_CFLAGS and LL_LDFLAGS and handles all
other environment variables automatically.
Since rules_ll uses the clang-linker-wrapper as its linking tool you don't
need to add -Wl,... to flags in LL_LDFLAGS.
Don't use spaces in these flags and separate different flags with colons.
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
rules_ll = {
# If you use this file as template, substitute the line below with this,
# where `<version>` is the version of rules_ll you want to use:
#
# rules_ll.url = "github:eomii/rules_ll/<version>";
url = "github:eomii/rules_ll";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-parts.follows = "flake-parts";
};
};
outputs =
{ self
, rules_ll
, flake-parts
, ...
} @ inputs:
flake-parts.lib.mkFlake { inherit inputs; }
{
systems = [
"x86_64-linux"
];
imports = [
inputs.rules_ll.flakeModule
];
perSystem =
{ config
, pkgs
, system
, lib
, ...
}:
let
openssl = (pkgs.openssl.override { static = true; });
in
{
rules_ll.settings.actionEnv = rules_ll.lib.action-env {
inherit pkgs;
LL_CFLAGS = "-I${openssl.dev}/include";
LL_LDFLAGS = "-L${openssl.out}/lib";
};
devShells.default = pkgs.mkShell {
nativeBuildInputs = [ pkgs.bazel_7 ];
shellHook = ''
# Generate .bazelrc.ll which containes action-env
# configuration when rules_ll is run from a nix environment.
${config.rules_ll.installationScript}
# Prevent rules_cc from using anything other than clang.
export CC=clang
# Probably a bug in nix. Setting LD=ld.lld here won't work.
export LD=${pkgs.llvmPackages_17.lld}/bin/ld.lld
# Java needs to be the same version as in the Bazel wrapper.
export JAVA_HOME=${pkgs.jdk17_headless}/lib/openjdk
'';
};
};
};
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
rules_ll = {
# If you use this file as template, substitute the line below with this,
# where `<version>` is the version of rules_ll you want to use:
#
# rules_ll.url = "github:eomii/rules_ll/<version>";
url = "github:eomii/rules_ll";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-parts.follows = "flake-parts";
};
};
outputs =
{ self
, rules_ll
, flake-parts
, ...
} @ inputs:
flake-parts.lib.mkFlake { inherit inputs; }
{
systems = [
"x86_64-linux"
];
imports = [
inputs.rules_ll.flakeModule
];
perSystem =
{ config
, pkgs
, system
, lib
, ...
}:
{
rules_ll.settings.actionEnv = rules_ll.lib.action-env {
inherit pkgs;
LL_CFLAGS = "-I${pkgs.openssl.dev}/include";
LL_LDFLAGS = "-L${pkgs.openssl.out}/lib:-rpath=${pkgs.openssl.out}/lib";
};
devShells.default = pkgs.mkShell {
nativeBuildInputs = [ pkgs.bazel_7 ];
shellHook = ''
# Generate .bazelrc.ll which containes action-env
# configuration when rules_ll is run from a nix environment.
${config.rules_ll.installationScript}
# Prevent rules_cc from using anything other than clang.
export CC=clang
# Probably a bug in nix. Setting LD=ld.lld here won't work.
export LD=${pkgs.llvmPackages_17.lld}/bin/ld.lld
# Java needs to be the same version as in the Bazel wrapper.
export JAVA_HOME=${pkgs.jdk17_headless}/lib/openjdk
'';
};
};
};
You can see the values of LL_CFLAGS and LL_LDFLAGS in the generated
.bazelrc.ll:
cat .bazelrc.ll $LL_CFLAGS
# ...
#
# build --action_env=LL_CFLAGS=-I/nix/store/<...>-openssl-<version>-dev/include
# build --action_env=LL_LDFLAGS=-L/nix/store/<...>-openssl-<version>/lib
# ...
#
# build --action_env=LL_CFLAGS=-I/nix/store/<...>-openssl-<version>-dev/include
# build --action_env=LL_LDFLAGS=-L/nix/store/<...>-openssl-<version>/lib:-rpath=/nix/store/<...>-openssl-<version>/lib
You can now add the library via link_flags or shared_object_link_flags:
load("@rules_ll//ll:defs.bzl", "ll_binary")
ll_binary(
name = "my_externally_dependent_executable",
srcs = ["main.cpp"],
link_flags = [
"-lcrypto", # Default linkage as set in the flake.
# "-l:libcrypto.a", # Explicit static linkage.
# "-l:libcrypto.so", # Explicit dynamic linkage.
],
)
Keep in mind that LL_CFLAGS and LL_LDFLAGS globally affect ll_* targets:
- Every compile action can see all headers in
LL_CFLAGS. If two dependencies contain headers with the same name your builds might break with confusing errors. If you suspect a wrong header inclusion you can add-Hto thecompile_flagsattribute of your target to print the full inclusion chain. - It's possible to reference paths to non-nix dependencies in these flags, but doing so breaks the hermeticity and reproducibility of the build.
- Referencing paths that contain headers supplied by
rules_llitself such as/usr/includeor${pkgs.libcxx}/includeoverrides parts of the internalll_*toolchains and breaks allll_*targets. - Link actions add all
-rpathvalues fromLL_LDFLAGSto every target even if the target doesn't actually link the corresponding library. This affectsll_binary,ll_testandll_librarywithemit = ["shared_object"].