mirror of
https://github.com/stenzek/duckstation.git
synced 2026-10-11 22:49:58 +00:00
CMake: Make bundle building on MacOS optional
Also makes Xcode build without needing to copy/symlink resources.
This commit is contained in:
@@ -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}")
|
||||
|
||||
+14
-7
@@ -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
|
||||
)
|
||||
|
||||
if(CMAKE_GENERATOR MATCHES "Xcode")
|
||||
# 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(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
|
||||
|
||||
+22
-20
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user