CMake: Make bundle building on MacOS optional

Also makes Xcode build without needing to copy/symlink resources.
pull/3794/head
Stenzek 2 weeks ago
parent 17a721c8f7
commit 1f065b5eae
No known key found for this signature in database

@ -59,7 +59,7 @@ jobs:
mkdir build mkdir build
cd build cd build
export MACOSX_DEPLOYMENT_TARGET=13.3 export MACOSX_DEPLOYMENT_TARGET=13.3
cmake -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENGL=OFF -DCMAKE_PREFIX_PATH="$HOME/deps" -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -G Ninja .. cmake -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_BUILD_TYPE=Release -DBUILD_MACOS_BUNDLE=ON -DENABLE_OPENGL=OFF -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -G Ninja ..
cmake --build . --parallel cmake --build . --parallel
mv bin/DuckStation.app . mv bin/DuckStation.app .
codesign -s - --deep -f -v DuckStation.app codesign -s - --deep -f -v DuckStation.app

@ -46,7 +46,7 @@ function(copy_base_translations target)
endif() endif()
target_sources(${target} PRIVATE ${path}) target_sources(${target} PRIVATE ${path})
if(APPLE) if(BUILD_MACOS_BUNDLE)
set_source_files_properties(${path} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/translations) set_source_files_properties(${path} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/translations)
else() else()
add_custom_command(TARGET ${target} POST_BUILD add_custom_command(TARGET ${target} POST_BUILD

@ -16,7 +16,13 @@ if(LINUX OR BSD)
option(ENABLE_WAYLAND "Support Wayland window system" ON) option(ENABLE_WAYLAND "Support Wayland window system" ON)
endif() endif()
if(APPLE) if(APPLE)
option(BUILD_MACOS_BUNDLE "Build MacOS application bundles" OFF)
option(SKIP_POSTPROCESS_BUNDLE "Disable bundle post-processing, including Qt additions" OFF) option(SKIP_POSTPROCESS_BUNDLE "Disable bundle post-processing, including Qt additions" OFF)
# Cannot have a Xcode and bundle build for now due to code signing.
if(CMAKE_GENERATOR MATCHES "Xcode" AND BUILD_MACOS_BUNDLE)
message(FATAL_ERROR "Cannot build bundles when using the Xcode generator")
endif()
endif() endif()
# Set _DEBUG macro for Debug builds. # Set _DEBUG macro for Debug builds.

@ -314,17 +314,28 @@ if(APPLE)
) )
endforeach() endforeach()
set(metallib_file ${CMAKE_CURRENT_BINARY_DIR}/${library_name}.metallib) set(metallib_filename "${library_name}.metallib")
set(metallib_path "${CMAKE_CURRENT_BINARY_DIR}/${metallib_filename}")
add_custom_command( add_custom_command(
OUTPUT ${metallib_file} OUTPUT ${metallib_path}
COMMAND xcrun metallib -o ${metallib_file} ${air_files} COMMAND xcrun metallib -o ${metallib_path} ${air_files}
DEPENDS ${air_files} DEPENDS ${air_files}
COMMENT "Linking Metal library ${library_name}.metallib" COMMENT "Linking Metal library ${metallib_filename}"
) )
target_sources(${target} PRIVATE ${metallib_file}) target_sources(${target} PRIVATE ${metallib_path})
set_source_files_properties(${metallib_file} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) if(BUILD_MACOS_BUNDLE)
set_source_files_properties(${metallib_path} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
else()
set(RESOURCES_DIRECTORY "$<TARGET_FILE_DIR:${target}>/resources")
add_custom_command(TARGET duckstation-qt POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory "${RESOURCES_DIRECTORY}"
)
add_custom_command(TARGET duckstation-qt POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${metallib_path}" "${RESOURCES_DIRECTORY}/${metallib_filename}"
)
endif()
endfunction() endfunction()
endif() endif()
@ -344,7 +355,7 @@ function(add_resources TARGET DEST_SUBDIR SOURCE_DIR)
# Get the subdirectory portion (if any) # Get the subdirectory portion (if any)
get_filename_component(REL_SUBDIR "${REL_PATH}" DIRECTORY) get_filename_component(REL_SUBDIR "${REL_PATH}" DIRECTORY)
if(APPLE) if(BUILD_MACOS_BUNDLE)
# On macOS, add as source with MACOSX_PACKAGE_LOCATION # On macOS, add as source with MACOSX_PACKAGE_LOCATION
target_sources(${TARGET} PRIVATE "${SOURCE_FILE}") target_sources(${TARGET} PRIVATE "${SOURCE_FILE}")
if(REL_SUBDIR) if(REL_SUBDIR)
@ -391,7 +402,7 @@ function(add_runtime_libraries TARGET)
get_filename_component(dyn_lib_dir "${dyn_lib_path}" DIRECTORY) get_filename_component(dyn_lib_dir "${dyn_lib_path}" DIRECTORY)
if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode" AND NOT SKIP_POSTPROCESS_BUNDLE) if(BUILD_MACOS_BUNDLE)
# For normal macOS bundle generators, put the dylib into Contents/Frameworks. # For normal macOS bundle generators, put the dylib into Contents/Frameworks.
message(STATUS "Bundling imported library ${dyn_lib_soname}") message(STATUS "Bundling imported library ${dyn_lib_soname}")
target_sources(${TARGET} PRIVATE "${dyn_lib_path}") target_sources(${TARGET} PRIVATE "${dyn_lib_path}")

@ -47,6 +47,8 @@
#ifdef _WIN32 #ifdef _WIN32
#include "common/windows_headers.h" #include "common/windows_headers.h"
#include <ShlObj.h> #include <ShlObj.h>
#elifdef __APPLE__
#include "common/cocoa_tools.h"
#endif #endif
LOG_CHANNEL(Core); LOG_CHANNEL(Core);
@ -56,7 +58,7 @@ namespace Core {
/// Use two async worker threads, should be enough for most tasks. /// Use two async worker threads, should be enough for most tasks.
static constexpr u32 NUM_ASYNC_WORKER_THREADS = 2; static constexpr u32 NUM_ASYNC_WORKER_THREADS = 2;
static bool SetAppRootAndResources(const char* resources_subdir, Error* error); static bool SetAppRootAndResources(Error* error);
static bool SetDataRoot(Error* error); static bool SetDataRoot(Error* error);
static void SetDefaultSettings(SettingsInterface& si, bool host, bool system, bool controller, bool ignore_user_prefs); static void SetDefaultSettings(SettingsInterface& si, bool host, bool system, bool controller, bool ignore_user_prefs);
@ -82,9 +84,9 @@ ALIGN_TO_CACHE_LINE static CoreLocals s_locals;
} // namespace Core } // namespace Core
bool Core::SetCriticalFolders(const char* resources_subdir, Error* error) bool Core::SetCriticalFolders(Error* error)
{ {
if (!SetAppRootAndResources(resources_subdir, error)) if (!SetAppRootAndResources(error))
return false; return false;
if (!SetDataRoot(error)) if (!SetDataRoot(error))
@ -101,7 +103,7 @@ bool Core::SetCriticalFolders(const char* resources_subdir, Error* error)
return true; return true;
} }
bool Core::SetAppRootAndResources(const char* resources_subdir, Error* error) bool Core::SetAppRootAndResources(Error* error)
{ {
const std::string program_path = FileSystem::GetProgramPath(error); const std::string program_path = FileSystem::GetProgramPath(error);
if (program_path.empty()) if (program_path.empty())
@ -111,10 +113,15 @@ bool Core::SetAppRootAndResources(const char* resources_subdir, Error* error)
EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path));
// MacOS resources are inside the app bundle, so canonicalize them.
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, resources_subdir);
#ifdef __APPLE__ #ifdef __APPLE__
EmuFolders::Resources = Path::Canonicalize(EmuFolders::Resources); // MacOS resources are inside the app bundle. We might not be running in a bundle.
const std::optional<std::string> bundle_path = CocoaTools::GetBundlePath();
if (bundle_path.has_value())
EmuFolders::Resources = Path::Combine(bundle_path.value(), "Contents/Resources");
else
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources");
#else
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources");
#endif #endif
if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str()))

