Python:如何编写一个脚本来使用CMake编译和运行C++程序?

3

假设我有以下项目结构:

|   CMakeLists.txt
|   run_experiments.py
+---libs
\---src
        main.cpp
        main.h

如何通过CMake使run_experiments.py编译程序并使用不同的命令行参数多次运行它? 我尝试过的方法:

# run_experiments.py

import os

os.system("mkdir bin build")
os.chdir("build")
os.system('cmake -G"Unix Makefiles" ..')
os.system("make")
# and so on...

但它已经看起来很丑了,我正在寻找最优雅的跨平台解决方案(例如在Windows上使用MinGW和Linux)。

更新:添加了我的CMakeLists.txt,这是CLion默认生成的:

cmake_minimum_required(VERSION 3.16)
project(test_tokenizing)

set(CMAKE_CXX_STANDARD 14)

add_executable(test_tokenizing src/main.cpp src/main.h)

1
cmake --build可以帮助您避免一些特定于平台的二进制文件,例如make。而且,您可以使用类似os.mkdir的东西来避免调用某些特定于操作系统的命令。您可能仍然需要一个子进程来调用cmake,对此我建议使用subprocess模块而不是os.system - 0x5453
@MarkSetchell Windows和Linux。 - i spin and then i lock
1
除非您的项目中的 CMakeLists.txt 包含一个名为 run 的可执行目标,否则您需要使用该可执行文件的名称来运行它。而这个名称肯定是 与平台相关 的(在 Windows 上具有 .exe 扩展名,在 Linux 上没有扩展名)。 - Tsyvarev
我在想,使用CMake构建C++项目并在构建可执行文件后让CMake运行Python脚本是否更容易,而不是使用Python脚本调用CMake。随着CMake 3.19引入的预设功能,用户甚至不需要再指定所有必需的CMake选项,这可能是您首先想要编写CMake调用脚本的主要原因。 - Corristo
1
根据您对@jignatius答案的回应,我了解到您和您的同事没有一致的构建环境,这就是为什么您不能假设CMake、NMake或Visual Studio在PATH中的原因。因此,也许这是您可以解决的一个角度,例如通过在docker容器中提供构建环境,或者使用像Conan这样的软件包管理器来打包库依赖项以及构建工具。关于CMake 3.19:如果这是唯一剩下的原因需要python调用CMake,您可以从源代码构建并将其安装到git存储库中。 - Corristo
显示剩余3条评论
2个回答

1
如果C++代码不是动态生成的或者类似的情况,我建议您在启动Python程序之前先构建它,然后仅从Python程序中执行它。
如果您有一个很好的理由需要从Python代码中构建它,我在https://github.com/kyotov/experiments为您创建了一个小例子。如下所示的内容适用于run_experiements.py:
import os
import subprocess


def main():
    os.makedirs('build', exist_ok=True)
    subprocess.check_call('cmake -B build -G "NMake Makefiles"', shell=True)
    subprocess.check_call('cmake --build .', shell=True, cwd='build')

    subprocess.check_call('experiments 1 2 3', shell=True, cwd='build')
    subprocess.check_call('experiments 2 3 4', shell=True, cwd='build')


if __name__ == '__main__':
    main()

我使用以下内容作为 main.cpp
#include <iostream>

int main(int argc, char **argv) {
    for (auto i = 0; i < argc; i++)
        std::cout << argv[i] << std::endl;
    return 0;
}

当我运行python run_experiements.py时,我会得到以下输出:

(base) C:\kamen\clion\experiments>python run_experiments.py
-- Configuring done
-- Generating done
-- Build files have been written to: C:/kamen/clion/experiments/build
[100%] Built target experiments
experiments
1
2
3
experiments
2
3
4

