With further progress, there are advanced techniques, this article mainly introduces some of my habits on Windows.
- Windows GUI applications
- Windows console applications do not display the console black box
- Generating .sln files on Windows
- Custom CMake variables
- Super useful custom operations
- Built-in commands (copy, delete, etc.)
- Execute commands
- Generate files
- make install
Windows GUI Applications
add_executable(${PROJECT_NAME} WIN32
${SRC_FILES}
)
WIN32parameter tellsCMaketo configure the application as a Windows GUI application.MACOSX_BUNDLEparameter configures a macOS bundle application.EXCLUDE_FROM_ALLparameter means that the program will not be compiled by default. If you want to compile it, you need to compile it manually, for example,make testspecifies the compilation name astest.- If no options are specified, it is configured as a console application.
Windows Console Applications Do Not Display the Console Black Box
Sometimes you want the application to run in the background without any pop-up windows. You can set it as follows on Windows:
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ENTRY:mainCRTStartup")
Or like this:
set_target_properties(${PROJECT_NAME} PROPERTIES
LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
)
Generating .sln Files on Windows
In Windows development, IDE is likely to be closely related to VS, so I usually use scripts to generate .sln files, and then open the generated .sln file directly in VS for project development. Here is a script example:
cmake -S . ^
-B build_win ^
-G "Visual Studio 16" ^
-A Win32 ^
-DCMAKE_GENERATOR_TOOLSET=v142 ^
-DCMAKE_SYSTEM_VERSION=8.1 ^
-DPROJECT_ARCH=x86 ^
-DBUILD_DEMO=ON
- **
-S .**: Specifies the source code directory..means the current directory, which is the directory where the script is located. -B build_win: Specifies the build directory.build_winindicates the path of the build directory.- **
-G "Visual Studio 16"**: Specifies the generator."Visual Studio 16"indicates using Visual Studio 2019 as the generator. - **
-A Win32**: Specifies the architecture of the target platform.Win32indicates a 32-bit Windows platform. - **
-DCMAKE_GENERATOR_TOOLSET=v142**: This parameter is used to specify the version of the toolset used for building. - **
-DCMAKE_SYSTEM_VERSION=8.1**: This parameter is used to specify the system version of the target platform. - **
-DPROJECT_ARCH=x86**: Defines aCMakevariable namedPROJECT_ARCHand sets its value tox86. This was mentioned in the previous cross-platform article. - **
-DBUILD_DEMO=ON**: Defines aCMakevariable namedBUILD_DEMOand sets its value toON.
Additionally:
- If the
-Gparameter is not specified, CMake will try to select an appropriate generator based on the current system’s default configuration. Usually, it will choose the default generator that matches the operating system and system version. - If the
-DCMAKE_GENERATOR_TOOLSETparameter is not specified, CMake will use the default generator toolset. This means that the default toolset associated with the selected generator and the build target platform will be used. - If the
-DCMAKE_SYSTEM_VERSIONparameter is not specified, CMake will use the default system version. This may cause issues when using certain specific system features or APIs because CMake cannot distinguish the exact version of the target system.
If these parameters are omitted, CMake will build based on the environment’s default configuration, which may lead to different behaviors and results in different environments. Therefore, to ensure consistency and reproducibility of the build, it is best to explicitly specify these parameters according to the required configuration.
In actual projects, I prefer to use scripts to directly fix the compilation parameters, as project compilation parameters are generally fixed, which can be done through several scripts to make it easier and less error-prone.
Custom CMake Variables
Following the previous script, let’s look at how to pass parameters to CMake, which is also used quite frequently in projects.
# Switch ON/OFF type
option(BUILD_DEMO "Build the demo" OFF)
# Direct if statement
if (BUILD_DEMO)
add_subdirectory(example)
endif()
# String type
if(PROJECT_ARCH STREQUAL "x86_64")
add_compile_options(-m64 -march=x86-64)
add_link_options(-m64)
elseif(PROJECT_ARCH STREQUAL "x86")
add_compile_options(-m32)
add_link_options(-m32)
endif()
Super Useful Custom Operations
As long as you want to achieve build automation through CMake, I do not allow anyone to not know the add_custom_command command, which is mainly used to add custom build commands to the project. The most commonly used syntax is as follows; first, look at the official description:
add_custom_command(TARGET target_name
[PRE_BUILD | PRE_LINK | POST_BUILD]
COMMAND command...
[ARGS arguments...]
[WORKING_DIRECTORY dir]
[COMMENT comment...]
[DEPENDEES dependees...]
[BYPRODUCTS files...]
[IMPLICIT_DEPENDS language depend_list...]
[COMMAND_EXPAND_LISTS]
[VERBATIM]
[APPEND]
[USES_TERMINAL]
[OUTPUT output_files...])
Here are some common parameters and options explained:
TARGET target_name: Specifies the target (executable, library, etc.) that needs to execute the custom command.PRE_BUILD | PRE_LINK | POST_BUILD: Specifies when the custom command is executed: during pre-build, pre-link, or post-build.COMMAND command...: Specifies the command to be executed. It can be a shell command, external tool, or script.ARGS arguments...: Specifies the arguments for the command.WORKING_DIRECTORY dir: Specifies the working directory for the command execution.COMMENT comment...: Adds a comment to the custom command.DEPENDEES dependees...: Specifies the dependent targets; the custom command will be re-executed when the dependent targets change.BYPRODUCTS files...: Specifies the files generated by the custom command.IMPLICIT_DEPENDS language depend_list...: Specifies implicit dependencies, which are usually generated by the generator.COMMAND_EXPAND_LISTS: Expands variable references in the parameter list.VERBATIM: Executes the command as is, without any escaping.APPEND: Appends the custom command to the existing commands of the target.USES_TERMINAL: Indicates that the command requires terminal support to view the output.OUTPUT output_files...: Specifies the output files generated by the custom command.
Using add_custom_command allows you to flexibly add custom commands during the build process, such as executing scripts, generating files, copying files, etc. These commands can be combined with CMake’s target and dependency mechanisms to achieve more advanced build automation.
Let me give some frequently used command examples:
Built-in Commands (Copy, Delete, etc.)
Copy the output of module A to the output path of the current module:
add_custom_command(TARGET ${PROJECT_NAME}
PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
$<TARGET_FILE_DIR:A>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
)
CMAKE_COMMAND -E can carry multiple built-in commands. Here are some commonly used built-in commands and their explanations:
copy: Copies files or directories. For example:copy <source> <destination>remove: Deletes files or directories. For example:remove <file>make_directory: Creates a directory. For example:make_directory <directory>remove_directory: Deletes a directory. For example:remove_directory <directory>rename: Renames files or directories. For example:rename <oldname> <newname>echo: Outputs a text message. For example:echo <message>- ……
It is recommended to check the CMake official website for detailed supported parameters and the minimum required CMake version: https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-E
There are also platform-specific commands, such as write_regv to write to the registry on Windows.
Execute Commands
On Linux, it may be simpler to directly call commands, as follows:
add_custom_command(TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND mkdir -p
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/config/"
)
On Windows, this is usually avoided because executing commands may have issues with the path
/.
Generate Files
This section is not used much, here is a simple example:
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/output.txt
COMMAND echo "Hello, CMake!" > output.txt
COMMENT "Generating output.txt"
VERBATIM
)
# Import all files
add_executable(${PROJECT_NAME}
${SRC_FILES}
output.txt
)
One thing to note is that the generated file must be used, otherwise
add_custom_commandwill not execute.
make install
The make install command is commonly used in build systems to install the generated files to a specified directory. By default, this command will not execute automatically unless explicitly called.
In CMake, you can use the install command to define installation rules. This command instructs CMake to install specific files, directories, or targets during the build process. For example, you can install generated binaries, library files, header files, configuration files, etc.
After using the install command, run make install (on Unix systems) or cmake --build . --target install (to install directly after building) or cmake -DBUILD_TYPE=Debug -P build/cmake_install.cmake (BUILD_TYPE can be specified according to actual needs, here only install) (on Windows systems) to execute the installation action. This will copy the generated files to the specified installation directory according to the defined rules.
Here’s a simple example:
# Copy products to the product name folder in the project root directory
install(TARGETS ${PROJECT_NAME} DESTINATION ${PROJECT_ROOT_PATH}/${PROJECT_NAME})
# Copy the config folder in the project root directory to the product name folder in the project root directory
install(DIRECTORY "${PROJECT_ROOT_PATH}/config/" DESTINATION ${PROJECT_ROOT_PATH}/${PROJECT_NAME})
# Copy the README.md file in the project root directory to the product name folder in the project root directory
install(FILES "${PROJECT_ROOT_PATH}/README.md" DESTINATION ${PROJECT_ROOT_PATH}/${PROJECT_NAME})
If the build organization builds the product, actually using add_custom_command is already sufficient~