MediaPipe 系列 06:Bazel 构建系统——MediaPipe 编译入门完整指南

前言:为什么 MediaPipe 选择 Bazel?

6.1 构建系统的挑战

大型 C++ 项目面临的构建挑战:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
┌─────────────────────────────────────────────────────────────────────────┐
│ 构建系统挑战 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 问题:如何高效编译大型 C++ 项目? │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 挑战: │ │
│ │ │ │
│ │ • 依赖管理 │ │
│ │ - 外部库(OpenCV, TFLite, NCNN) │ │
│ │ - 版本冲突 │ │
│ │ - 跨平台兼容性 │ │
│ │ │ │
│ │ • 增量编译 │ │
│ │ - 只重新编译修改的部分 │ │
│ │ - 依赖变化检测 │ │
│ │ - 避免全量重编译 │ │
│ │ │ │
│ │ • 跨平台编译 │ │
│ │ - Linux/macOS/Windows │ │
│ │ - Android/iOS │ │
│ │ - 嵌入式平台(高通、TI) │ │
│ │ │ │
│ │ • 并行编译 │ │
│ │ - 多目标并行 │ │
│ │ - 多线程编译 │ │
│ │ - 分布式编译 │ │
│ │ │ │
│ │ • 可重现性 │ │
│ │ - 相同输入产生相同输出 │ │
│ │ - 团队协作一致性 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 解决方案:Bazel 构建系统 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

6.2 Bazel 的核心优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
┌─────────────────────────────────────────────────────────────┐
│ Bazel 核心优势 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 增量编译 │ │
│ │ │ │
│ │ • 只重新编译修改的部分 │ │
│ │ • 自动检测依赖变化 │ │
│ │ • 智能缓存机制 │ │
│ │ │ │
│ │ 示例: │ │
│ │ 修改 my_calculator.cc │ │
│ │ → 只重新编译 my_calculator │ │
│ │ → 依赖它的目标自动重新链接 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 跨平台支持 │ │
│ │ │ │
│ │ • Linux/macOS/Windows │ │
│ │ • Android/iOS │ │
│ │ • 嵌入式平台 │ │
│ │ │ │
│ │ 统一构建规则: │ │
│ │ bazel build //path:target │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 依赖管理 │ │
│ │ │ │
│ │ • 自动下载外部依赖 │ │
│ │ • 版本锁定 │ │
│ │ • 依赖图管理 │ │
│ │ │ │
│ │ 示例: │ │
│ │ http_archive( │ │
│ │ name = "opencv", │ │
│ │ url = "https://...", │ │
│ │ ) │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 并行构建 │ │
│ │ │ │
│ │ • 多目标并行编译 │ │
│ │ • 多线程编译 │ │
│ │ • 分布式编译支持 │ │
│ │ │ │
│ │ 示例: │ │
│ │ bazel build -j 8 //path:target │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 可重现性 │ │
│ │ │ │
│ │ • 相同输入产生相同输出 │ │
│ │ • 沙盒隔离 │ │
│ │ • 团队协作一致性 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

6.3 Bazel vs CMake

维度 Bazel CMake
增量编译 原生支持(精细粒度) 有限支持
依赖管理 原生支持(自动下载) 需要外部工具
跨平台 统一接口 平台特定配置
学习曲线 陡峭 平缓
社区支持 Google 生态 广泛支持
IDE 集成 CLion/VSCode 全平台 IDE
构建速度 快(分布式缓存) 中等
适用场景 大型项目 中小型项目

七、Bazel 安装与配置

7.1 安装 Bazel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# ========== Ubuntu/Debian ==========
# 方法 1:使用官方 APT 仓库
sudo apt install apt-transport-https curl gnupg
curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | \
sudo tee /etc/apt/sources.list.d/bazel.list
sudo apt update && sudo apt install bazel

