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
cd build
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
mv bin/DuckStation.app .
codesign -s - --deep -f -v DuckStation.app

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

@ -16,7 +16,13 @@ if(LINUX OR BSD)
option(ENABLE_WAYLAND "Support Wayland window system" ON)
endif()
if(APPLE)
option(BUILD_MACOS_BUNDLE "Build MacOS application bundles" 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()
# Set _DEBUG macro for Debug builds.

@ -314,17 +314,28 @@ if(APPLE)
)
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(
OUTPUT ${metallib_file}
COMMAND xcrun metallib -o ${metallib_file} ${air_files}
OUTPUT ${metallib_path}
COMMAND xcrun metallib -o ${metallib_path} ${air_files}
DEPENDS ${air_files}
COMMENT "Linking Metal library ${library_name}.metallib"
COMMENT "Linking Metal library ${metallib_filename}"
)
target_sources(${target} PRIVATE ${metallib_file})
set_source_files_properties(${metallib_file} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
target_sources(${target} PRIVATE ${metallib_path})
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()
endif()
@ -344,7 +355,7 @@ function(add_resources TARGET DEST_SUBDIR SOURCE_DIR)
# Get the subdirectory portion (if any)
get_filename_component(REL_SUBDIR "${REL_PATH}" DIRECTORY)
if(APPLE)
if(BUILD_MACOS_BUNDLE)
# On macOS, add as source with MACOSX_PACKAGE_LOCATION
target_sources(${TARGET} PRIVATE "${SOURCE_FILE}")
if(REL_SUBDIR)
@ -391,7 +402,7 @@ function(add_runtime_libraries TARGET)
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.
message(STATUS "Bundling imported library ${dyn_lib_soname}")
target_sources(${TARGET} PRIVATE "${dyn_lib_path}")

@ -47,6 +47,8 @@
#ifdef _WIN32
#include "common/windows_headers.h"
#include <ShlObj.h>
#elifdef __APPLE__
#include "common/cocoa_tools.h"
#endif
LOG_CHANNEL(Core);
@ -56,7 +58,7 @@ namespace Core {
/// Use two async worker threads, should be enough for most tasks.
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 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
bool Core::SetCriticalFolders(const char* resources_subdir, Error* error)
bool Core::SetCriticalFolders(Error* error)
{
if (!SetAppRootAndResources(resources_subdir, error))
if (!SetAppRootAndResources(error))
return false;
if (!SetDataRoot(error))
@ -101,7 +103,7 @@ bool Core::SetCriticalFolders(const char* resources_subdir, Error* error)
return true;
}
bool Core::SetAppRootAndResources(const char* resources_subdir, Error* error)
bool Core::SetAppRootAndResources(Error* error)
{
const std::string program_path = FileSystem::GetProgramPath(error);
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));
// MacOS resources are inside the app bundle, so canonicalize them.
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, resources_subdir);
#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
if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str()))

@ -10,7 +10,7 @@ class Error;
namespace Core {
/// 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.
/// We split this out so it can be retrieved by the host for error message purposes,

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

@ -80,6 +80,7 @@
#include "common/windows_headers.h"
#include <objbase.h> // CoInitializeEx
#elif defined(__APPLE__)
#include "common/cocoa_tools.h"
#include <unistd.h>
#endif
@ -847,16 +848,7 @@ void QtHost::DownloadFile(QWidget* parent, std::string url, std::string path,
bool QtHost::InitializeFoldersAndConfig(Error* error)
{
// Path to the resources directory relative to the application binary.
// 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))
if (!Core::SetCriticalFolders(error))
return false;
Error config_error;
@ -2503,7 +2495,11 @@ void QtHost::UpdateApplicationLanguage(QWidget* dialog_parent)
#ifndef __APPLE__
const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath());
#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
// Qt base uses underscores instead of hyphens.

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

@ -24,7 +24,7 @@ if(APPLE)
find_library(COCOA_LIBRARY Cocoa REQUIRED)
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
MACOSX_BUNDLE true

@ -268,27 +268,29 @@ function(add_util_resources target)
get_property(UTIL_METAL_SOURCES GLOBAL PROPERTY UTIL_METAL_SOURCES)
add_metal_sources(${target} ${UTIL_METAL_SOURCES} metal_shaders macos-metal2.3)
# Copy MoltenVK into the bundle
unset(MOLTENVK_PATH CACHE)
find_file(MOLTENVK_PATH NAMES
libMoltenVK.dylib
lib/libMoltenVK.dylib
)
if (MOLTENVK_PATH)
target_sources(${target} PRIVATE "${MOLTENVK_PATH}")
set_source_files_properties("${MOLTENVK_PATH}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks)
message(STATUS "Using MoltenVK from ${MOLTENVK_PATH}")
else()
message(WARNING "MoltenVK not found in path, it will depend on the target system having it.")
endif()
if(BUILD_MACOS_BUNDLE)
# Copy MoltenVK into the bundle
unset(MOLTENVK_PATH CACHE)
find_file(MOLTENVK_PATH NAMES
libMoltenVK.dylib
lib/libMoltenVK.dylib
)
if (MOLTENVK_PATH)
target_sources(${target} PRIVATE "${MOLTENVK_PATH}")
set_source_files_properties("${MOLTENVK_PATH}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks)
message(STATUS "Using MoltenVK from ${MOLTENVK_PATH}")
else()
message(WARNING "MoltenVK not found in path, it will depend on the target system having it.")
endif()
# Copy ffmpeg into the bundle.
foreach(component avcodec avformat avutil swresample swscale)
string(REGEX REPLACE "\([0-9]+\)\.[0-9]+\.[0-9]+" "\\1" major "${FFMPEG_${component}_VERSION}")
string(REPLACE "lib${component}.dylib" "lib${component}.${major}.dylib" version_lib "${FFMPEG_${component}_LIBRARIES}")
target_sources(${target} PRIVATE ${version_lib})
set_source_files_properties(${target} PRIVATE ${version_lib} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks)
endforeach()
# Copy ffmpeg into the bundle.
foreach(component avcodec avformat avutil swresample swscale)
string(REGEX REPLACE "\([0-9]+\)\.[0-9]+\.[0-9]+" "\\1" major "${FFMPEG_${component}_VERSION}")
string(REPLACE "lib${component}.dylib" "lib${component}.${major}.dylib" version_lib "${FFMPEG_${component}_LIBRARIES}")
target_sources(${target} PRIVATE ${version_lib})
set_source_files_properties(${target} PRIVATE ${version_lib} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks)
endforeach()
endif()
endif()
# Copy dynamically-loaded libraries into the bundle.

Loading…
Cancel
Save