为了使其正常工作,您需要正确设置环境以找到所需的工具。在我的情况下,我使用Miniconda3进行Python开发,然后使用""C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat""将编译器和cmake添加至路径中。
如果您没有Linux机器,但是如果您将-G设为条件并使用Unix Makefiles,它也可能在Linux(以及其他地方)上运行。请注意,在Windows上,您不必担心.exe,因为即使您没有指定它,shell也足够聪明,会自动启动程序。

为使此功能正常工作,您需要正确设置环境以查找所需的工具 - 这是问题所在。脚本必须可以“开箱即用”,除了用户必须像我在MinGW中添加CMake和Make到PATH的那一刻。这段代码适用于它,但在Linux上不起作用,在那里可执行文件必须以./为前缀。 - i spin and then i lock
无论如何,感谢您付出努力回答我的问题。已点赞。 - i spin and then i lock
你可以在Unix上运行build/experiments而无需切换到build文件夹,从而避免使用"./"的需要...这会在Windows上造成一些小问题,因为你需要使用相反的斜杠。但是你可以使用os.path.sep来构建正确的路径分隔符...像这样的事情会越来越多,不幸的是它们会使解决方案变得越来越长。 - Kamen
你想在Windows上支持哪些构建系统?我担心总是需要一些预先设置才能将它们放在路径中... MSVC?MinGW?Cygwin? - Kamen

0
Python的subprocess模块可以让你运行像CMake和与之构建的二进制文件这样的外部程序。但是在Windows和Linux上可执行文件名会有所不同(在Windows上具有.exe扩展名)。你应该能够指定./test_tokenizing来运行你的程序,并且系统应该能够找到它,只要你指定了当前工作目录cwd
试一试:
import os
import platform
import subprocess
import sys


def main(*args):
    current_directory = os.getcwd()
    build_dir = os.path.join(current_directory, 'build')

    if not os.path.exists(build_dir):
        os.makedirs(build_dir)

    if platform.system() == 'Windows':
        exe = os.path.join(build_dir, 'test_tokenizing.exe')
    else:
        exe = os.path.join(build_dir, 'test_tokenizing')

    cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + build_dir]
    cmake_args += ['-G', 'Unix Makefiles']

    subprocess.run(['cmake', ".."] + cmake_args, cwd=build_dir, check=True)
    subprocess.run(['cmake', '--build', '.'], cwd=build_dir, check=True)
    p = subprocess.run([exe, *args], cwd=build_dir, capture_output=True, text=True)
    print(p.stdout)


if __name__ == '__main__':
    args = []
    if len(sys.argv) > 1:
        args += sys.argv[1:]
    main(*args)

(在subprocess.run中使用capture_outputtext需要Python 3.7+)。

您可以像这样将命令行参数传递给脚本:

python run_experiments.py hello world

参数将被转发到函数main(),该函数将使用它们调用您的C++可执行文件。

编辑:

以防万一./test_tokenizing在某些奇特的操作系统上无法工作或者您想包含一些特定于平台的编译器/构建标志,我已经放置了一个检查平台的代码,并指定了您的C++可执行文件的完整路径。


谢谢,我已经在Windows上使用MinGW和WSL Ubuntu进行了测试,它可以正常工作。但是为什么要使用-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=而不是-B呢?这与C/C++库有关吗?我的项目是一个可执行文件。使用绝对路径和相对路径有什么区别?我们不能通过在可执行文件前加上./来删除系统的检查吗?在Windows上添加.exe是不必要的,就像其他帖子中所说的那样。 - i spin and then i lock
@kenticent 你关于使用前缀 ./test_tokenizing 的观点是正确的。请查看我上面的更新。关于 DCMAKE_LIBRARY_OUTPUT_DIRECTORY=,如果你正在构建一些库文件,我在那里放置了它。这将为您将它们放置在构建文件夹中。 - jignatius
谢谢。我希望您添加使用if的替代版本,因为它在处理一些奇特的操作系统或编写依赖于特定平台的代码时非常有用。 - i spin and then i lock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接