# 方法 2:使用二进制安装
wget https://github.com/bazelbuild/bazel/releases/download/6.1.2/bazel-6.1.2-installer-linux-x86_64.sh
chmod +x bazel-6.1.2-installer-linux-x86_64.sh
./bazel-6.1.2-installer-linux-x86_64.sh --user

# ========== macOS ==========
# 方法 1:使用 Homebrew
brew install bazel

# 方法 2:使用二进制安装
brew install bazelisk # Bazel 版本管理器

# ========== Windows ==========
# 方法 1:使用 Chocolatey
choco install bazel

# 方法 2:使用二进制安装
# 下载:https://github.com/bazelbuild/bazel/releases

# ========== 验证安装 ==========
bazel --version
# 输出:bazel 6.1.2

# ========== 安装依赖 ==========
# Ubuntu
sudo apt install -y \
build-essential \
python3 \
python3-pip \
git \
curl \
wget

# 安装 Python 依赖
pip3 install numpy

# ========== 安装 OpenCV(可选)==========
sudo apt install -y \
libopencv-dev \
python3-opencv

7.2 Bazel 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# ========== .bazelrc 配置文件 ==========
# 项目根目录下创建 .bazelrc 文件

# 基本配置
build --jobs 8 # 并行编译任务数
build --compilation_mode=opt # 优化编译
build --cxxopt=-std=c++17 # C++ 标准版本
build --cxxopt=-O3 # 优化级别

# GPU 配置
build --define MEDIAPIPE_DISABLE_GPU=0
build --define MEDIAPIPE_GPU=OPENGL

# Android 配置
build:android --config=android_arm64
build:android --crosstool_top=@androidndk//:default_toolchain
build:android_arm64 --cpu=arm64-v8a

# iOS 配置
build:ios --config=ios_arm64
build:ios --xcode_version=14.0

# 缓存配置
build --disk_cache=~/.cache/bazel
build --repository_cache=~/.cache/bazel-repo

# 输出配置
build --verbose_failures
build --sandbox_debug

# 测试配置
test --test_output=errors
test --test_summary=detailed

7.3 环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ========== 设置环境变量 ==========
# 添加到 ~/.bashrc 或 ~/.zshrc

# Bazel 缓存目录
export BAZEL_CACHE_DIR=~/.cache/bazel

# Android SDK 路径
export ANDROID_SDK_HOME=/path/to/android-sdk
export ANDROID_NDK_HOME=/path/to/android-ndk

# Java 路径
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64

# Python 路径
export PYTHON_BIN_PATH=/usr/bin/python3

# ========== 应用环境变量 ==========
source ~/.bashrc

八、WORKSPACE 文件详解

8.1 WORKSPACE 作用

WORKSPACE 文件定义项目根目录和外部依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────┐
│ WORKSPACE 作用 │
├─────────────────────────────────────────────────────────────┤
│ │
1. 标记项目根目录 │
│ • Bazel 从 WORKSPACE 所在目录开始查找 BUILD 文件 │
│ • 项目根目录下的文件使用 // 开头的路径 │
│ │
2. 定义项目名称 │
│ • workspace(name = "mediapipe") │
│ • 用于外部项目引用 │
│ │
3. 配置外部依赖 │
│ • http_archive:从 URL 下载 │
│ • git_repository:从 Git 仓库克隆 │
│ • local_repository:本地路径 │
│ • new_http_archive:解压后使用自定义 BUILD │
│ │
4. 配置工具链 │
│ • Android SDK/NDK │
│ • iOS 工具链 │
│ • 交叉编译工具链 │
│ │
└─────────────────────────────────────────────────────────────┘

