BazelDeps

Warning

This feature is experimental and subject to breaking changes. See the Conan stability section for more information.

The BazelDeps is the dependencies generator for Bazel. Generates a <REPOSITORY>/BUILD.bazel file per dependency, where the <REPOSITORY>/ folder is the Conan recipe reference name by default, e.g., mypkg/BUILD.bazel. Apart from that, it also generates Bazel 6.x compatible file like dependencies.bzl, and other Bazel >= 7.1 compatible ones like conan_deps_module_extension.bzl and conan_deps_repo_rules.bzl. All of them contain the logic to load all your Conan dependencies through your WORKSPACE | MODULE.bazel.

The BazelDeps generator can be used by name in conanfiles:

conanfile.py

  1. class Pkg(ConanFile):
  2. generators = "BazelDeps"

conanfile.txt

  1. [generators]
  2. BazelDeps

And it can also be fully instantiated in the conanfile generate() method:

  1. from conan import ConanFile
  2. from conan.tools.google import BazelDeps
  3. class App(ConanFile):
  4. settings = "os", "arch", "compiler", "build_type"
  5. requires = "zlib/1.2.11"
  6. def generate(self):
  7. bz = BazelDeps(self)
  8. bz.generate()

Generated files

When the BazelDeps generator is used, every invocation of conan install will generate several bazel files. For the conanfile.py above, for example:

  1. $ conan install .
  2. .
  3. ├── BUILD.bazel
  4. ├── conanfile.py
  5. ├── dependencies.bzl
  6. └── zlib
  7. └── BUILD.bazel

Every conan install generates these files:

  • BUILD.bazel: An empty file aimed to be alongside the dependencies.bzl one. More information here.

  • zlib/BUILD.bazel: contains all the targets that you can load from any of your BUILD files. More information in Customization.

  • dependencies.bzl: (Bazel 6.x compatible) this file tells your Bazel WORKSPACE how to load the dependencies.

  • conan_deps_module_extension.bzl: (since Conan 2.4.0)(Bazel >= 7.1 compatible) This file is used to load each dependency as repository.

  • conan_deps_repo_rules.bzl: (since Conan 2.4.0)(Bazel >= 7.1 compatible) The rule provided by this file is used to create a repository. It is not intended to be used by consumers but by conan_deps_module_extension.bzl.

Let’s check the content of the files created:

Bazel 6.x compatible

dependencies.bzl

  1. # This Bazel module should be loaded by your WORKSPACE file.
  2. # Add these lines to your WORKSPACE one (assuming that you're using the "bazel_layout"):
  3. # load("@//conan:dependencies.bzl", "load_conan_dependencies")
  4. # load_conan_dependencies()
  5. def load_conan_dependencies():
  6. native.new_local_repository(
  7. name="zlib",
  8. path="/path/to/conan/package/folder/",
  9. build_file="/your/current/working/directory/zlib/BUILD.bazel",
  10. )

Bazel >= 7.1 compatible

conan_deps_repo_rules.bzl

  1. # This bazel repository rule is used to load Conan dependencies into the Bazel workspace.
  2. # It's used by a generated module file that provides information about the conan packages.
  3. # Each conan package is loaded into a bazel repository rule, with having the name of the
  4. # package. The whole method is based on symlinks to not copy the whole package into the
  5. # Bazel workspace, which is expensive.
  6. def _conan_dependency_repo(rctx):
  7. package_path = rctx.workspace_root.get_child(rctx.attr.package_path)
  8. child_packages = package_path.readdir()
  9. for child in child_packages:
  10. rctx.symlink(child, child.basename)
  11. rctx.symlink(rctx.attr.build_file_path, "BUILD.bazel")
  12. conan_dependency_repo = repository_rule(
  13. implementation = _conan_dependency_repo,
  14. attrs = {
  15. "package_path": attr.string(
  16. mandatory = True,
  17. doc = "The path to the Conan package in conan cache.",
  18. ),
  19. "build_file_path": attr.string(
  20. mandatory = True,
  21. doc = "The path to the BUILD file.",
  22. ),
  23. },
  24. )

