Skip to content

CMake and the build system

Peter Jonas edited this page Feb 2, 2024 · 1 revision

Summary

What is CMake?

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.

Build tools supported by MuseScore

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.

Building a CMake project

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.

Additional steps

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.

Out-of-source builds

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).

CMake files

The CMakeList.txt and *.cmake files in the repository contain instructions that CMake uses to generate a build system for the native tool.

CMakeLists.txt

You'll find a file called CMakeLists.txt at the root of MuseScore's repository and in most subdirectories.

Source tree

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

Build tree / binary tree

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()).

CMake modules and *.cmake files

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.

build.cmake

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 variables

Built-in variables

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.

Common tasks

Adding new C++ files to the build system

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.

Running external commands

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

Translation

Compilation

  1. Set up developer environment
  2. Install Qt and Qt Creator
  3. Get MuseScore's source code
  4. Install dependencies
  5. Compile on the command line
  6. Compile in Qt Creator

Beyond compiling

  1. Find your way around the code
  2. Submit a Pull Request
  3. Fix the CI checks

Misc. development

Architecture general

Audio

Engraving

Extensions

Google Summer of Code

References

Clone this wiki locally