CMake is not unfamiliar to many who are familiar with C and C++. Today, we will look at a somewhat unusual application scenario: using CMake to manage and run Python applications. This approach can be effective in certain situations, such as when code generation is done in C++, and tool scripts are written in Python, while the entire project is integrated through CMake, allowing it to ignore the differences in dependency virtual environments.First, let’s look at a simple example, with the project organized as follows:
project/├── app1/ # Python Application 1│ ├── CMakeLists.txt # Module build script│ ├── main.py # Main program│ ├── requirements.txt # Dependency list├── app2/ # Python Application 2│ ├── CMakeLists.txt │ ├── main.py │ ├── requirements.txt ├── app3/ # C++ Application│ ├── CMakeLists.txt │ ├── main.cpp # C++ source code└── build/ # Build output directory ├── CMakeLists.txt # Top-level build script
Analysis of the top-level CMakeLists.txt
# Specify the minimum CMake versioncmake_minimum_required(VERSION 3.28)# Set the project nameproject(ExamplePythonSetup)# Find the Python interpreterfind_package(Python3 REQUIRED COMPONENTS Interpreter)# Define a function to create a Python virtual environmentfunction(create_venv venv_dir requirements_path) # Check if the virtual environment already exists if(EXISTS ${venv_dir}) message(STATUS "The virtual environment already exists at ${venv_dir}, skipping creation.") return() endif() # Ensure the dependency file exists if(NOT EXISTS ${requirements_path}) message(FATAL_ERROR "Dependency file not found: ${requirements_path}") endif() # Create the virtual environment execute_process( COMMAND ${Python3_EXECUTABLE} -m venv ${venv_dir} RESULT_VARIABLE venv_creation_ret_code ) # Check the creation result if(venv_creation_ret_code) message(FATAL_ERROR "Failed to create virtual environment at ${venv_dir}!") endif() # Install dependencies execute_process( COMMAND ${venv_dir}/bin/pip install -r ${requirements_path} RESULT_VARIABLE pip_install_ret_code ) # Check the installation result if(pip_install_ret_code) message(FATAL_ERROR "Failed to install dependencies from ${requirements_path}!") endif() # Output success message message(STATUS "The virtual environment has been configured at ${venv_dir}, dependencies have been installed from ${requirements_path}")endfunction()# Include all subdirectories for buildingadd_subdirectory(app1)add_subdirectory(app2)add_subdirectory(app3)
It does a few things: 1. Finds the system Python interpreter; 2. Defines the create_venv function to implement the creation of the virtual environment and installation of dependencies; 3. Unifies the build process of each submodule.For the Python application module app1, the CMakeLists.txt is as follows:
# Specify the app1 virtual environment pathset(APP1_VENV ${CMAKE_BINARY_DIR}/app1_venv)# Create app1's virtual environmentcreate_venv(${APP1_VENV} ${CMAKE_SOURCE_DIR}/app1/requirements.txt)# Add a custom target to run app1add_custom_target(run_app1 COMMAND ${APP1_VENV}/bin/python ${CMAKE_SOURCE_DIR}/app1/main.py DEPENDS ${APP1_VENV})
Another app2’s CMakeLists.txt is as follows:
# Specify the app2 virtual environment pathset(APP2_VENV ${CMAKE_BINARY_DIR}/app2_venv)# Create app2's virtual environmentcreate_venv(${APP2_VENV} ${CMAKE_SOURCE_DIR}/app2/requirements.txt)# Add a custom target to run app2add_custom_target(run_app2 COMMAND ${APP2_VENV}/bin/python ${CMAKE_SOURCE_DIR}/app2/main.py DEPENDS ${APP2_VENV})
For the C++ module app3, it is as follows:
# Compile C++ code into an executableadd_executable(main main.cpp)# Add a custom target to run app3add_custom_target(run_app3 COMMAND ${CMAKE_BINARY_DIR}/app3/main DEPENDS main)
Initializing the build environmentEnter the build directory and execute
cmake ..
At this point, CMake will automatically trigger the process of creating virtual environments, generating independent virtual environments for the two Python modules, and installing dependencies based on requirements.txt.
# Build all targetscmake --build .# Run applications (both Python and C++)make run_app1 # Run the first Python applicationmake run_app2 # Run the second Python applicationmake run_app3 # Run the C++ application
Additionally, in VS Code, through the CMake extension, you can directly see these three run targets in the bottom status bar of the compiler, and click the button to execute them without needing to remember command line arguments.If you prefer the efficient build of Ninja, you can also operate like this:
# Generate Ninja configurationcmake .. -G Ninja# Execute buildcmake --build .# Run applications with Ninja ninja run_app1ninja run_app2ninja run_app3
In summary, with the above method, CMake is no longer just a tool exclusive to C++, but a good choice for cross-language projects. So, when encountering such situations, consider trying this approach.