@ -10,7 +10,7 @@ class Error;
namespace Core { namespace Core {
/// Based on the current configuration, determines what the data directory is. /// Based on the current configuration, determines what the data directory is.
bool SetCriticalFolders(const char* resources_subdir, Error* error); bool SetCriticalFolders(Error* error);
/// Returns the path to the configuration file. /// Returns the path to the configuration file.
/// We split this out so it can be retrieved by the host for error message purposes, /// We split this out so it can be retrieved by the host for error message purposes,

@ -248,6 +248,7 @@ if(APPLE)
target_link_libraries(duckstation-qt PRIVATE ${COREGRAPHICS_LIBRARY}) target_link_libraries(duckstation-qt PRIVATE ${COREGRAPHICS_LIBRARY})
# Don't generate a bundle for XCode, it makes code signing fail... # Don't generate a bundle for XCode, it makes code signing fail...
if(NOT CMAKE_GENERATOR MATCHES "Xcode" AND BUILD_MACOS_BUNDLE)
get_scm_version() get_scm_version()
set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DuckStation.app) set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DuckStation.app)
set_target_properties(duckstation-qt PROPERTIES set_target_properties(duckstation-qt PROPERTIES
@ -256,13 +257,11 @@ if(APPLE)
OUTPUT_NAME DuckStation OUTPUT_NAME DuckStation
) )
if(CMAKE_GENERATOR MATCHES "Xcode") # Copy icon into the bundle
set_target_properties(duckstation-qt PROPERTIES target_sources(duckstation-qt PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns")
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO if(NOT SKIP_POSTPROCESS_BUNDLE)
)
elseif(NOT SKIP_POSTPROCESS_BUNDLE)
# Inject Qt Libraries into bundle. # Inject Qt Libraries into bundle.
find_program(MACDEPLOYQT_EXE macdeployqt HINTS "${QT_BINARY_DIRECTORY}") find_program(MACDEPLOYQT_EXE macdeployqt HINTS "${QT_BINARY_DIRECTORY}")
add_custom_target(duckstation-postprocess-bundle ALL add_custom_target(duckstation-postprocess-bundle ALL
@ -270,21 +269,25 @@ if(APPLE)
) )
add_dependencies(duckstation-postprocess-bundle duckstation-qt) add_dependencies(duckstation-postprocess-bundle duckstation-qt)
endif() endif()
elseif(CMAKE_GENERATOR MATCHES "Xcode")
# Copy icon into the bundle set_target_properties(duckstation-qt PROPERTIES
target_sources(duckstation-qt PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns") XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources) XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO
)
endif()
endif() endif()
# Compile qrc to a binary file. # Compile qrc to a binary file.
if(NOT APPLE) if(NOT BUILD_MACOS_BUNDLE)
set(RCC_FILE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/resources/duckstation-qt.rcc") set(RCC_FILENAME "duckstation-qt.rcc")
qt_add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc DESTINATION ${RCC_FILE} OPTIONS -no-compress) set(RCC_BUILD_PATH "${CMAKE_CURRENT_BINARY_DIR}/${RCC_FILENAME}")
set(RCC_OUTPUT_DIR "$<TARGET_FILE_DIR:duckstation-qt>/resources")
qt_add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc DESTINATION ${RCC_BUILD_PATH} OPTIONS -no-compress)
add_dependencies(duckstation-qt duckstation-qt-rcc) add_dependencies(duckstation-qt duckstation-qt-rcc)
# Need to ensure the resources directory exists, it might not. Happens when low CPU count and parallel builds. add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "${RCC_OUTPUT_DIR}")
add_custom_target(duckstation-qt-rcc-mkdir COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/resources") add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${RCC_BUILD_PATH}" "${RCC_OUTPUT_DIR}/${RCC_FILENAME}")
add_dependencies(duckstation-qt-rcc duckstation-qt-rcc-mkdir)
else() else()
set(RCC_FILE "${CMAKE_CURRENT_BINARY_DIR}/duckstation-qt.rcc") set(RCC_FILE "${CMAKE_CURRENT_BINARY_DIR}/duckstation-qt.rcc")
qt_add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc DESTINATION ${RCC_FILE} OPTIONS -no-compress) qt_add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc DESTINATION ${RCC_FILE} OPTIONS -no-compress)
@ -296,7 +299,7 @@ endif()
# Translation setup. # Translation setup.
qt_add_lrelease(duckstation-qt TS_FILES ${TS_FILES} QM_FILES_OUTPUT_VARIABLE QM_FILES) qt_add_lrelease(duckstation-qt TS_FILES ${TS_FILES} QM_FILES_OUTPUT_VARIABLE QM_FILES)
if(NOT APPLE) if(NOT BUILD_MACOS_BUNDLE)
set(QM_OUTPUT_DIR "$<TARGET_FILE_DIR:duckstation-qt>/translations") set(QM_OUTPUT_DIR "$<TARGET_FILE_DIR:duckstation-qt>/translations")
add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "${QM_OUTPUT_DIR}") add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "${QM_OUTPUT_DIR}")
foreach (QM_FILE IN LISTS QM_FILES) foreach (QM_FILE IN LISTS QM_FILES)

