-
Notifications
You must be signed in to change notification settings - Fork 2.7k
CMake and the build system
- What is CMake?
- Build tools supported by MuseScore
- Building a CMake project
- CMake files
- CMake variables
- Common tasks
CMake is a build system generator. It doesn't compile any code, and it doesn't even interact directly with the compiler. Instead, CMake's job is to write instructions for a native build tool (e.g. Make, Ninja, XCode, or Visual Studio), and then the build tool uses those instructions to run the compiler.
CMake serves as abstraction layer: we write one set of generic instructions for CMake, and then CMake translates those generic instructions into new instructions that are specific to whichever build tool happens to be available on your machine.
CMake can generate instructions for many build tools. These are the ones currently supported by MuseScore:
- Windows: Visual Studio, MinGW or Ninja.
- macOS: Xcode, Unix Makefiles or Ninja.
- Linux: Unix Makefiles or Ninja.
You can type cmake --help
to see the full list of CMake build system generators available on your platform, but note that MuseScore only supports the ones listed above.
These steps are the same for pretty much all CMake projects regardless of platform, build system, and compiler:
mkdir my_build_dir # Create build folder (CMake calls this the "binary directory").
cd my_build_dir # Change to it.
# First build only
cmake .. [options] # CONFIGURE: Run CMake to generate a build system for the native tool.
# Every build
cmake --build . # BUILD: Run the native build tool (or ask CMake to run it).
cmake --install . # INSTALL: Copy compiled files to destination folder.
# Optional
ctest # TEST: Run unit tests to check the program works as expected.
cpack # PACKAGE: Create binary archive or MSI/DMG installer.
The only difference should be the options you provide to CMake in the configure step (-D
and -G
are common).
Occasionally, options may be passed in at other steps too. See options for the build, install, test and package steps.
Hint: We provide a build.cmake
wrapper script that will run the above commands for you. See Compile on the command line.
Some projects introduce extra steps that use non-CMake tools, like custom prebuild.sh
or build_package.bat
scripts. This complicates the workflow and should be avoided if at all possible. It's OK to write a script, but you should get CMake, CPack, or the native build tool to run that script for you as part of one of the above steps. See Running external commands.
During the configure step, CMake was run inside the build directory (my_build_dir
), and the ..
argument was the (relative) path to the source directory. The fact that the source and build directories are different means that this is an out-of-source build. If they were the same then it would be an in-source build.
Comparison of in-source and out-of-source builds (click to show/hide)
The advantage of an out-of-source build is that it avoids cluttering the source directory with lots of generated files. You are able to maintain several configurations at once simply by creating a different build directory for each configuration. When you no longer need a particular configuration, or you want to build it again from scratch, you only have to delete its build directory and all the files related to that configuration will be gone.
The disadvantage of out-of-source builds is a slightly more complicated workflow. When editing CMake files, you will come across relative file paths, and you'll need to work out whether the file path is relative to the source directory or the build directory (or sometimes to the installation directory, but that complication exists even for in-source builds).
The CMakeList.txt
and *.cmake
files in the repository contain instructions that CMake uses to generate a build system for the native tool.
You'll find a file called CMakeLists.txt
at the root of MuseScore's repository and in most subdirectories.
When you run cmake ..
in the configure step, you are telling CMake where to look for the top-most CMakeLists.txt
, which defines the project as well as options and variables to control the build. The top-most CMakeLists.txt
also loads other CMakeLists.txt
in subdirectories, and those can load more CMakeLists.txt
in their subdirectories, and so on. This is the source tree.
project(mscore)
set(MUSESCORE_BUILD_MODE "dev") # define a variable
option(MUE_BUILD_CLOUD_MODULE "Build cloud module" ON) # define an option
add_subdirectory(src) # load src/CMakeLists.txt
Each CMakeList.txt
in the source tree contains instructions for processing the files in its own directory (the "source directory"). During the configure step, CMake creates a corresponding "binary directory" within the build folder to store the output of those instructions. (The output could be configured files produced during the configure step, or object/generated files produced during the build step. Installed files go elsewhere.) There is one binary directory for each source directory that contains a CMakeLists.txt
.
# Process file utility.sh.in in the current source directory
# to create utility.sh in the current build directory.
configure_file(utility.sh.in utility.sh)
# Copy utility.sh from the current build directory to the bin subfolder
# of the installation directory, dropping the .sh extension.
install(PROGRAM "${CMAKE_CURRENT_BINARY_DIR}/utility.sh" DESTINATION bin RENAME utility)
Hint: Take care with relative file paths in CMake. Depending on the context, they can be relative to the source directory, build directory, or the installation directory. Check the CMake documentation for the command in question (e.g. configure_file()
, install()
).
Additional instructions can be loaded into any CMakeLists.txt
file by way of the include()
command. This is commonly done to include CMake files or modules that contain custom function()
and macro()
definitions, so that the defined function or macro can be used in the current file.
A CMake module is just a file with .cmake
extension that happens to be saved in any of the CMAKE_MODULE_PATH
directories.
include(path/to/file.cmake) # load instructions from path/to/file.cmake
# OR
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/path/to")
include(file) # load instructions from path/to/file.cmake
Please note that MuseScore's modules are completely unrelated to CMake modules. Excluding the specific case of the CMAKE_MODULE_PATH
variable, if you see the word "module" in our CMake or C++ files it's probably referring to one of MuseScore's modules.
Our build.cmake
file is special. It is not part of the CMake build system and cannot be included in other CMake files. Instead, it's a script (like a shell script) that happens to be written in the CMake language. It can be invoked on the command line using cmake -P build.cmake
(or ./build.cmake
on Unix-like systems).
Essentially, all the script does is execute the commands outlined in Building a CMake project. Many projects create build.sh
, build.bat
or Makefile
wrapper scripts for their CMake code, but such scripts only work on a limited number of platforms. By writing our wrapper script in the CMake language, we can ensure it will work on all of the platforms that CMake itself supports.
CMake provides variables that allow you to keep track of where you are in the source and binary trees.
Variable | Meaning |
---|---|
CMAKE_CURRENT_SOURCE_DIR |
The directory of the current CMakeLists.txt being processed. |
CMAKE_CURRENT_BINARY_DIR |
The corresponding directory in the build tree. |
PROJECT_SOURCE_DIR |
The directory of the last CMakeLists.txt to include the project() keyword. |
PROJECT_BINARY_DIR |
The corresponding directory in the build tree. |
CMAKE_SOURCE_DIR |
The root of the source tree (i.e. the MuseScore folder). |
CMAKE_BINARY_DIR |
The root of the build tree (i.e. your build folder). |
CMAKE_CURRENT_LIST_FILE |
The current file being processed (includes *.cmake files, not just CMakeLists.txt ). |
CMAKE_CURRENT_LIST_DIR |
The directory of the file currently being processed. |
CMAKE_PARENT_LIST_FILE |
The file that loaded (with include() , add_subdirectory() , etc.) the one currently being processed. |
CMAKE_CURRENT_FUNCTION_LIST_FILE |
The file that defined (with function() ) the function currently being executed. |
CMAKE_CURRENT_FUNCTION_LIST_DIR |
The directory of that file. |
See the full list of built-in variables.
If you create new .h
or .cpp
code files then you need to tell CMake about them before they will be compiled. To do this, you need to add the name of your new code files to one of the CMake files (CMakeLists.txt
or *.cmake
) that you will find throughout the repository. Find an existing C++ file that's near to your new files (e.g. in the same directory or a parent directory) and check to see which CMake file the existing code file is mentioned in, then mention your new file in the same way.
You can get CMake to run external programs and scripts at any stage of the build.
Build stage | CMake command | Comment |
---|---|---|
configure |
• execute_process()
|
Runs every time the configure step is called. |
build |
• add_custom_target() • add_custom_command()
|
Prefer add_custom_command() because it only runs when its output files are outdated. add_custom_target() runs on every build , which can be useful if the command doesn't create any output files. |
install |
Use execute_process() within:• install(SCRIPT) • install(CODE)
|
Runs on every install . |
package |
Use execute_process() within:• CPACK_EXTERNAL_PACKAGE_SCRIPT • CPACK_INSTALL_COMMANDS • CPACK_*_SCRIPTS
|
Runs on every package . Use CPACK_EXTERNAL_PACKAGE_SCRIPT to build packages that CPack can't generate by itself (e.g. AppImages - see example PR). Use the other options to modify packages that CPack can build by itself (e.g. MSI and DMG installers). |
Testing
- Manual testing
- Automatic testing
Translation
Compilation
- Set up developer environment
- Install Qt and Qt Creator
- Get MuseScore's source code
- Install dependencies
- Compile on the command line
- Compile in Qt Creator
Beyond compiling
Misc. development
Architecture general
- Architecture overview
- AppShell
- Modularity
- Interact workflow
- Channels and Notifications
- Settings and Configuration
- Error handling
- Launcher and Interactive
- Keyboard Navigation
Audio
Engraving
- Style settings
- Working with style files
- Style parameter changes for 4.0
- Style parameter changes for 4.1
- Style parameter changes for 4.2
- Style parameter changes for 4.3
- Style parameter changes for 4.4
Extensions
- Extensions overview
- Manifest
- Forms
- Macros
- Api
- Legacy plugin API
Google Summer of Code
References