8.2 项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mediapipe/                           # 项目根目录
├── WORKSPACE # 项目根标记 + 外部依赖
├── .bazelrc # Bazel 配置
├── .bazelversion # Bazel 版本
├── BUILD # 根构建文件
├── mediapipe/ # 主要代码
│ ├── BUILD
│ ├── framework/
│ │ ├── BUILD
│ │ ├── calculator_framework.h
│ │ └── calculator_framework.cc
│ ├── calculators/
│ │ ├── core/
│ │ │ └── BUILD
│ │ └── custom/
│ │ └── BUILD
│ └── graphs/
│ ├── hand_tracking/
│ │ └── BUILD
│ └── face_detection/
│ └── BUILD
├── third_party/ # 第三方库 BUILD 文件
│ ├── BUILD
│ ├── opencv_linux.BUILD
│ ├── tflite_linux.BUILD
│ └── ncnn_linux.BUILD
└── examples/
├── desktop/
│ └── BUILD
└── android/
└── BUILD

8.3 WORKSPACE 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# WORKSPACE

# ========== 项目名称 ==========
workspace(name = "mediapipe")

# ========== 加载 Bazel 规则 ==========
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

# ========== Android SDK ==========
android_sdk_repository(
name = "androidsdk",
path = "/opt/android-sdk",
api_level = 31,
build_tools_version = "31.0.0",
)

# ========== Android NDK ==========
android_ndk_repository(
name = "androidndk",
path = "/opt/android-ndk",
api_level = 21,
)

# ========== iOS 工具链 ==========
# iOS 配置通过命令行参数传递
# bazel build --config=ios_arm64 //path:target

# ========== OpenCV(Linux)==========
http_archive(
name = "linux_opencv",
build_file = "@//third_party:opencv_linux.BUILD",
url = "https://github.com/opencv/opencv/archive/4.5.5.tar.gz",
sha256 = "a1c5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "opencv-4.5.5",
)

# ========== OpenCV(macOS)==========
http_archive(
name = "macos_opencv",
build_file = "@//third_party:opencv_macos.BUILD",
url = "https://github.com/opencv/opencv/archive/4.5.5.tar.gz",
sha256 = "a1c5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "opencv-4.5.5",
)

# ========== TensorFlow Lite ==========
http_archive(
name = "org_tensorflow",
build_file = "@//third_party:tensorflow.BUILD",
url = "https://github.com/tensorflow/tensorflow/archive/v2.11.0.tar.gz",
sha256 = "b1c5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "tensorflow-2.11.0",
)

# ========== NCNN(腾讯)==========
http_archive(
name = "linux_ncnn",
build_file = "@//third_party:ncnn_linux.BUILD",
url = "https://github.com/Tencent/ncnn/archive/20221128.tar.gz",
sha256 = "c1d5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "ncnn-20221128",
)

# ========== Abseil(Google C++ 库)==========
http_archive(
name = "com_google_absl",
url = "https://github.com/abseil/abseil-cpp/archive/20220623.0.tar.gz",
sha256 = "d1d5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "abseil-cpp-20220623.0",
)

# ========== glog(Google 日志库)==========
http_archive(
name = "com_google_glog",
url = "https://github.com/google/glog/archive/v0.6.0.tar.gz",
sha256 = "e1d5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "glog-0.6.0",
)

# ========== Protobuf ==========
http_archive(
name = "com_google_protobuf",
url = "https://github.com/protocolbuffers/protobuf/archive/v3.21.12.tar.gz",
sha256 = "f1d5c24a6c958bf24cc7c0d56a4f0d87d5e3e9e1e8e9e5d6c7e8f9a0b1c2d3e4",
strip_prefix = "protobuf-3.21.12",
)

# ========== 本地依赖(可选)==========
# local_repository(
# name = "my_local_lib",
# path = "/path/to/local/lib",
# )

8.4 第三方库 BUILD 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# third_party/opencv_linux.BUILD