@ -80,6 +80,7 @@
#include "common/windows_headers.h" #include "common/windows_headers.h"
#include <objbase.h> // CoInitializeEx #include <objbase.h> // CoInitializeEx
#elif defined(__APPLE__) #elif defined(__APPLE__)
#include "common/cocoa_tools.h"
#include <unistd.h> #include <unistd.h>
#endif #endif
@ -847,16 +848,7 @@ void QtHost::DownloadFile(QWidget* parent, std::string url, std::string path,
bool QtHost::InitializeFoldersAndConfig(Error* error) bool QtHost::InitializeFoldersAndConfig(Error* error)
{ {
// Path to the resources directory relative to the application binary. if (!Core::SetCriticalFolders(error))
// On Windows/Linux, these are in the binary directory.
// On macOS, this is in the bundle resources directory.
#ifndef __APPLE__
static constexpr const char* RESOURCES_RELATIVE_PATH = "resources";
#else
static constexpr const char* RESOURCES_RELATIVE_PATH = "../Resources";
#endif
if (!Core::SetCriticalFolders(RESOURCES_RELATIVE_PATH, error))
return false; return false;
Error config_error; Error config_error;
@ -2503,7 +2495,11 @@ void QtHost::UpdateApplicationLanguage(QWidget* dialog_parent)
#ifndef __APPLE__ #ifndef __APPLE__
const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath()); const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath());
#else #else
const QString base_dir = QStringLiteral("%1/../Resources/translations").arg(qApp->applicationDirPath()); QString base_dir;
if (const std::optional<std::string> bundle_path = CocoaTools::GetBundlePath(); bundle_path.has_value())
base_dir = QString::fromStdString(Path::Combine(bundle_path.value(), "Contents/Resources/translations"));
else
base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath());
#endif #endif
// Qt base uses underscores instead of hyphens. // Qt base uses underscores instead of hyphens.

