305 lines
12 KiB
CMake
Executable file
305 lines
12 KiB
CMake
Executable file
#!/usr/bin/env -S cmake -P
|
|
|
|
# This cross-platform script automates the process of creating a build directory and
|
|
# compiling MuseScore. Despite being written in the CMake language, you should think
|
|
# of this file as like a shell script or batch file rather than as part of the CMake
|
|
# project tree. As always, the project tree starts in the top-level CMakeLists.txt.
|
|
|
|
string(TIMESTAMP SCRIPT_START_TIMESTAMP "%s" UTC)
|
|
|
|
if(NOT DEFINED CMAKE_SCRIPT_MODE_FILE)
|
|
file(RELATIVE_PATH SCRIPT_PATH "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_LIST_FILE}")
|
|
message(FATAL_ERROR
|
|
"This file is a script. You should run it with:\n"
|
|
" \$ cmake -P ${SCRIPT_PATH} [args...]\n"
|
|
"Don't try to use it in other CMake files with include().\n"
|
|
"See https://cmake.org/cmake/help/latest/manual/cmake.1.html#run-a-script"
|
|
)
|
|
endif()
|
|
|
|
cmake_minimum_required(VERSION 3.16) # should match version in CMakeLists.txt
|
|
|
|
# CMake arguments up to '-P' (ignore these)
|
|
set(i "1")
|
|
while(i LESS "${CMAKE_ARGC}")
|
|
if("${CMAKE_ARGV${i}}" STREQUAL "-P")
|
|
math(EXPR i "${i} + 1") # ignore '-P' option
|
|
break() # done with CMake arguments
|
|
endif()
|
|
math(EXPR i "${i} + 1") # next argument
|
|
endwhile()
|
|
|
|
# Script arguments (store these for later processing)
|
|
# By convention, SCRIPT_ARG[0] is the name of the script
|
|
set(SCRIPT_ARGS "") # empty argument list
|
|
while(i LESS "${CMAKE_ARGC}")
|
|
list(APPEND SCRIPT_ARGS "${CMAKE_ARGV${i}}")
|
|
math(EXPR i "${i} + 1") # next argument
|
|
endwhile()
|
|
|
|
# load custom CMake functions and macros
|
|
include("${CMAKE_CURRENT_LIST_DIR}/build/cmake/GetUtilsFunctions.cmake") # "fn__" namespace
|
|
|
|
# Set the name of the build folder (just the folder name, not the full path)
|
|
function(build_folder
|
|
VAR_NAME # Name of variable to store build folder name
|
|
)
|
|
# Windows has a 260 character limit on file paths, so the build folder
|
|
# name must be abbreviated to prevent build files exceeding this limit.
|
|
# The limit applies to the *entire* path, not just individual components.
|
|
# Abbreviation is not essential on other platforms but is still welcome.
|
|
if(WIN32)
|
|
set(PLATFORM "Win")
|
|
elseif(APPLE)
|
|
set(PLATFORM "Mac")
|
|
else()
|
|
set(PLATFORM "${CMAKE_HOST_SYSTEM_NAME}")
|
|
endif()
|
|
set(GEN_SHORT "${GENERATOR}")
|
|
string(REGEX REPLACE ".*Visual Studio ([0-9]+).*" "VS\\1" GEN_SHORT "${GEN_SHORT}")
|
|
string(REGEX REPLACE ".*MinGW.*" "MinGW" GEN_SHORT "${GEN_SHORT}")
|
|
string(REGEX REPLACE ".*Makefile.*" "Make" GEN_SHORT "${GEN_SHORT}")
|
|
set(BUILD_FOLDER "${PLATFORM}-Qt${QT_VERSION}-${QT_COMPILER}-${GEN_SHORT}-${BUILD_TYPE}")
|
|
string(REGEX REPLACE "[ /\\]" "" BUILD_FOLDER "${BUILD_FOLDER}") # remove spaces and slashes
|
|
set("${VAR_NAME}" "${BUILD_FOLDER}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
# Default values for independent build variables
|
|
set(SOURCE_PATH "${CMAKE_CURRENT_LIST_DIR}") # MuseScore repo (directory of this script)
|
|
set(ALL_BUILDS_PATH "${SOURCE_PATH}/builds") # Where all build folders will be created
|
|
|
|
# CPUS
|
|
cmake_host_system_information(RESULT CPUS QUERY NUMBER_OF_LOGICAL_CORES)
|
|
if(NOT "${CPUS}" GREATER "0")
|
|
include(ProcessorCount)
|
|
ProcessorCount(CPUS)
|
|
endif()
|
|
|
|
# Overrides
|
|
# Use this file to replace build variable defaults with your own values.
|
|
# E.g. set your own GENERATOR, BUILD_TYPE, BUILD_FOLDER, or CPUS.
|
|
if(EXISTS "${SOURCE_PATH}/build_overrides.cmake")
|
|
message(STATUS "Including personal build overrides")
|
|
include("${SOURCE_PATH}/build_overrides.cmake")
|
|
endif()
|
|
|
|
# QTDIR environment variable is set automatically by Qt Creator. It can also
|
|
# be set manually in build_overrides.cmake for use with other IDEs.
|
|
# Typical value for ENV{QTDIR}: /path/to/Qt/<version>/<compiler>
|
|
if(DEFINED ENV{QTDIR})
|
|
# Add selected build kit to PATH.
|
|
if(WIN32)
|
|
set(ENV{PATH} "$ENV{QTDIR}/bin;$ENV{PATH}") # semicolon
|
|
else()
|
|
set(ENV{PATH} "$ENV{QTDIR}/bin:$ENV{PATH}") # colon
|
|
endif()
|
|
endif()
|
|
|
|
fn__require_program(QMAKE Qt --version "https://musescore.org/en/handbook/developers-handbook/compilation" qmake)
|
|
fn__set_qt_variables("${QMAKE}")
|
|
message(STATUS "QT_LOCATION: ${QT_LOCATION}")
|
|
message(STATUS "QT_VERSION: ${QT_VERSION}")
|
|
message(STATUS "QT_COMPILER: ${QT_COMPILER}")
|
|
|
|
# Process script arguments
|
|
set(i "1")
|
|
list(LENGTH SCRIPT_ARGS nargs)
|
|
while(i LESS "${nargs}")
|
|
list(GET SCRIPT_ARGS "${i}" ARG)
|
|
if("${ARG}" STREQUAL "clean")
|
|
set(ARG_CLEAN "TRUE")
|
|
elseif("${ARG}" STREQUAL "configure")
|
|
set(ARG_CONFIGURE "TRUE")
|
|
elseif("${ARG}" STREQUAL "build")
|
|
set(ARG_CONFIGURE "TRUE")
|
|
set(ARG_BUILD "TRUE")
|
|
elseif("${ARG}" STREQUAL "install")
|
|
set(ARG_CONFIGURE "TRUE")
|
|
set(ARG_BUILD "TRUE")
|
|
set(ARG_INSTALL "TRUE")
|
|
elseif("${ARG}" STREQUAL "run")
|
|
set(ARG_RUN "TRUE")
|
|
else()
|
|
# Other arguments are used by certain subprocesses in build steps
|
|
if(ARG_RUN)
|
|
list(APPEND RUN_ARGS "${ARG}") # args after "run" belong to Run step
|
|
else()
|
|
list(APPEND CONFIGURE_ARGS "${ARG}") # all other args belong to Configure step
|
|
endif()
|
|
endif()
|
|
math(EXPR i "${i} + 1") # next argument
|
|
endwhile()
|
|
|
|
# Default if no build steps given as arguments
|
|
if(NOT (ARG_CLEAN OR ARG_CONFIGURE OR ARG_BUILD OR ARG_INSTALL OR ARG_RUN))
|
|
set(ARG_CONFIGURE "TRUE")
|
|
set(ARG_BUILD "TRUE")
|
|
set(ARG_INSTALL "TRUE")
|
|
endif()
|
|
|
|
# Default values for build variables that depend on other build variables.
|
|
# These will only be set here if they were not already defined elsewhere,
|
|
# e.g. by script arguments or in build_overrides.cmake.
|
|
|
|
fn__get_option(GENERATOR -G ${CONFIGURE_ARGS})
|
|
if(WIN32)
|
|
fn__set_default(GENERATOR "Visual Studio 16 2019")
|
|
else()
|
|
fn__set_default(GENERATOR "Unix Makefiles")
|
|
endif()
|
|
|
|
fn__get_option(BUILD_TYPE -DCMAKE_BUILD_TYPE ${CONFIGURE_ARGS})
|
|
fn__set_default(BUILD_TYPE "RelWithDebInfo")
|
|
|
|
if(NOT DEFINED BUILD_FOLDER)
|
|
build_folder(BUILD_FOLDER)
|
|
endif()
|
|
set(BUILD_PATH "${ALL_BUILDS_PATH}/${BUILD_FOLDER}")
|
|
|
|
fn__get_option(INSTALL_PATH -DCMAKE_INSTALL_PREFIX ${CONFIGURE_ARGS})
|
|
fn__set_default(INSTALL_PATH "install") # relative to BUILD_PATH
|
|
|
|
# MSCORE_EXECUTABLE (path relative to INSTALL_PATH)
|
|
if(WIN32)
|
|
fn__set_default(MSCORE_EXECUTABLE "bin/MuseScore4.exe")
|
|
elseif(APPLE)
|
|
fn__set_default(MSCORE_EXECUTABLE "mscore.app/Contents/MacOS/mscore")
|
|
else()
|
|
fn__set_default(MSCORE_EXECUTABLE "bin/mscore")
|
|
endif()
|
|
|
|
# make paths absolute if they are not already
|
|
get_filename_component(BUILD_PATH "${BUILD_PATH}" ABSOLUTE BASE_DIR "${SOURCE_PATH}")
|
|
get_filename_component(INSTALL_PATH "${INSTALL_PATH}" ABSOLUTE BASE_DIR "${BUILD_PATH}")
|
|
get_filename_component(MSCORE_EXECUTABLE "${MSCORE_EXECUTABLE}" ABSOLUTE BASE_DIR "${INSTALL_PATH}")
|
|
|
|
message(STATUS "SOURCE_PATH: ${SOURCE_PATH}")
|
|
message(STATUS "BUILD_PATH: ${BUILD_PATH}")
|
|
message(STATUS "INSTALL_PATH: ${INSTALL_PATH}")
|
|
message(STATUS "MSCORE_EXECUTABLE: ${MSCORE_EXECUTABLE}")
|
|
message(STATUS "CPUS: ${CPUS}")
|
|
message(STATUS "GENERATOR: ${GENERATOR}")
|
|
message(STATUS "BUILD_TYPE: ${BUILD_TYPE}")
|
|
|
|
list(APPEND CONFIGURE_ARGS "-G" "${GENERATOR}")
|
|
list(APPEND CONFIGURE_ARGS "-DCMAKE_INSTALL_PREFIX=${INSTALL_PATH}")
|
|
list(APPEND CONFIGURE_ARGS "-DCMAKE_BUILD_TYPE=${BUILD_TYPE}")
|
|
|
|
if(QT_COMPILER MATCHES "_64$")
|
|
list(APPEND CONFIGURE_ARGS "-DBUILD_64=ON")
|
|
else()
|
|
list(APPEND CONFIGURE_ARGS "-DBUILD_64=OFF")
|
|
endif()
|
|
|
|
#### ACTUAL BUILD STEPS START HERE ####
|
|
|
|
# Clean - delete an existing build directory.
|
|
#
|
|
# We usually avoid this because performing a clean build takes much longer
|
|
# than an incremental build, but it is occassionally necessary. If you
|
|
# encounter errors during a build then you should try doing a clean build.
|
|
|
|
if(ARG_CLEAN)
|
|
message("\n~~~~ Actualizing Clean step ~~~~\n")
|
|
message("Deleting ${BUILD_PATH}")
|
|
file(REMOVE_RECURSE "${BUILD_PATH}")
|
|
endif()
|
|
|
|
# Configure - generate build system for the native build tool.
|
|
#
|
|
# We only do this explicitly on the very first build. On subsequent builds
|
|
# the native build tool will redo the configuration if any CMake files were
|
|
# edited. You can force this to happen by deleting CMakeCache.txt.
|
|
|
|
if(ARG_CONFIGURE AND NOT EXISTS "${BUILD_PATH}/CMakeCache.txt")
|
|
message("\n~~~~ Actualizing Configure step ~~~~\n")
|
|
file(MAKE_DIRECTORY "${BUILD_PATH}")
|
|
if(QT_COMPILER MATCHES "msvc")
|
|
set(CMAKE_WRAPPER "${SOURCE_PATH}/build/cmake_wrapper.bat")
|
|
else()
|
|
set(CMAKE_WRAPPER "cmake")
|
|
endif()
|
|
fn__command_string(ARGS_STR ${CONFIGURE_ARGS})
|
|
message("CONFIGURE_ARGS: ${ARGS_STR}")
|
|
execute_process(
|
|
# List of CMake arguments in CONFIGURE_ARGS variable. It must be unquoted here.
|
|
COMMAND "${CMAKE_WRAPPER}" -S "${SOURCE_PATH}" -B . ${CONFIGURE_ARGS}
|
|
WORKING_DIRECTORY "${BUILD_PATH}"
|
|
RESULT_VARIABLE EXIT_STATUS
|
|
)
|
|
if(NOT "${EXIT_STATUS}" EQUAL "0")
|
|
file(REMOVE "${BUILD_PATH}/CMakeCache.txt") # need to configure again next time
|
|
message(FATAL_ERROR "Configure step failed with status ${EXIT_STATUS}. See output above for details.")
|
|
endif()
|
|
endif()
|
|
|
|
# Build - compile code with the native build tool.
|
|
#
|
|
# We always do this. We can rely on the native build tool to be efficient and
|
|
# only (re)compile source files that have been edited since the last build.
|
|
|
|
if(ARG_BUILD)
|
|
message("\n~~~~ Actualizing Build step ~~~~\n")
|
|
execute_process(
|
|
COMMAND cmake --build . --config "${BUILD_TYPE}" --parallel "${CPUS}"
|
|
WORKING_DIRECTORY "${BUILD_PATH}"
|
|
RESULT_VARIABLE EXIT_STATUS
|
|
)
|
|
if(NOT "${EXIT_STATUS}" EQUAL "0")
|
|
message(FATAL_ERROR "Build step failed with status ${EXIT_STATUS}. See output above for details.")
|
|
endif()
|
|
endif()
|
|
|
|
# Install - move compiled files to destination folders.
|
|
#
|
|
# Again, we always do this and rely on the tool itself to be efficient and
|
|
# only install files that have changed since last time.
|
|
|
|
if(ARG_INSTALL)
|
|
message("\n~~~~ Actualizing Install step ~~~~\n")
|
|
execute_process(
|
|
COMMAND cmake --install . --config "${BUILD_TYPE}"
|
|
WORKING_DIRECTORY "${BUILD_PATH}"
|
|
RESULT_VARIABLE EXIT_STATUS
|
|
)
|
|
if(NOT "${EXIT_STATUS}" EQUAL "0")
|
|
message(FATAL_ERROR "Install step failed with status ${EXIT_STATUS}. See output above for details.")
|
|
endif()
|
|
endif()
|
|
|
|
# Run - attempt to run the compiled program within the installation folder.
|
|
#
|
|
# The working directory is unchanged. Use build_override.cmake to set the
|
|
# CMake variable RUN_ARGS to contain a list of arguments to pass to MuseScore
|
|
# on the command line. In addition, script arguments after "run" will be
|
|
# appened to this list, but note that certain arguments cannot be passed this
|
|
# way (e.g. --help, --version) because they cancel CMake script processing.
|
|
|
|
if(ARG_RUN)
|
|
message("\n~~~~ Actualizing Run step ~~~~\n")
|
|
if(WIN32)
|
|
set(CMD "cmd.exe" "/c") # allow CMake to launch a GUI application
|
|
else()
|
|
set(CMD "") # not an issue on other platforms
|
|
endif()
|
|
fn__command_string(ARGS_STR ${RUN_ARGS})
|
|
message("RUN_ARGS: ${ARGS_STR}")
|
|
execute_process(
|
|
# List of MuseScore arguments in RUN_ARGS variable. It must be unquoted here.
|
|
COMMAND ${CMD} "${MSCORE_EXECUTABLE}" ${RUN_ARGS}
|
|
RESULT_VARIABLE EXIT_STATUS
|
|
)
|
|
if(NOT "${EXIT_STATUS}" EQUAL "0")
|
|
message(FATAL_ERROR "Run step failed with status ${EXIT_STATUS}. See output above for details.")
|
|
endif()
|
|
endif()
|
|
|
|
# Package - create installer archive for distribution to end users.
|
|
# TODO
|
|
|
|
string(TIMESTAMP SCRIPT_END_TIMESTAMP "%s" UTC)
|
|
math(EXPR SCRIPT_ELAPSED_TIME "${SCRIPT_END_TIMESTAMP} - ${SCRIPT_START_TIMESTAMP}")
|
|
math(EXPR SCRIPT_ELAPSED_MINS "${SCRIPT_ELAPSED_TIME} / 60")
|
|
math(EXPR SCRIPT_ELAPSED_SECS "${SCRIPT_ELAPSED_TIME} % 60")
|
|
list(GET SCRIPT_ARGS "0" SCRIPT_NAME)
|
|
message("\n${SCRIPT_NAME}: Complete after ${SCRIPT_ELAPSED_MINS} minutes and ${SCRIPT_ELAPSED_SECS} seconds")
|