# OpenCV 库定义
cc_library(
name = "opencv",
srcs = glob([
"modules/core/src/**/*.cpp",
"modules/imgproc/src/**/*.cpp",
"modules/imgcodecs/src/**/*.cpp",
"modules/highgui/src/**/*.cpp",
"modules/videoio/src/**/*.cpp",
]),
hdrs = glob([
"modules/core/include/**/*.h",
"modules/imgproc/include/**/*.h",
"modules/imgcodecs/include/**/*.h",
"modules/highgui/include/**/*.h",
"modules/videoio/include/**/*.h",
]),
includes = [
"modules/core/include",
"modules/imgproc/include",
"modules/imgcodecs/include",
"modules/highgui/include",
"modules/videoio/include",
],
copts = [
"-DOPENCV_ENABLE_NONFREE",
"-DCV_DISABLE_DEBUG_BUILD",
],
linkopts = [
"-lpthread",
"-lm",
"-ldl",
],
visibility = ["//visibility:public"],
)

# third_party/ncnn_linux.BUILD

# NCNN 库定义
cc_library(
name = "ncnn",
srcs = glob([
"src/**/*.cpp",
]),
hdrs = glob([
"src/**/*.h",
]),
includes = ["src"],
copts = [
"-DNCNN_STDIO",
"-DNCNN_STRING",
"-DNCNN_SIMPLEOCV",
],
visibility = ["//visibility:public"],
)

九、BUILD 文件详解

9.1 BUILD 文件基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# BUILD

# ========== 包声明(可选)==========
# package(default_visibility = ["//visibility:public"])

# ========== 加载自定义规则 ==========
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_library")

# ========== C++ 库 ==========
cc_library(
name = "my_library", # 目标名称
srcs = [ # 源文件
"my_library.cc",
"helper.cc",
],
hdrs = [ # 头文件
"my_library.h",
"helper.h",
],
deps = [ # 依赖
"//mediapipe/framework:calculator_framework",
"@com_google_absl//absl/strings",
],
copts = [ # 编译选项
"-O3",
"-std=c++17",
],
linkopts = [ # 链接选项
"-lpthread",
],
visibility = ["//visibility:public"], # 可见性
)

# ========== C++ 可执行文件 ==========
cc_binary(
name = "my_app", # 可执行文件名称
srcs = ["main.cc"], # 源文件
deps = [ # 依赖
":my_library",
"//mediapipe/framework:calculator_framework",
],
)

# ========== C++ 测试 ==========
cc_test(
name = "my_test", # 测试名称
srcs = ["my_test.cc"], # 测试源文件
deps = [ # 依赖
":my_library",
"@com_google_googletest//:gtest",
],
)

# ========== 文件组 ==========
filegroup(
name = "graph_config",
srcs = ["graph.pbtxt"], # 配置文件
)

# ========== Proto 库 ==========
proto_library(
name = "my_proto",
srcs = ["my.proto"],
)

cc_proto_library(
name = "my_cc_proto",
deps = [":my_proto"],
)

9.2 Calculator BUILD 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# mediapipe/calculators/custom/BUILD

load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_library")

# ========== 单个 Calculator ==========
cc_library(
name = "my_calculator",
srcs = ["my_calculator.cc"],
hdrs = ["my_calculator.h"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame",
"//mediapipe/framework/port:opencv_imgproc",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/memory",
],
copts = [
"-O3",
"-std=c++17",
],
alwayslink = 1, # 重要:确保 Calculator 被注册
visibility = ["//visibility:public"],
)

# ========== 多个 Calculator 打包 ==========
cc_library(
name = "custom_calculators",
deps = [
":my_calculator",
":another_calculator",
":yet_another_calculator",
],
)

# ========== 可执行程序 ==========
cc_binary(
name = "my_calculator_demo",
srcs = ["demo.cc"],
deps = [
":my_calculator",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/port:opencv_highgui",
"//mediapipe/framework/port:opencv_imgproc",
],
linkopts = [
"-lpthread",
"-lm",
],
)

