BazelDeps

警告

此功能为实验性功能,可能会有重大更改。有关详细信息,请参阅Conan 稳定性部分。

BazelDeps 是 Bazel 的依赖项生成器。它为每个依赖项生成一个 *<REPOSITORY>/BUILD.bazel* 文件,其中 *<REPOSITORY>/* 文件夹默认为 Conan 配方引用名称,例如,*mypkg/BUILD.bazel*。除此之外,它还生成 Bazel 6.x 兼容的文件,如 *dependencies.bzl*,以及其他 Bazel >= 7.1 兼容的文件,如 *conan_deps_module_extension.bzl* 和 *conan_deps_repo_rules.bzl*。所有这些都包含通过你的 *WORKSPACE* | *MODULE.bazel* 加载所有 Conan 依赖项的逻辑。

BazelDeps 生成器可以在 conanfile 中按名称使用

conanfile.py
class Pkg(ConanFile):
    generators = "BazelDeps"
conanfile.txt
[generators]
BazelDeps

它也可以在 conanfile 的 generate() 方法中完全实例化

from conan import ConanFile
from conan.tools.google import BazelDeps

class App(ConanFile):
    settings = "os", "arch", "compiler", "build_type"
    requires = "zlib/1.2.11"

    def generate(self):
        bz = BazelDeps(self)
        bz.generate()

生成的文件

当使用 BazelDeps 生成器时,每次调用 conan install 都会生成多个 bazel 文件。例如,对于上面的 *conanfile.py*

$ conan install .
.
├── BUILD.bazel
├── conanfile.py
├── dependencies.bzl
└── zlib
    └── BUILD.bazel

每次 conan install 都会生成这些文件

  • BUILD.bazel:一个空文件,旨在与 *dependencies.bzl* 文件一起使用。更多信息请参见此处

  • zlib/BUILD.bazel:包含你可以从任何 *BUILD* 文件加载的所有目标。更多信息请参见自定义

  • dependencies.bzl:(与 Bazel 6.x 兼容)此文件告诉你的 Bazel *WORKSPACE* 如何加载依赖项。

  • conan_deps_module_extension.bzl:(自Conan 2.4.0起)(与 Bazel >= 7.1 兼容)此文件用于将每个依赖项作为存储库加载。

  • conan_deps_repo_rules.bzl:(自Conan 2.4.0起)(与 Bazel >= 7.1 兼容)此文件提供的规则用于创建存储库。它不打算被使用者使用,而是被 *conan_deps_module_extension.bzl* 使用。

让我们检查一下创建的文件内容

与 Bazel 6.x 兼容

dependencies.bzl
# This Bazel module should be loaded by your WORKSPACE file.
# Add these lines to your WORKSPACE one (assuming that you're using the "bazel_layout"):
# load("@//conan:dependencies.bzl", "load_conan_dependencies")
# load_conan_dependencies()

def load_conan_dependencies():
    native.new_local_repository(
        name="zlib",
        path="/path/to/conan/package/folder/",
        build_file="/your/current/working/directory/zlib/BUILD.bazel",
    )

与 Bazel >= 7.1 兼容

conan_deps_repo_rules.bzl
# This bazel repository rule is used to load Conan dependencies into the Bazel workspace.
# It's used by a generated module file that provides information about the conan packages.
# Each conan package is loaded into a bazel repository rule, with having the name of the
# package. The whole method is based on symlinks to not copy the whole package into the
# Bazel workspace, which is expensive.
def _conan_dependency_repo(rctx):
    package_path = rctx.workspace_root.get_child(rctx.attr.package_path)

    child_packages = package_path.readdir()
    for child in child_packages:
        rctx.symlink(child, child.basename)

    rctx.symlink(rctx.attr.build_file_path, "BUILD.bazel")

conan_dependency_repo = repository_rule(
    implementation = _conan_dependency_repo,
    attrs = {
        "package_path": attr.string(
            mandatory = True,
            doc = "The path to the Conan package in conan cache.",
        ),
        "build_file_path": attr.string(
            mandatory = True,
            doc = "The path to the BUILD file.",
        ),
    },
)
conan_deps_module_extension.bzl
# This module provides a repo for each requires-dependency in your conanfile.
# It's generated by the BazelDeps, and should be used in your Module.bazel file.
load(":conan_deps_repo_rules.bzl", "conan_dependency_repo")

def _load_dependenies_impl(mctx):
    conan_dependency_repo(
        name = "zlib",
        package_path = "/path/to/conan/package/folder/",
        build_file_path = "/your/current/working/directory/zlib/BUILD.bazel",
    )

    return mctx.extension_metadata(
        # It will only warn you if any direct
        # dependency is not imported by the 'use_repo' or even it is imported
        # but not created. Notice that root_module_direct_dev_deps can not be None as we
        # are giving 'all' value to root_module_direct_deps.
        # Fix the 'use_repo' calls by running 'bazel mod tidy'
        root_module_direct_deps = 'all',
        root_module_direct_dev_deps = [],

        # Prevent writing function content to lockfiles:
        # - https://bazel.build/rules/lib/builtins/module_ctx#extension_metadata
        # Important for remote build. Actually it's not reproducible, as local paths will
        # be different on different machines. But we assume that conan works correctly here.
        # IMPORTANT: Not compatible with bazel < 7.1
        reproducible = True,
    )

conan_extension = module_extension(
    implementation = _load_dependenies_impl,
    os_dependent = True,
    arch_dependent = True,
)

鉴于上面的示例,并假设你的 *WORKSPACE* | *MODULE.bazel* 位于同一目录下,你需要在其中添加以下行

与 Bazel 6.x 兼容

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

与 Bazel >= 7.1 兼容

MODULE.bazel
load_conan_dependencies = use_extension("//:conan_deps_module_extension.bzl", "conan_extension")
# use_repo(load_conan_dependencies, "dep1", "dep2", ..., "depN")
use_repo(load_conan_dependencies, "zlib")

你可以看到,*zlib/BUILD.bazel* 定义了以下全局目标

zlib/BUILD.bazel
# Components precompiled libs
# Root package precompiled libs
cc_import(
    name = "z_precompiled",
    static_library = "lib/libz.a",
)

# Components libraries declaration
# Package library declaration
cc_library(
    name = "zlib",
    hdrs = glob([
        "include/**",
    ]),
    includes = [
        "include",
    ],
    visibility = ["//visibility:public"],
    deps = [
        ":z_precompiled",
    ],
)

# Filegroup library declaration
filegroup(
    name = "zlib_binaries",
    srcs = glob([
        "bin/**",
    ]),
    visibility = ["//visibility:public"],
)
  • zlib: bazel 库目标。用于依赖它的标签将是 @zlib//:zlib

  • zlib_binaries:bazel 文件组目标。用于依赖它的标签将是 @zlib//:zlib_binaries

你可以使用 bazel_layoutBazelDeps 生成的所有文件放入另一个文件夹

conanfile.py
from conan import ConanFile
from conan.tools.google import BazelDeps, bazel_layout

class App(ConanFile):
    settings = "os", "arch", "compiler", "build_type"
    requires = "zlib/1.2.11"

    def layout(self):
        bazel_layout(self)

    def generate(self):
        bz = BazelDeps(self)
        bz.generate()

再次运行 conan install 命令,我们现在得到以下结构

$ conan install .
.
├── conan
│   ├── BUILD.bazel
│   ├── dependencies.bzl
│   ├── conan_deps_module_extension.bzl
│   ├── conan_deps_repo_rules.bzl
│   └── zlib
│       └── BUILD.bazel
└── conanfile.py

现在,你的 Conan-bazel 文件已在 *conan/* 文件夹中生成,你的 WORKSPACE 将如下所示

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

或者你的 MODULE.bazel

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

自定义

命名

<REPOSITORY>/BUILD.bazel 文件包含依赖项声明的所有目标。默认情况下,<REPOSITORY>/ 文件夹和其中声明的目标将按照以下规则命名

  • 对于包,它使用包名称作为文件夹/目标名称,例如,包 zlib/1.2.11 将具有
    • 文件夹:zlib/BUILD.bazel

    • 全局目标:zlib

    • 如何使用:@zlib//:zlib

  • 对于组件,包名称 + 连字符 + 组件名称,例如,包 openssl/3.1.4 将具有
    • 文件夹:openssl/BUILD.bazel

    • 全局目标:openssl

    • 组件目标:openssl-sslopenssl-crypto

    • 如何使用
      • @openssl//:openssl(包含所有组件的全局目标)

      • @openssl//:openssl-ssl(组件目标)

      • @openssl//:openssl-crypto(组件目标)

你可以使用 bazel_target_namebazel_repository_name 属性更改该默认行为。请参阅下面的属性部分

参考

class BazelDeps(conanfile)
参数

conanfile< ConanFile 对象 > 当前配方对象。始终使用 self

build_context_activated

为指定的 Conan 包名称激活构建上下文。

generate()

生成所有目标 <DEP>/BUILD.bazel 文件、一个 dependencies.bzl 文件(用于 bazel<7)、一个 conan_deps_repo_rules.bzl 文件和一个 conan_deps_module_extension.bzl 文件(用于 bazel>=7.1),所有这些文件都在构建文件夹中。

对于 bazel < 7,需要强调的是,你的 WORKSPACE Bazel 文件应该加载 dependencies.bzl 文件

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

对于 bazel >= 7.1,你的 Module.bazel 文件应该加载 conan_deps_module_extension.bzl 文件,例如像这样

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

build_context_activated

当你有**构建要求**时,默认情况下不会生成 Bazel 文件。但是你可以使用 **build_context_activated** 属性激活它

def build_requirements(self):
    self.tool_requires("my_tool/0.0.1")

def layout(self):
    bazel_layout(self)

def generate(self):
    bz = BazelDeps(self)
    # generate the build-mytool/BUILD.bazel file for the tool require
    bz.build_context_activated = ["my_tool"]
    bz.generate()

运行 conan install 命令,创建的结构如下

$ conan install . -pr:b default
.
├── conan
│   ├── BUILD.bazel
│   ├── build-my_tool
│      └── BUILD.bazel
│   ├── conan_deps_module_extension.bzl
│   ├── conan_deps_repo_rules.bzl
│   └── dependencies.bzl
└── conanfile.py

请注意,*my_tool* Bazel 文件夹的前缀为 build-,这表示它正在构建上下文中使用。

属性

以下属性会影响 BazelDeps 生成器

  • bazel_target_name 属性将定义在 <REPOSITORY>/BUILD.bazel 中声明的目标的名称。此属性可以在全局和组件 cpp_info 级别定义。

  • bazel_repository_name 属性将定义分配依赖项 *BUILD.bazel* 的文件夹的名称。此属性只能在全局 cpp_info 级别定义。

示例

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