第03章 - 构建系统与环境搭建
3.1 构建系统为什么会劝退人
MapLibre Native 的构建链在同类项目里算复杂:核心是 C++ 静态库,Apple 平台用 CMake 生成 Xcode 工程,Android 用 Gradle 套 CMake 交叉编译 JNI,Linux/Windows 用 CMake 直接产可执行文件,另有一套 Bazel 体系用于部分平台与测试。同一份源码被多套工具链驱动,日志里的路径前缀完全不一样。
这一章的目标很具体:让环境能编、能跑、能验证。先讲版本矩阵,再按平台给最短成功路径,最后是这套构建体系特有的坑。
| 组件 | main 分支要求 | 依据 |
|---|---|---|
| CMake | ≥ 3.25 | CMakeLists.txt 第 1 行 |
| C++ 标准 | C++20 | CMakeLists.txt 的 CMAKE_CXX_STANDARD |
| Bazel | 8.6.0(.bazelversion 固定) |
.bazelversion |
| Android minSdk | 23 | MapLibreAndroid/build.gradle.kts |
| Android NDK | 28.2.13676358 | platform/android/buildSrc/src/main/kotlin/Versions.kt |
| iOS deployment target | 15.5 | CMakePresets.json 的 ios 基础 preset |
| macOS deployment target | 14.3 | CMakePresets.json 的 macos 基础 preset |
C++20 和 CMake 3.25 是两个硬门槛。Ubuntu 22.04 自带 CMake 3.22,apt install cmake 装出来不够用,必须用 Kitware 官方源或 pip 装;编译器的 C++20 支持也要够新。
3.2 后端必须先选
main 分支不允许”什么都不选”或”选两个”。cmake/validate-backend-options.cmake 在配置阶段就拦截:
if (backend_count EQUAL 0)
message(FATAL_ERROR
"You need to set a rendering backend. "
"Set exactly one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, or MLN_WITH_WEBGPU.")
elseif (backend_count GREATER 1)
message(FATAL_ERROR
"Multiple rendering backends selected. "
"Please enable only one of: ...")
endif()
同一文件还拦截两个已废弃开关:
if (MLN_LEGACY_RENDERER)
message(FATAL "The legacy renderer is no longer supported")
endif()
if (MLN_DRAWABLE_RENDERER)
message(FATAL "Do not pass MLN_DRAWABLE_RENDERER, the drawable renderer is now the default")
endif()
从旧脚本复制参数的人会连续撞两次:先撞 MLN_LEGACY_RENDERER,删掉后撞”没选后端”(因为 MLN_WITH_OPENGL 在 main 里默认是 OFF,这一点与旧版相反)。
| 后端 | 开关 | 额外依赖 | 典型平台 |
|---|---|---|---|
| OpenGL | MLN_WITH_OPENGL |
EGL/Wayland 或 X11 | Linux、Windows、Android |
| Metal | MLN_WITH_METAL |
Apple SDK | iOS、macOS |
| Vulkan | MLN_WITH_VULKAN |
Vulkan SDK / 驱动 | Linux、Windows、Android(实验) |
| WebGPU | MLN_WITH_WEBGPU + MLN_WEBGPU_IMPL_DAWN 或 MLN_WEBGPU_IMPL_WGPU |
Dawn 或 wgpu-native | 实验 |
3.3 CMakePresets:主要构建入口
main 分支的 CMakePresets.json 覆盖几乎所有组合,直接用它比手写参数稳:
git clone --recurse-submodules -j8 https://github.com/maplibre/maplibre-native.git
cd maplibre-native
cmake --preset linux-opengl
cmake --build build-linux-opengl --target mbgl-render
常用 preset 与产物目录:
| preset | 后端 | 二进制目录 | 适用 |
|---|---|---|---|
linux-opengl |
OpenGL | build-linux-opengl |
Linux 默认 |
linux-vulkan |
Vulkan | build-linux-vulkan |
Linux Vulkan |
linux-webgpu-dawn |
WebGPU | build-linux-webgpu-dawn |
实验 |
ios-metal |
Metal | build-ios |
iOS 生产 |
ios-webgpu-dawn |
WebGPU | build-ios-webgpu-dawn |
实验 |
macos-metal |
Metal | build-macos-metal |
macOS |
windows-opengl |
OpenGL | 对应目录 | Windows |
harmonyos |
— | 对应目录 | HarmonyOS |
Linux 上 linux-opengl 现在默认用 Wayland,官方文档明确要求装 libegl1-mesa-dev。用 X11 的话在 preset 基础上加 -DMLN_WITH_WAYLAND=OFF -DMLN_WITH_X11=ON。
3.4 Linux:最短路径
# 构建工具
apt install build-essential clang cmake ccache ninja-build pkg-config
# 系统依赖
apt install libcurl4-openssl-dev libglfw3-dev libuv1-dev libpng-dev \
libicu-dev libjpeg-turbo8-dev libwebp-dev xvfb
# Wayland 下的 EGL
apt install libegl1-mesa-dev
cmake --preset linux-opengl
cmake --build build-linux-opengl --target mbgl-render
./build-linux-opengl/bin/mbgl-render \
--style https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json \
--output out.png
无头服务器会报 Error: Failed to open X display.,官方给的修法是 xvfb-run -a:
xvfb-run -a ./build-linux-opengl/bin/mbgl-render \
--style https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json \
--output out.png
也可以改用 Docker 隔离依赖:
cd platform/linux
docker build -t maplibre-native-image .
cd ../..
docker run --rm -it -v "$(pwd)":/root/ maplibre-native-image
仓库根目录还有 flake.nix,装了 Nix 的人直接 nix develop 进一个依赖齐全的 shell,比手动装依赖干净。
3.5 iOS / macOS:CMake 生成 Xcode 工程
main 分支的 iOS 构建已从”Bazel 生成 xcodeproj”改为 CMake preset 直接产 Xcode 工程。docs/mdbook/src/platforms/ios/ios-rendering-backends.md 给的标准流程:
cmake --preset ios-metal -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
xed "build-ios/MapLibre Native.xcodeproj"
然后在 Xcode 里选 app target 运行,或命令行构建:
cmake --build build-ios -- -sdk iphonesimulator -arch arm64
后端切换就是换 preset:
# Metal(默认,生产推荐)
cmake --preset ios-metal -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
# WebGPU + Dawn(实验)
cmake --preset ios-webgpu-dawn -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
# WebGPU + wgpu-native(实验)
cmake --preset ios-webgpu-wgpu -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
每个 preset 实际注入的变量:
| preset | CMake 变量 |
|---|---|
ios-metal |
MLN_WITH_METAL=ON |
ios-webgpu-dawn |
MLN_WITH_WEBGPU=ON、MLN_WEBGPU_IMPL_DAWN=ON |
ios-webgpu-wgpu |
MLN_WITH_WEBGPU=ON、MLN_WEBGPU_IMPL_WGPU=ON |
Objective-C 层是共享的,后端差异靠编译期条件编译选择,例如 MLNMapView+WebGPU.mm 里的 #if MLN_WEBGPU_IMPL_DAWN / #elif MLN_WEBGPU_IMPL_WGPU。这也意味着同一份 iOS 业务代码换后端不需要改。
macOS 的 preset 是 macos-metal、macos-metal-xcode、macos-vulkan、macos-vulkan-xcode。加 -xcode 后缀的是生成 Xcode 工程,不加的是 Ninja。
Bazel 体系仍然存在(.bazelversion 8.6.0、MODULE.bazel),主要服务于测试与部分平台构建,不再是 iOS 开发的主路径。
3.6 Android:Gradle + Nix
Android 开发者的官方入口 platform/android(用 Android Studio 打开)。命令行构建:
cd platform/android
./gradlew :MapLibreAndroidTestApp:assembleVulkanDebug
注意 target 名里的 Vulkan——Android 现在有 OpenGL 与 Vulkan 两套构建变体。装 NDK 比较麻烦时,官方推荐 Nix:
nix develop .#android
cd platform/android
./gradlew :MapLibreAndroidTestApp:assembleVulkanDebug
TestApp 用 MapTiler 矢量瓦片,需要 API Key。Gradle 首次运行会读 MLN_API_KEY 环境变量写进 MapLibreAndroidTestApp/src/main/res/values/developer-config.xml:
export MLN_API_KEY=your_maptiler_key
./gradlew :MapLibreAndroidTestApp:assembleDebug
NDK 版本固定为 28.2.13676358(Versions.kt)。本机装别的版本时 Gradle 会自行下载;离线环境会卡住,需要预装或用 Nix。
CMake 由 Gradle 调起,platform/android/android.cmake 开头就强制要求工具链:
if(NOT ANDROID_NDK_TOOLCHAIN_INCLUDED)
message(FATAL_ERROR "-- Toolchain file not included, see https://developer.android.com/ndk/guides/cmake")
endif()
链接库是 EGL、GLESv3、jnigraphics、log、z,这是 Android 端 OpenGL 变体的直接体现。
3.7 验证构建结果
“编译通过”不等于”能正确渲染”,至少跑一项:
# 单元测试
cmake --build build-linux-opengl --target mbgl-test-runner
./build-linux-opengl/mbgl-test-runner
# 渲染测试(与基准 PNG 做像素比对)
./build-linux-opengl/mbgl-render-test-runner \
--manifestPath metrics/linux-clang8-release-style.json
渲染测试跑完会在 manifest 旁生成 HTML 报告,含每个用例的 expected.png、actual.png、diff.png。只跑单个用例加过滤器:
./build-linux-opengl/mbgl-render-test-runner \
--manifestPath metrics/linux-clang8-release-style.json \
--filter "render-tests/fill-visibility/visible"
3.7.1 产物与 target 名
构建产物按平台分布在不同目录,找产物时按 target 名 grep 比按路径猜快:
| target | 产物 | 用途 |
|---|---|---|
mbgl-core |
libmbgl-core.a |
核心静态库,供自研 C++ 应用链接 |
mbgl-render |
build-*/bin/mbgl-render |
命令行出图工具 |
mbgl-test-runner |
build-*/mbgl-test-runner |
C++ 单元测试 |
mbgl-render-test-runner |
build-*/mbgl-render-test-runner |
像素比对渲染测试 |
mbgl-benchmark |
build-*/mbgl-benchmark |
性能基准 |
自研 C++ 应用只需要链接 mbgl-core,但要注意它依赖的第三方库也得一起链:vendor/ 下的 icu、sqlite、csscolorparser、parsedate 等在 CMake 里是独立 target(mbgl-vendor-icu 等),mbgl-core 静态链接后这些符号仍需在最终链接阶段提供。用 target_link_libraries(app PRIVATE mbgl-core) 时 CMake 会自动传递 PUBLIC 依赖,但 PRIVATE 依赖不会——缺符号时先看 mbgl-core 的链接可见性。
3.7.2 增量构建的效率
全量构建耗时以十分钟计,日常开发靠增量。几个加速手段:
ccache:preset 里已配CMAKE_CXX_COMPILER_LAUNCHER,同一个文件改一行后重编只走缓存命中。ninja:preset 默认生成器,比 make 快。- 只编目标:
cmake --build build-linux-opengl --target mbgl-render,不要无参数全编。 - 并行度:
cmake --build ... -j $(nproc),默认可能只用一半核。
一个常见误解是”改了头文件必然全量重编”。实际上只有 include 该头文件的翻译单元会重编;把改动限制在 .cpp 内部能显著减少重编范围。
3.8 排错清单
| 现象 | 原因 | 修法 |
|---|---|---|
CMake 3.25 or higher is required |
系统 CMake 太旧(Ubuntu 22.04 是 3.22) | Kitware 源、pip 或 cmake 官方二进制 |
The legacy renderer is no longer supported |
传了 -DMLN_LEGACY_RENDERER=ON |
删除该参数 |
Do not pass MLN_DRAWABLE_RENDERER |
传了旧开关 | 删除,drawable 已是默认 |
You need to set a rendering backend |
没开任何 MLN_WITH_*(OpenGL 在 main 默认 OFF) |
用 preset 或显式打开一个 |
Multiple rendering backends selected |
同时开了两个后端 | 只留一个 |
Failed to open X display |
无头环境 | xvfb-run -a,或容器里起 Xvfb |
-- Toolchain file not included |
直接对 CMake 编 Android | 走 Gradle,或手动传 NDK 的 android.toolchain.cmake |
| Wayland 相关链接错误 | 缺 libegl1-mesa-dev |
安装,或关 Wayland 开 X11 |
编译被 -Werror 卡住 |
MLN_WITH_WERROR 默认 ON |
加 -DMLN_WITH_WERROR=OFF 先编通 |
| Gradle 卡在下载 NDK | 本机无 28.2.13676358 | 预装或用 nix develop .#android |
3.9 生产建议
- 用 preset 而不是手写参数:后端单选、平台变量都在 preset 里,手写必然漏项,且升级后预设会跟着改。
- CI 镜像锁死工具链:CMake 3.25+、C++20 编译器、NDK 28.2,三样都固定。
-Werror对编译器版本敏感,镜像漂移会随机构建失败。 - 日常开发用 ccache:
CMAKE_CXX_COMPILER_LAUNCHER=ccache(preset 里已配),冷编译与增量编译差一个数量级。 - 核心自己编、SDK 用官方包:绝大多数项目不需要编 Android/iOS SDK,用 Maven/SwiftPM 官方包;只有改核心时才维护 fork。
- 渲染测试进 CI:它比单元测试更容易发现样式回归,输出是可视化 HTML,排查成本低。
3.10 本章小结
| 要点 | 内容 |
|---|---|
| 硬门槛 | CMake ≥ 3.25、C++20、Bazel 8.6.0 |
| 后端 | 编译期单选,validate-backend-options.cmake 强制;MLN_WITH_OPENGL 默认 OFF |
| 废弃开关 | MLN_LEGACY_RENDERER、MLN_DRAWABLE_RENDERER 传入即 FATAL |
| 主要入口 | CMakePresets.json:linux-opengl、ios-metal、macos-metal、windows-opengl 等 |
| iOS 构建 | cmake --preset ios-metal -DDEVELOPMENT_TEAM_ID=... 生成 Xcode 工程 |
| Android 构建 | Gradle(:MapLibreAndroidTestApp:assembleVulkanDebug),NDK 28.2.13676358,可用 Nix |
| 环境隔离 | Docker(platform/linux/Dockerfile)与 Nix(flake.nix)两条路 |
| 验证 | mbgl-test-runner、mbgl-render-test-runner + HTML 报告 |