如何基于Python、CMake与Docker构建支持Windows和Linux(x86_64/aarch64)的统一C++跨平台构建系统
基于Python的跨平台C++/Qt构建编排方案
我刚好在团队里解决过几乎一模一样的多平台C++/Qt构建碎片化问题,分享一下我们用Python脚本实现的无缝构建编排方案,完全能满足你从单一Linux主机构建多平台目标的需求。
整体架构设计
我们把Python脚本拆成了4个核心模块,各司其职又能协同工作:
- 配置解析模块:负责读取
CMakePresets.json和自定义的工具链配置文件 - 交互菜单模块:给用户提供可视化的构建选项选择
- 环境编排模块:自动搭建Wine/MinGW/MSVC或Docker构建环境
- 构建执行模块:调用CMake、Ninja等工具完成编译
核心模块实现细节
1. 解析CMakePresets.json,复用现有配置
不需要重新造轮子,直接用Python的json模块加载预设文件,提取关键信息:
import json def load_cmake_presets(presets_path="./CMakePresets.json"): with open(presets_path, "r") as f: presets = json.load(f) # 整理成易访问的字典,键为预设名称,值为对应的配置 build_presets = {} for preset in presets.get("buildPresets", []): build_presets[preset["name"]] = { "generator": preset.get("generator", "Ninja"), "toolchainFile": preset.get("toolchainFile"), "cacheVariables": preset.get("cacheVariables", {}), "environment": preset.get("environment", {}) } return build_presets
这样就能直接复用你已经定义好的CMake预设,不用重复配置。
2. 交互式菜单,让选择更直观
用简单的循环+input就能实现基础菜单,如果想要更美观的界面可以用prompt-toolkit库:
def show_build_menu(presets): print("=== 选择构建配置 ===") # 按平台分组显示预设,更清晰 win_presets = [name for name in presets if "windows" in name.lower()] linux_presets = [name for name in presets if "linux" in name.lower()] print("\n[Windows 构建选项]") for idx, preset_name in enumerate(win_presets, 1): print(f"{idx}. {preset_name}") print("\n[Linux 构建选项]") for idx, preset_name in enumerate(linux_presets, len(win_presets)+1): print(f"{idx}. {preset_name}") while True: try: choice = int(input("\n输入选项编号: ")) all_presets = win_presets + linux_presets if 1 <= choice <= len(all_presets): return all_presets[choice-1] else: print("无效选项,请重新输入") except ValueError: print("请输入数字编号")
3. 环境编排:自动处理Wine和Docker场景
Wine+MinGW/MSVC 构建Windows目标
脚本需要自动设置Wine环境,处理路径转换,调用对应的工具链:
import os import subprocess def setup_wine_environment(wine_prefix="./.wine"): # 创建Wine前缀(首次运行会自动初始化) os.makedirs(wine_prefix, exist_ok=True) env = os.environ.copy() env["WINEPREFIX"] = os.path.abspath(wine_prefix) # 把便携工具链路径加入Wine的PATH env["WINEPATH"] = "C:\\mingw64\\bin;C:\\Qt\\6.5.0\\mingw_64\\bin;C:\\Program Files\\Microsoft Visual Studio\\2022\\BuildTools\\bin" return env def build_with_wine(preset_config, project_root, build_dir): env = setup_wine_environment() # 转换Linux路径为Wine能识别的Windows路径 wine_project_root = subprocess.check_output(["winepath", "-w", project_root], env=env).decode().strip() wine_build_dir = subprocess.check_output(["winepath", "-w", build_dir], env=env).decode().strip() # 构造CMake命令 cmake_cmd = [ "wine", "cmake", "-S", wine_project_root, "-B", wine_build_dir, "-G", preset_config["generator"] ] # 添加工具链文件(如果有) if preset_config.get("toolchainFile"): wine_toolchain = subprocess.check_output(["winepath", "-w", preset_config["toolchainFile"]], env=env).decode().strip() cmake_cmd.extend(["-DCMAKE_TOOLCHAIN_FILE=" + wine_toolchain]) # 添加缓存变量 for var, value in preset_config["cacheVariables"].items(): cmake_cmd.extend([f"-D{var}={value}"]) # 执行配置和构建 subprocess.run(cmake_cmd, env=env, check=True) subprocess.run(["wine", "cmake", "--build", wine_build_dir], env=env, check=True)
MSVC注意事项:需要先通过Wine运行vcvarsall.bat初始化环境,可以用嵌套命令实现:
msvc_init_cmd = 'cmd /c "C:\\Program Files\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Auxiliary\\Build\\vcvarsall.bat x64 && cmake ..."' subprocess.run(["wine", "cmd", "/c", msvc_init_cmd], env=env, check=True)
Docker容器构建Linux目标
预先制作包含所有工具链的Docker镜像(比如Ubuntu+GCC+Qt+aarch64交叉编译工具链),脚本动态生成docker run命令:
def build_with_docker(preset_config, project_root, build_dir, docker_image="my-qt-build-env:latest"): # 挂载项目目录和构建目录到容器内 volumes = [ f"{os.path.abspath(project_root)}:/project", f"{os.path.abspath(build_dir)}:/build" ] # 构造CMake配置命令 docker_config_cmd = [ "docker", "run", "--rm", *[f"-v {vol}" for vol in volumes], docker_image, "cmake", "-S", "/project", "-B", "/build", "-G", preset_config["generator"] ] # 添加工具链和缓存变量 if preset_config.get("toolchainFile"): docker_config_cmd.extend(["-DCMAKE_TOOLCHAIN_FILE=/project/" + os.path.basename(preset_config["toolchainFile"])]) for var, value in preset_config["cacheVariables"].items(): docker_config_cmd.extend([f"-D{var}={value}"]) # 执行配置和构建 subprocess.run(docker_config_cmd, check=True) subprocess.run([ "docker", "run", "--rm", *[f"-v {vol}" for vol in volumes], docker_image, "cmake", "--build", "/build" ], check=True)
交叉编译aarch64时,只要在Docker镜像中安装aarch64-linux-gnu-gcc,并在CMake预设里指定对应的工具链文件即可。
4. 便携工具链管理,避免系统级安装
脚本可以集成工具链的自动下载和无交互安装:
def install_portable_qt(qt_version="6.5.0", target="windows-x86_64-mingw", install_dir="./.toolchains"): os.makedirs(install_dir, exist_ok=True) # 使用Qt在线安装器进行无交互安装 installer_cmd = [ "./qt-unified-linux-x64-online.run", "--accept-licenses", "--accept-obligations", "--confirm-command", f"install qt.qt6.{qt_version}.{target}", f"--installationFolder={os.path.abspath(install_dir)}/qt" ] subprocess.run(installer_cmd, check=True)
把工具链安装在项目目录下的.toolchains文件夹里,团队成员同步项目时可以共享,或者脚本首次运行自动下载。
额外优化建议
- 构建缓存:每个预设对应独立的构建目录(比如
./build/windows-mingw),避免冲突,还可以开启CMake的缓存加速 - 日志记录:把构建输出重定向到日志文件,方便后续排查问题
- 团队共享配置:把Docker镜像名、Wine前缀路径、工具链安装目录等放在
build_config.json里,团队成员同步这个文件即可统一环境 - 错误处理:给subprocess调用添加异常捕获,友好提示用户构建失败的原因和可能的解决方向
内容的提问来源于stack exchange,提问作者A van Olphen
相关产品推荐
相关产品推荐