# ========== 测试 ==========
cc_test(
name = "my_calculator_test",
srcs = ["my_calculator_test.cc"],
deps = [
":my_calculator",
"//mediapipe/framework:calculator_framework",
"@com_google_googletest//:gtest_main",
],
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
┌─────────────────────────────────────────────────────────────┐
│ alwayslink 详解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:Calculator 未被注册 │
│ │
│ 原因: │
│ ┌─────────────────────────────────────────────┐ │
│ │ Calculator 使用 REGISTER_CALCULATOR 宏注册 │ │
│ │ │ │
│ │ // my_calculator.cc │ │
│ │ REGISTER_CALCULATOR(MyCalculator); │ │
│ │ │ │
│ │ 这是一个全局构造函数,在 main() 之前执行 │ │
│ │ │ │
│ │ 问题: │ │
│ │ 如果没有任何代码引用 MyCalculator, │ │
│ │ 链接器会优化掉 my_calculator.cc 中的 │ │
│ │ 未引用符号(包括 REGISTER_CALCULATOR) │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 解决方案:设置 alwayslink = 1
│ ┌─────────────────────────────────────────────┐ │
│ │ cc_library( │ │
│ │ name = "my_calculator", │ │
│ │ srcs = ["my_calculator.cc"], │ │
│ │ alwayslink = 1, # 重要! │ │
│ │ ) │ │
│ │ │ │
│ │ alwayslink = 1 的作用: │ │
│ │ • 强制链接库中的所有对象文件 │ │
│ │ • 即使没有引用也保留 │ │
│ │ • 确保全局构造函数被执行 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

十、编译命令详解

10.1 基本编译命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ========== 基本语法 ==========
bazel build [options] //path/to:target

# ========== 编译 Calculator ==========
bazel build //mediapipe/calculators/custom:my_calculator

# ========== 编译可执行程序 ==========
bazel build //mediapipe/examples/desktop/my_app:my_app

# ========== 编译所有目标 ==========
bazel build //...

# ========== 编译特定包下的所有目标 ==========
bazel build //mediapipe/calculators/...

10.2 编译选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ========== 优化级别 ==========
bazel build -c opt //path:target # 优化编译(推荐)
bazel build -c dbg //path:target # 调试编译(包含调试信息)

# ========== 并行编译 ==========
bazel build -j 8 //path:target # 使用 8 个并行任务
bazel build -j auto //path:target # 自动检测 CPU 核心数

# ========== 显示详细输出 ==========
bazel build -s //path:target # 显示执行的命令
bazel build --verbose_failures //path:target # 失败时显示详细错误

# ========== GPU 配置 ==========
bazel build --define MEDIAPIPE_DISABLE_GPU=1 //path:target # 禁用 GPU
bazel build --define MEDIAPIPE_GPU=OPENGL //path:target # 使用 OpenGL
bazel build --define MEDIAPIPE_GPU=METAL //path:target # 使用 Metal(macOS/iOS)

# ========== C++ 标准版本 ==========
bazel build --cxxopt=-std=c++17 //path:target
bazel build --cxxopt=-std=c++14 //path:target

# ========== 优化选项 ==========
bazel build --cxxopt=-O3 --cxxopt=-march=native //path:target

10.3 跨平台编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# ========== Android 编译 ==========
# ARM64(推荐)
bazel build -c opt --config=android_arm64 \
//mediapipe/examples/android:my_app

# ARM32
bazel build -c opt --config=android_arm \
//mediapipe/examples/android:my_app

# x86_64(模拟器)
bazel build -c opt --config=android_x86_64 \
//mediapipe/examples/android:my_app

# ========== iOS 编译 ==========
# ARM64(真机)
bazel build -c opt --config=ios_arm64 \
//mediapipe/examples/ios:my_app

# x86_64(模拟器)
bazel build -c opt --config=ios_x86_64 \
//mediapipe/examples/ios:my_app

# ========== 嵌入式平台 ==========
# 高通 QNN
bazel build -c opt --define PLATFORM=qnn \
//mediapipe/examples/embedded:my_app

# TI TDA4
bazel build -c opt --define PLATFORM=tda4 \
//mediapipe/examples/embedded:my_app

10.4 运行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ========== 运行单个测试 ==========
bazel test //path/to:my_test

# ========== 运行所有测试 ==========
bazel test //...

# ========== 显示测试输出 ==========
bazel test --test_output=all //path/to:my_test

# ========== 显示测试摘要 ==========
bazel test --test_summary=detailed //path/to:my_test

# ========== 运行特定测试 ==========
bazel test --test_filter=MyTest.TestCase //path/to:my_test

10.5 清理与缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ========== 清理构建输出 ==========
bazel clean # 清理构建输出
bazel clean --expunge # 完全清理(包括缓存)

# ========== 清理特定目标 ==========
bazel clean //path/to:target

# ========== 缓存管理 ==========
# 查看缓存大小
du -sh ~/.cache/bazel

# 清理缓存
rm -rf ~/.cache/bazel

# 设置缓存位置
bazel build --disk_cache=/path/to/cache //path:target

十一、IMS 自定义 Calculator 编译实战

11.1 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mediapipe/
├── WORKSPACE
├── .bazelrc
├── third_party/
│ ├── opencv_linux.BUILD
│ ├── ncnn_linux.BUILD
│ └── tflite_linux.BUILD
├── mediapipe/
│ ├── calculators/
│ │ └── ims/
│ │ ├── BUILD
│ │ ├── ncnn_inference_calculator.h
│ │ ├── ncnn_inference_calculator.cc
│ │ ├── fatigue_score_calculator.h
│ │ ├── fatigue_score_calculator.cc
│ │ ├── ar_calculator.h
│ │ └── ar_calculator.cc
│ ├── graphs/
│ │ └── ims/
│ │ ├── dms/
│ │ │ ├── BUILD
│ │ │ └── dms_graph.pbtxt
│ │ └── oms/
│ │ ├── BUILD
│ │ └── oms_graph.pbtxt
│ └── examples/
│ └── desktop/
│ └── ims/
│ ├── BUILD
│ └── dms_demo.cc

11.2 Calculator BUILD 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# mediapipe/calculators/ims/BUILD

load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_library")

# ========== NCNN 推理 Calculator ==========
cc_library(
name = "ncnn_inference_calculator",
srcs = ["ncnn_inference_calculator.cc"],
hdrs = ["ncnn_inference_calculator.h"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:tensor",
"//mediapipe/framework/port:opencv_imgproc",
"@linux_ncnn//:ncnn",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
copts = [
"-O3",
"-std=c++17",
"-fPIC",
],
alwayslink = 1,
visibility = ["//visibility:public"],
)

# ========== EAR 计算 Calculator ==========
cc_library(
name = "ar_calculator",
srcs = ["ar_calculator.cc"],
hdrs = ["ar_calculator.h"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:landmark_cc_proto",
],
alwayslink = 1,
visibility = ["//visibility:public"],
)

# ========== PERCLOS 计算 Calculator ==========
cc_library(
name = "perclos_calculator",
srcs = ["perclos_calculator.cc"],
hdrs = ["perclos_calculator.h"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:landmark_cc_proto",
],
alwayslink = 1,
visibility = ["//visibility:public"],
)

# ========== 疲劳评分 Calculator ==========
cc_library(
name = "fatigue_score_calculator",
srcs = ["fatigue_score_calculator.cc"],
hdrs = ["fatigue_score_calculator.h"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:landmark_cc_proto",
],
alwayslink = 1,
visibility = ["//visibility:public"],
)

# ========== 所有 Calculator 打包 ==========
cc_library(
name = "ims_calculators",
deps = [
":ncnn_inference_calculator",
":ar_calculator",
":perclos_calculator",
":fatigue_score_calculator",
],
visibility = ["//visibility:public"],
)

11.3 Graph BUILD 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# mediapipe/graphs/ims/dms/BUILD

# ========== Graph 配置文件 ==========
filegroup(
name = "dms_graph_config",
srcs = ["dms_graph.pbtxt"],
visibility = ["//visibility:public"],
)

# ========== Graph 库(包含所有 Calculator)==========
cc_library(
name = "dms_graph",
deps = [
"//mediapipe/calculators/ims:ims_calculators",
],
visibility = ["//visibility:public"],
)

# ========== 可执行程序 ==========
cc_binary(
name = "dms_demo",
srcs = ["dms_demo.cc"],
deps = [
":dms_graph",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/port:opencv_highgui",
"//mediapipe/framework/port:opencv_imgproc",
"//mediapipe/framework/port:opencv_videoio",
],
linkopts = [
"-lpthread",
"-lm",
"-ldl",
],
visibility = ["//visibility:public"],
)

11.4 编译与运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ========== 编译 DMS Demo ==========
cd /path/to/mediapipe

bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 \
//mediapipe/graphs/ims/dms:dms_demo

# ========== 运行 DMS Demo ==========
GLOG_logtostderr=1 \
bazel-bin/mediapipe/graphs/ims/dms/dms_demo \
--calculator_graph_config_file=mediapipe/graphs/ims/dms/dms_graph.pbtxt

# ========== 编译 Android 版本 ==========
bazel build -c opt --config=android_arm64 \
//mediapipe/graphs/ims/dms:dms_demo

# ========== 编译 iOS 版本 ==========
bazel build -c opt --config=ios_arm64 \
//mediapipe/graphs/ims/dms:dms_demo

十二、常见问题与调试

12.1 找不到依赖

1
2
3
4
5
6
7
8
9
10
11
# ========== 错误信息 ==========
# ERROR: no such package '@linux_opencv//'

# ========== 解决方案 ==========
# 1. 检查 WORKSPACE 中的依赖定义
# 2. 确保 URL 和 sha256 正确
# 3. 检查网络连接
# 4. 清理缓存并重试

bazel clean --expunge
bazel build //path:target

12.2 Calculator 未注册

1
2
3
4
5
6
7
8
9
10
# ========== 错误信息 ==========
# Calculator "MyCalculator" not found

# ========== 解决方案 ==========
# 1. 确保 BUILD 文件中有 alwayslink = 1
# 2. 确保 Calculator 使用 REGISTER_CALCULATOR 宏
# 3. 确保依赖关系正确

# 检查 Calculator 是否被注册
nm -C bazel-bin/path/to/my_app | grep REGISTER

12.3 编译内存不足

1
2
3
4
5
6
7
8
9
# ========== 错误信息 ==========
# java.lang.OutOfMemoryError: Java heap space

# ========== 解决方案 ==========
# 增加 Bazel 内存限制
bazel build --host_jvm_args=-Xmx8g //path:target

# 或在 .bazelrc 中配置
# build --host_jvm_args=-Xmx8g

12.4 缓存问题

1
2
3
4
5
6
7
8
9
# ========== 问题:修改后编译结果未更新 ==========
# ========== 解决方案 ==========
# 清理缓存
bazel clean
bazel build //path:target

# 完全清理
bazel clean --expunge
rm -rf ~/.cache/bazel

十三、总结

概念 说明 文件
WORKSPACE 项目根标记 + 外部依赖 WORKSPACE
BUILD 构建规则定义 BUILD
cc_library C++ 库 BUILD
cc_binary 可执行文件 BUILD
alwayslink 强制链接(Calculator 必需) BUILD
bazel build 编译命令 命令行
bazel test 测试命令 命令行

下篇预告

MediaPipe 系列 07:Calculator 开发规范——从接口到实现

深入讲解 Calculator 开发规范、接口设计、错误处理、性能优化。


参考资料

  1. Bazel. Official Documentation
  2. Bazel. C++ Rules
  3. Google AI Edge. MediaPipe Build Instructions

系列进度: 6/55
更新时间: 2026-03-12


MediaPipe 系列 06:Bazel 构建系统——MediaPipe 编译入门完整指南
https://dapalm.com/2026/03/12/MediaPipe系列06-Bazel构建系统:MediaPipe编译入门/
作者
Mars
发布于
2026年3月12日
许可协议