@ -82,7 +82,7 @@ static std::string s_dump_base_directory;
bool RegTestHost::InitializeFoldersAndConfig(Error* error) bool RegTestHost::InitializeFoldersAndConfig(Error* error)
{ {
if (!Core::SetCriticalFolders("resources", error)) if (!Core::SetCriticalFolders(error))
return false; return false;
if (!Core::InitializeBaseSettingsLayer({}, error)) if (!Core::InitializeBaseSettingsLayer({}, error))

@ -24,7 +24,7 @@ if(APPLE)
find_library(COCOA_LIBRARY Cocoa REQUIRED) find_library(COCOA_LIBRARY Cocoa REQUIRED)
target_link_libraries(updater PRIVATE ${COCOA_LIBRARY}) target_link_libraries(updater PRIVATE ${COCOA_LIBRARY})
if(NOT CMAKE_GENERATOR MATCHES "Xcode" AND NOT SKIP_POSTPROCESS_BUNDLE) if(BUILD_MACOS_BUNDLE)
set_target_properties(updater PROPERTIES OUTPUT_NAME "Updater") set_target_properties(updater PROPERTIES OUTPUT_NAME "Updater")
set_target_properties(updater PROPERTIES set_target_properties(updater PROPERTIES
MACOSX_BUNDLE true MACOSX_BUNDLE true

@ -268,6 +268,7 @@ function(add_util_resources target)
get_property(UTIL_METAL_SOURCES GLOBAL PROPERTY UTIL_METAL_SOURCES) get_property(UTIL_METAL_SOURCES GLOBAL PROPERTY UTIL_METAL_SOURCES)
add_metal_sources(${target} ${UTIL_METAL_SOURCES} metal_shaders macos-metal2.3) add_metal_sources(${target} ${UTIL_METAL_SOURCES} metal_shaders macos-metal2.3)
if(BUILD_MACOS_BUNDLE)
# Copy MoltenVK into the bundle # Copy MoltenVK into the bundle
unset(MOLTENVK_PATH CACHE) unset(MOLTENVK_PATH CACHE)
find_file(MOLTENVK_PATH NAMES find_file(MOLTENVK_PATH NAMES
@ -290,6 +291,7 @@ function(add_util_resources target)
set_source_files_properties(${target} PRIVATE ${version_lib} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks) set_source_files_properties(${target} PRIVATE ${version_lib} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks)
endforeach() endforeach()
endif() endif()
endif()
# Copy dynamically-loaded libraries into the bundle. # Copy dynamically-loaded libraries into the bundle.
add_runtime_libraries(${target} spirv-cross-c-shared Shaderc::shaderc_shared SQLite3::sqlite3-shared SDL3::SDL3 add_runtime_libraries(${target} spirv-cross-c-shared Shaderc::shaderc_shared SQLite3::sqlite3-shared SDL3::SDL3

Loading…
Cancel
Save