conan_deps_module_extension.bzl

  1. # This module provides a repo for each requires-dependency in your conanfile.
  2. # It's generated by the BazelDeps, and should be used in your Module.bazel file.
  3. load(":conan_deps_repo_rules.bzl", "conan_dependency_repo")
  4. def _load_dependenies_impl(mctx):
  5. conan_dependency_repo(
  6. name = "zlib",
  7. package_path = "/path/to/conan/package/folder/",
  8. build_file_path = "/your/current/working/directory/zlib/BUILD.bazel",
  9. )
  10. return mctx.extension_metadata(
  11. # It will only warn you if any direct
  12. # dependency is not imported by the 'use_repo' or even it is imported
  13. # but not created. Notice that root_module_direct_dev_deps can not be None as we
  14. # are giving 'all' value to root_module_direct_deps.
  15. # Fix the 'use_repo' calls by running 'bazel mod tidy'
  16. root_module_direct_deps = 'all',
  17. root_module_direct_dev_deps = [],
  18. # Prevent writing function content to lockfiles:
  19. # - https://bazel.build/rules/lib/builtins/module_ctx#extension_metadata
  20. # Important for remote build. Actually it's not reproducible, as local paths will
  21. # be different on different machines. But we assume that conan works correctly here.
  22. # IMPORTANT: Not compatible with bazel < 7.1
  23. reproducible = True,
  24. )
  25. conan_extension = module_extension(
  26. implementation = _load_dependenies_impl,
  27. os_dependent = True,
  28. arch_dependent = True,
  29. )

Given the examples above, and imagining that your WORKSPACE | MODULE.bazel is at the same directory, you would have to add these lines in there:

Bazel 6.x compatible

WORKSPACE

  1. load("@//:dependencies.bzl", "load_conan_dependencies")
  2. load_conan_dependencies()

Bazel >= 7.1 compatible

MODULE.bazel

  1. load_conan_dependencies = use_extension("//:conan_deps_module_extension.bzl", "conan_extension")
  2. # use_repo(load_conan_dependencies, "dep1", "dep2", ..., "depN")
  3. use_repo(load_conan_dependencies, "zlib")

As you can observe, the zlib/BUILD.bazel defines these global targets:

zlib/BUILD.bazel

  1. # Components precompiled libs
  2. # Root package precompiled libs
  3. cc_import(
  4. name = "z_precompiled",
  5. static_library = "lib/libz.a",
  6. )
  7. # Components libraries declaration
  8. # Package library declaration
  9. cc_library(
  10. name = "zlib",
  11. hdrs = glob([
  12. "include/**",
  13. ]),
  14. includes = [
  15. "include",
  16. ],
  17. visibility = ["//visibility:public"],
  18. deps = [
  19. ":z_precompiled",
  20. ],
  21. )
  22. # Filegroup library declaration
  23. filegroup(
  24. name = "zlib_binaries",
  25. srcs = glob([
  26. "bin/**",
  27. ]),
  28. visibility = ["//visibility:public"],
  29. )
  • zlib: bazel library target. The label used to depend on it would be @zlib//:zlib.

  • zlib_binaries: bazel filegroup target. The label used to depend on it would be @zlib//:zlib_binaries.

You can put all the files generated by BazelDeps into another folder using the bazel_layout:

conanfile.py

  1. from conan import ConanFile
  2. from conan.tools.google import BazelDeps, bazel_layout
  3. class App(ConanFile):
  4. settings = "os", "arch", "compiler", "build_type"
  5. requires = "zlib/1.2.11"
  6. def layout(self):
  7. bazel_layout(self)
  8. def generate(self):
  9. bz = BazelDeps(self)
  10. bz.generate()

Running again the conan install command, we now get this structure:

  1. $ conan install .
  2. .
  3. ├── conan
  4. ├── BUILD.bazel
  5. ├── dependencies.bzl
  6. ├── conan_deps_module_extension.bzl
  7. ├── conan_deps_repo_rules.bzl
  8. └── zlib
  9. └── BUILD.bazel
  10. └── conanfile.py

