cmake_minimum_required(VERSION 3.18) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11 CACHE STRING "macOS deployment target") list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "In-source builds are not allowed. Please specify a" " build directory when running CMake" " (`${CMAKE_COMMAND} -S ${CMAKE_SOURCE_DIR} -B `).\n" "You should also delete these files, which have already been generated:\n" "* ${CMAKE_BINARY_DIR}/CMakeCache.txt\n" "* ${CMAKE_BINARY_DIR}/CMakeFiles") endif() # These flags are nonsense, Drawpile only uses libraries for internal assembly, # we don't want to install any into the system. It may also be possible to add # STATIC to every add_library call, but not sure if that doesn't break on some # platform, so just purge these flags to not let them mess with our build. foreach(garbage_flag IN ITEMS BUILD_SHARED_LIBS BUILD_STATIC_LIBS) if(DEFINED "${garbage_flag}") message(WARNING "Ignoring nonsensical '${garbage_flag}' argument") unset("${garbage_flag}") unset("${garbage_flag}" CACHE) endif() endforeach() # This must come before `project` because the version it pulls from an external # file is used as the CMake project version include(DrawpileVersions) project(Drawpile VERSION ${PROJECT_VERSION} HOMEPAGE_URL https://drawpile.net LANGUAGES C CXX ) include(DrawpileCheckSymlinks) # This must come after `project` because cross-compilation is configured by the # `project` call if(APPLE) enable_language(OBJCXX) endif() # CMake includes warning and exception handling flags into the MSVC command line # by default, which in turn causes MSVC to whinge about us overriding them with # every file that's compiled. We just delete those garbage flags out of the # default command line, that's apparently the "normal" solution. if(MSVC) message(STATUS "CMAKE_C_FLAGS before cleanup: ${CMAKE_C_FLAGS}") message(STATUS "CMAKE_CXX_FLAGS before cleanup: ${CMAKE_CXX_FLAGS}") message(STATUS "CMAKE_OBJCXX_FLAGS before cleanup: ${CMAKE_OBJCXX_FLAGS}") string(REGEX REPLACE "/EH[cs-]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REGEX REPLACE "/EH[cs-]+" "" CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS}") string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REGEX REPLACE "/W[0-4]" "" CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS}") message(STATUS "CMAKE_C_FLAGS before cleanup: ${CMAKE_C_FLAGS}") message(STATUS "CMAKE_CXX_FLAGS after cleanup: ${CMAKE_CXX_FLAGS}") message(STATUS "CMAKE_OBJCXX_FLAGS after cleanup: ${CMAKE_OBJCXX_FLAGS}") endif() # DrawpileOptions uses these so it must be included before then include(Cargo) include(FeatureSummary) # This must come after `project` because it relies on variables like `ANDROID` # that are only set once that is called include(DrawpileOptions) # This cannot go in `DrawpileVersions.cmake` because it relies on stuff from # `DrawpileOptions` if(CLIENT OR SERVERGUI) set(DP_MIN_QT_VERSION 5.12) else() # The minimum for headless servers is different than GUI because the current # Debian LTS provides only this older version of Qt, but it is too painful # to not have at least 5.12 when doing GUI stuff set(DP_MIN_QT_VERSION 5.11) endif() # This check must happen after `project()` because `CMAKE_CONFIGURATION_TYPES` # is not populated until then if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(FATAL_ERROR "Required build type missing." " Re-run CMake and specify one of these build types:\n" "* -DCMAKE_BUILD_TYPE=Debug\n" "* -DCMAKE_BUILD_TYPE=Release\n" "* -DCMAKE_BUILD_TYPE=RelWithDebInfo\n" "* -DCMAKE_BUILD_TYPE=MinSizeRel") endif() if(NOT CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) message(WARNING "Using ${PROJECT_NAME} as a subproject is untested.") endif() # CMake does not allow labels on the version in the project command, but having # the split version number is useful for other places, so just pretend like it # does semver and maybe it will someday set(PROJECT_VERSION "${PROJECT_VERSION}${PROJECT_VERSION_LABEL}") if(BUILD_LABEL) set(PROJECT_VERSION "${PROJECT_VERSION}+${BUILD_LABEL}") endif() # All of this information should be known by this point message(STATUS "Project version: ${PROJECT_VERSION}") message(STATUS "Protocol version: ${DRAWPILE_PROTO_SERVER_VERSION}.${DRAWPILE_PROTO_MAJOR_VERSION}.${DRAWPILE_PROTO_MINOR_VERSION}") if(ANDROID) if(BUILD_ANDROID_VERSION_CODE STREQUAL "") calculate_android_version_code(DRAWPILE_ANDROID_VERSION_CODE "${PROJECT_VERSION}" "${ANDROID_ABI}") if(DRAWPILE_ANDROID_VERSION_CODE LESS_EQUAL 0) message(FATAL_ERROR "Unable to determine version code from '${PROJECT_VERSION}', specify -DBUILD_VERSION_CODE explicitly") endif() else() set(DRAWPILE_ANDROID_VERSION_CODE "${BUILD_ANDROID_VERSION_CODE}") endif() message(STATUS "Android version code: ${DRAWPILE_ANDROID_VERSION_CODE}") if(ANDROID AND QT_VERSION VERSION_GREATER_EQUAL 6) set(QT_ANDROID_VERSION_NAME "${PROJECT_VERSION}") set(QT_ANDROID_VERSION_CODE "${DRAWPILE_ANDROID_VERSION_CODE}") endif() if (ANDROID_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "x86_64") message(STATUS "Enabling 16K page alignment for Android ABI ${ANDROID_ABI}") add_link_options(-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384) else() message(STATUS "Not enabling 16K page alignment for Android ${ANDROID_ABI}") endif() endif() # https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html if(QT_VERSION) find_package(QT ${DP_MIN_QT_VERSION} NAMES "Qt${QT_VERSION}" REQUIRED) else() find_package(QT ${DP_MIN_QT_VERSION} NAMES Qt6 Qt5 REQUIRED) endif() message(STATUS "Using Qt version ${QT_VERSION_MAJOR}") set(QT_PACKAGE_NAME Qt${QT_VERSION_MAJOR}) # Qt5AndroidSupport.cmake clobbers this variable, so make a copy set(DP_QT_DIR ${QT_DIR}) # There is an inherent conflict where the docs say `CMAKE_OSX_DEPLOYMENT_TARGET` # needs to be set before calling `enable_language` or `project`, but it is # impossible to run `find_package` before calling one of those and we need to # do that to learn which Qt version we are using since that defines what the # minimum deployment target is. So, we default it to the absolute minimum at the # start of the file, and then increase it to whatever Qt requires here include(QtMacDeploymentTarget) set_mac_deployment_target(${QT_VERSION}) include(AutoSourceGroup) include(DrawpileInstallDirs) # This *must* be included before `find_package(Qt5 COMPONENTS Core)` because # `find_package` loads `Qt5AndroidSupport.cmake` which immediately creates the # `apk` target and writes out `android_deployment_settings.json` using global # variables. if(ANDROID AND QT_VERSION VERSION_LESS 6) include(Qt5AndroidDeploymentTarget) endif() if(CLIENT OR SERVER) # Finding Qt needs to come first, otherwise automoc gets confused about # being unable to generate a MOC for the cmake-config directory below. find_package(${QT_PACKAGE_NAME} REQUIRED COMPONENTS Core Network) # This alters how some Android properties are handled in ways that don't # matter to us, so we just set this policy so that we don't get a warning. if(ANDROID AND QT_VERSION VERSION_GREATER_EQUAL 6.6.0) qt_policy(SET QTP0002 NEW) endif() endif() # This must be included *after* drawdance is added to make sure that we do not # pollute that sub-project with our compiler options. Once CMake 3.25 is the # minimum required version and Qt5 for Android is not used any more, it is # possible that the SYSTEM flag of `add_subdirectory` may do this and then this # can be moved down the list to where the rest of the dependencies are. include(GetIgnoreWarningsInDirectory) include(DrawpileCompilerOptions) include(Signing) if(TESTS) enable_testing() include(Tests) endif() # All intra-project includes should be fully qualified relative to the root # source directory to not go insane figuring out what is included from where include_directories(${CMAKE_CURRENT_LIST_DIR}/src) # Error messages shouldn't include home directories and such, so we use the # project directory length to chop it off the beginning of __FILE__. string(LENGTH "${PROJECT_SOURCE_DIR}/" project_dir_length) add_compile_definitions("DP_PROJECT_DIR_LENGTH=${project_dir_length}") add_subdirectory(src/cmake-config) if(CLIENT OR SERVER) # Everything needs these dependencies so just do it in one place find_package(${QT_PACKAGE_NAME} QUIET COMPONENTS LinguistTools) find_package(libsodium QUIET) if(EMSCRIPTEN) find_package(${QT_PACKAGE_NAME} REQUIRED COMPONENTS WebSockets) else() find_package(${QT_PACKAGE_NAME} QUIET COMPONENTS WebSockets) endif() if(ANDROID AND QT_VERSION VERSION_LESS 6) find_package(${QT_PACKAGE_NAME} REQUIRED COMPONENTS AndroidExtras) endif() if(TARGET libsodium::libsodium) add_compile_definitions(HAVE_LIBSODIUM) endif() if(HAVE_TCPSOCKETS) add_compile_definitions(HAVE_TCPSOCKETS) endif() if(TARGET "${QT_PACKAGE_NAME}::WebSockets") add_compile_definitions(HAVE_WEBSOCKETS) endif() # LinguistTools does not generate a target so its existence must be checked # using the old-style FOUND flag add_feature_info("Translations" ${QT_PACKAGE_NAME}LinguistTools_FOUND "") add_feature_info("Ext-auth support" "TARGET libsodium::libsodium" "") add_feature_info("WebSocket support" "TARGET ${QT_PACKAGE_NAME}::WebSockets" "") message(STATUS "Adding drawdance") add_subdirectory(src/drawdance) message(STATUS "Adding libshared") add_subdirectory(src/libshared) endif() if(CLIENT) message(STATUS "Adding libclient") add_subdirectory(src/libclient) message(STATUS "Adding desktop") add_subdirectory(src/desktop) endif() if(SERVER OR BUILTINSERVER) message(STATUS "Adding libserver") add_subdirectory(src/libserver) endif() if(SERVER) message(STATUS "Adding thinsrv") add_subdirectory(src/thinsrv) endif() if(TOOLS) message(STATUS "Adding tools") add_subdirectory(src/tools) endif() if(CARGO_COMMAND) add_custom_target(clippy COMMAND "${CARGO_COMMAND}" clippy -- # This lint isn't feasible in such a C-heavy codebase, since they # would just end up marking every function in the universe unsafe, # meaning we wouldn't get any of Rust's guarantees anywhere. -A clippy::not_unsafe_ptr_arg_deref # There's no useful safety docs that we could apply on a function level. -A clippy::missing_safety_doc # Too many positional arguments to a function isn't nice, but making a # struct to emulate named arguments is even worse. -A clippy::too_many_arguments # Turn on some of these restrictions for more consistency. -W clippy::empty_structs_with_brackets -W clippy::shadow_reuse -W clippy::shadow_same -W clippy::string_to_string -W clippy::try_err -W clippy::unseparated_literal_suffix -W clippy::verbose_file_reads # Turn on pedantic warnings, but disable the silly ones. -W clippy::pedantic # False positives when the string has already been lowercased. -A clippy::case_sensitive_file_extension_comparisons # If you didn't want these, you wouldn't be using `as` to cast. -A clippy::cast_possible_truncation -A clippy::cast_possible_wrap -A clippy::cast_precision_loss -A clippy::cast_sign_loss # We don't ever want to panic in a valid case, so nothing to document. -A clippy::missing_panics_doc # We do this only on extern "C" types, where it's necessary to do so. -A clippy::module_name_repetitions # Nothing needless about explicitly consuming a value. -A clippy::needless_pass_by_value # Single characters are fine, we use them for w and h or r, g, b and a. -A clippy::many_single_char_names # We don't have exciting error handling, so nothing to document. -A clippy::missing_errors_doc # False positives and not terribly useful a lint to begin with. -A clippy::must_use_candidate # Similar names are better than inconsistent names. -A clippy::similar_names # Artificially splitting up functions doesn't make them easier to read. -A clippy::too_many_lines # Putting the format arguments after the string often looks nicer. -A clippy::uninlined_format_args # False positives on argb literals that are perfectly readable. -A clippy::unreadable_literal ) endif() # This must run once all target creation is finished since it walks the list of # targets and their sources. This could be called any time and use # `cmake_language(DEFER)` once CMake 3.19 is the minimum supported version. disable_automoc_warnings() # Since Android is cross-compiled it is already always creating a distributable # build if(DIST_BUILD AND NOT ANDROID) include(DrawpileDistBuild) endif() include(DrawpilePackaging) if(ANDROID) if(QT_VERSION_MAJOR GREATER 5) add_feature_info("Qt Android minimum SDK version (QT_ANDROID_MIN_SDK_VERSION)" ON "${QT_ANDROID_MIN_SDK_VERSION}") add_feature_info("Qt Android compile SDK version (QT_ANDROID_COMPILE_SDK_VERSION)" ON "${QT_ANDROID_COMPILE_SDK_VERSION}") add_feature_info("Qt Android target SDK version (QT_ANDROID_TARGET_SDK_VERSION)" ON "${QT_ANDROID_TARGET_SDK_VERSION}") else() add_feature_info("Android minimum SDK version (ANDROID_MIN_SDK_VERSION)" ON ${ANDROID_MIN_SDK_VERSION}) add_feature_info("Android target SDK version (ANDROID_TARGET_SDK_VERSION)" ON ${ANDROID_TARGET_SDK_VERSION}) endif() endif() feature_summary(WHAT PACKAGES_NOT_FOUND ENABLED_FEATURES DISABLED_FEATURES) if(CMAKE_CONFIGURATION_TYPES) message(NOTICE "++ For a debug build, run: `${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}" " --config Debug`\n" "++ For a release build, run: `${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}" " --config Release`") else() message(NOTICE "++ To build, run: `${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}`") endif()