Now your Conan-bazel files were generated in the conan/ folder, your WORKSPACE will look like:

WORKSPACE

  1. load("@//conan:dependencies.bzl", "load_conan_dependencies")
  2. load_conan_dependencies()

Or your MODULE.bazel:

MODULE.bazel

  1. load_conan_dependencies = use_extension("//conan:conan_deps_module_extension.bzl", "conan_extension")
  2. use_repo(load_conan_dependencies, "zlib")

Customization

Naming

The <REPOSITORY>/BUILD.bazel file contains all the targets declared by the dependency. Both the <REPOSITORY>/ folder and the targets declared in there will be named following these rules by default:

  • For packages, it uses the package name as folder/target name, e.g., package zlib/1.2.11 will have:

    • Folder: zlib/BUILD.bazel.

    • Global target: zlib.

    • How it can be consumed: @zlib//:zlib.

  • For components, the package name + hyphen + component name, e.g., package openssl/3.1.4 will have:

    • Folder: openssl/BUILD.bazel.

    • Global target: openssl.

    • Components targets: openssl-ssl, and openssl-crypto.

    • How it can be consumed:

      • @openssl//:openssl (global one which includes all the components)

      • @openssl//:openssl-ssl (component one)

      • @openssl//:openssl-crypto (component one)

You can change that default behavior with the bazel_target_name and the bazel_repository_name properties. See Properties section below.

Reference

class BazelDeps(conanfile)

  • Parameters:

    conanfile< ConanFile object > The current recipe object. Always use self.

  • build_context_activated

    Activates the build context for the specified Conan package names.

  • generate()

    Generates all the targets <DEP>/BUILD.bazel files, a dependencies.bzl (for bazel<7), a conan_deps_repo_rules.bzl and a conan_deps_module_extension.bzl file (for bazel>=7.1) one in the build folder.

    In case of bazel < 7, it’s important to highlight that the dependencies.bzl file should be loaded by your WORKSPACE Bazel file:

    1. load("@//[BUILD_FOLDER]:dependencies.bzl", "load_conan_dependencies")
    2. load_conan_dependencies()

    In case of bazel >= 7.1, the conan_deps_module_extension.bzl file should be loaded by your Module.bazel file, e.g. like this:

    1. load_conan_dependencies = use_extension(
    2. "//build:conan_deps_module_extension.bzl",
    3. "conan_extension"
    4. )
    5. use_repo(load_conan_dependencies, "dep-1", "dep-2", ...)

build_context_activated

When you have a build-requirement, by default, the Bazel files are not generated. But you can activate it using the build_context_activated attribute:

  1. def build_requirements(self):
  2. self.tool_requires("my_tool/0.0.1")
  3. def layout(self):
  4. bazel_layout(self)
  5. def generate(self):
  6. bz = BazelDeps(self)
  7. # generate the build-mytool/BUILD.bazel file for the tool require
  8. bz.build_context_activated = ["my_tool"]
  9. bz.generate()

Running the conan install command, the structure created is as follows:

  1. $ conan install . -pr:b default
  2. .
  3. ├── conan
  4. ├── BUILD.bazel
  5. ├── build-my_tool
  6. └── BUILD.bazel
  7. ├── conan_deps_module_extension.bzl
  8. ├── conan_deps_repo_rules.bzl
  9. └── dependencies.bzl
  10. └── conanfile.py

Notice that my_tool Bazel folder is prefixed with build- which indicates that it’s being used in the build context.

Properties

The following properties affect the BazelDeps generator:

  • bazel_target_name property will define the name of the target declared in the <REPOSITORY>/BUILD.bazel. This property can be defined at both global and component cpp_info level.

  • bazel_repository_name property will define the name of the folder where the dependency BUILD.bazel will be allocated. This property can only be defined at global cpp_info level.

Example:

  1. def package_info(self):
  2. self.cpp_info.set_property("bazel_target_name", "my_target")
  3. self.cpp_info.set_property("bazel_repository_name", "my_repo")
  4. self.cpp_info.components["mycomponent"].set_property("bazel_target_name", "component_name")

See also