mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-09 04:30:49 -04:00
5c9c76cc0b
Co-authored-by: qwertyquerty <qwertytrogi@gmail.com> Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
216 lines
9.3 KiB
CMake
216 lines
9.3 KiB
CMake
# add_mod(<target> SOURCES <file>... MOD_JSON <mod.json> [RES_DIR <res>] [OUTPUT_DIR <dir>] [BUNDLE])
|
|
set(DUSK_MODS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/mods" CACHE PATH "Directory to write mod packages into")
|
|
|
|
function(_mod_lib_name out_var)
|
|
set(_arch "${CMAKE_SYSTEM_PROCESSOR}")
|
|
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
|
|
list(LENGTH CMAKE_OSX_ARCHITECTURES _count)
|
|
if (_count GREATER 1)
|
|
message(FATAL_ERROR "add_mod: universal binaries are not supported")
|
|
endif ()
|
|
set(_arch "${CMAKE_OSX_ARCHITECTURES}")
|
|
endif ()
|
|
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _platform)
|
|
string(TOLOWER "${_arch}" _arch)
|
|
if (_arch MATCHES "^(i[3-6]86|x86)$")
|
|
set(_arch "x86")
|
|
endif ()
|
|
if (WIN32)
|
|
set(_ext ".dll")
|
|
elseif (APPLE)
|
|
set(_ext ".dylib")
|
|
else ()
|
|
set(_ext ".so")
|
|
endif ()
|
|
set(${out_var} "${_platform}-${_arch}${_ext}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
function(_mod_resolve_source_path out_var path)
|
|
if (IS_ABSOLUTE "${path}")
|
|
set(_path "${path}")
|
|
else ()
|
|
set(_path "${CMAKE_CURRENT_SOURCE_DIR}/${path}")
|
|
endif ()
|
|
set(${out_var} "${_path}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
function(_mod_collect_assets out_var dir)
|
|
if (NOT IS_DIRECTORY "${dir}")
|
|
message(FATAL_ERROR "add_mod: asset directory does not exist: ${dir}")
|
|
endif ()
|
|
|
|
file(GLOB_RECURSE _files CONFIGURE_DEPENDS LIST_DIRECTORIES false "${dir}/*")
|
|
set(${out_var} ${_files} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
function(add_mod target_name)
|
|
cmake_parse_arguments(ARG "BUNDLE" "MOD_JSON;RES_DIR;OUTPUT_DIR" "SOURCES" ${ARGN})
|
|
if (NOT ARG_MOD_JSON)
|
|
message(FATAL_ERROR "add_mod: MOD_JSON is required")
|
|
endif ()
|
|
_mod_resolve_source_path(_mod_json "${ARG_MOD_JSON}")
|
|
if (NOT EXISTS "${_mod_json}")
|
|
message(FATAL_ERROR "add_mod: MOD_JSON does not exist: ${_mod_json}")
|
|
endif ()
|
|
|
|
add_library(${target_name} SHARED ${ARG_SOURCES})
|
|
_mod_lib_name(_lib_name)
|
|
set_target_properties(${target_name} PROPERTIES
|
|
PREFIX ""
|
|
C_VISIBILITY_PRESET hidden
|
|
CXX_VISIBILITY_PRESET hidden
|
|
VISIBILITY_INLINES_HIDDEN ON
|
|
WINDOWS_EXPORT_ALL_SYMBOLS OFF)
|
|
target_compile_features(${target_name} PRIVATE cxx_std_20)
|
|
target_link_libraries(${target_name} PRIVATE dusklight_game_headers)
|
|
|
|
if (NOT TARGET dusklight)
|
|
# Apply global compile options for out-of-tree mod builds
|
|
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
|
target_compile_options(${target_name} PRIVATE
|
|
-Wno-multichar -Wno-trigraphs -Wno-deprecated-declarations)
|
|
elseif (APPLE)
|
|
target_compile_options(${target_name} PRIVATE
|
|
-Wno-declaration-after-statement -Wno-non-pod-varargs)
|
|
elseif (MSVC)
|
|
target_compile_options(${target_name} PRIVATE
|
|
"$<$<COMPILE_LANGUAGE:C,CXX>:/bigobj>"
|
|
"$<$<COMPILE_LANGUAGE:C,CXX>:/utf-8>")
|
|
endif ()
|
|
# Use signed char on ARM to match the original game (and x86)
|
|
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _mod_arch)
|
|
if (_mod_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
|
|
target_compile_options(${target_name} PRIVATE -fsigned-char)
|
|
endif ()
|
|
endif ()
|
|
|
|
set(_output_dir "${DUSK_MODS_OUTPUT_DIR}")
|
|
if (ARG_OUTPUT_DIR)
|
|
set(_output_dir "${ARG_OUTPUT_DIR}")
|
|
endif ()
|
|
set(_stage "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_stage")
|
|
set(_out "${_output_dir}/${target_name}.dusk")
|
|
|
|
set(_zip_args "${_lib_name}" mod.json)
|
|
set(_package_deps "${_mod_json}")
|
|
set(_package_inputs "${_mod_json}")
|
|
set(_extra_cmds "")
|
|
if (ARG_RES_DIR)
|
|
_mod_resolve_source_path(_res_dir "${ARG_RES_DIR}")
|
|
_mod_collect_assets(_res_deps "${_res_dir}")
|
|
list(APPEND _package_deps ${_res_deps})
|
|
list(APPEND _package_inputs "${_res_dir}" ${_res_deps})
|
|
list(APPEND _zip_args res)
|
|
list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
|
|
"${_res_dir}" "${_stage}/res")
|
|
endif ()
|
|
|
|
set(_bundle_cmds "")
|
|
if (ARG_BUNDLE AND TARGET dusklight)
|
|
file(READ "${_mod_json}" _mod_json_text)
|
|
string(JSON _mod_id GET "${_mod_json_text}" id)
|
|
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_TARGETS "${target_name}")
|
|
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_IDS "${_mod_id}")
|
|
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_STAGES "${_stage}")
|
|
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_PACKAGES "${_out}")
|
|
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_LIB_NAMES "${_lib_name}")
|
|
set(_bundle_cmds
|
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundled_mods"
|
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_out}" "${CMAKE_BINARY_DIR}/bundled_mods/${target_name}.dusk")
|
|
endif ()
|
|
|
|
set(_package_target "${target_name}_package")
|
|
set(_package_inputs_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_package_inputs.txt")
|
|
list(SORT _package_inputs)
|
|
set(_package_inputs_text "")
|
|
foreach (_package_input IN LISTS _package_inputs)
|
|
string(APPEND _package_inputs_text "${_package_input}\n")
|
|
endforeach ()
|
|
file(GENERATE OUTPUT "${_package_inputs_file}" CONTENT "${_package_inputs_text}")
|
|
add_custom_command(OUTPUT "${_out}"
|
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_stage}"
|
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${_stage}" "${_output_dir}"
|
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:${target_name}>" "${_stage}/${_lib_name}"
|
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_mod_json}" "${_stage}/mod.json"
|
|
${_extra_cmds}
|
|
COMMAND ${CMAKE_COMMAND} -E chdir "${_stage}" ${CMAKE_COMMAND} -E tar cvf "${_out}" --format=zip ${_zip_args}
|
|
${_bundle_cmds}
|
|
DEPENDS ${target_name} ${_package_deps} "${_package_inputs_file}"
|
|
COMMENT "Packaging ${target_name} -> ${_out}"
|
|
COMMAND_EXPAND_LISTS
|
|
VERBATIM
|
|
)
|
|
add_custom_target(${_package_target} ALL DEPENDS "${_out}")
|
|
if (TARGET dusklight_mods)
|
|
add_dependencies(dusklight_mods ${_package_target})
|
|
endif ()
|
|
endfunction()
|
|
|
|
# Install rules for BUNDLE mods.
|
|
# - Windows: the .dusk archives into <install>/mods (the loader extracts native libs to the
|
|
# user cache).
|
|
# - Linux: pre-extracted stage dirs into <install>/mods so native libs dlopen in place from
|
|
# read-only installs.
|
|
# - macOS: pre-extracted stage dirs into the installed app's Contents/Resources/mods, dylibs
|
|
# ad-hoc signed in place, then the whole bundle re-signed.
|
|
# - iOS/tvOS: assets into <app>/mods/<id> and the dylib into Frameworks/<id>.dylib.
|
|
# - Android: nothing here; gradle packs ${CMAKE_BINARY_DIR}/bundled_mods into APK assets.
|
|
function(install_bundled_mods)
|
|
get_property(_targets GLOBAL PROPERTY DUSK_BUNDLED_MOD_TARGETS)
|
|
if (NOT _targets OR ANDROID)
|
|
return ()
|
|
endif ()
|
|
get_property(_ids GLOBAL PROPERTY DUSK_BUNDLED_MOD_IDS)
|
|
get_property(_stages GLOBAL PROPERTY DUSK_BUNDLED_MOD_STAGES)
|
|
get_property(_lib_names GLOBAL PROPERTY DUSK_BUNDLED_MOD_LIB_NAMES)
|
|
list(LENGTH _targets _count)
|
|
math(EXPR _last "${_count} - 1")
|
|
|
|
if (APPLE)
|
|
get_target_property(_app_name dusklight OUTPUT_NAME)
|
|
if (NOT _app_name)
|
|
set(_app_name dusklight)
|
|
endif ()
|
|
set(_bundle_dir "${CMAKE_INSTALL_PREFIX}/${_app_name}.app")
|
|
if (IOS OR TVOS)
|
|
foreach (_i RANGE ${_last})
|
|
list(GET _targets ${_i} _target)
|
|
list(GET _ids ${_i} _id)
|
|
list(GET _stages ${_i} _stage)
|
|
list(GET _lib_names ${_i} _lib_name)
|
|
install(DIRECTORY "${_stage}/" DESTINATION "${_bundle_dir}/mods/${_id}"
|
|
PATTERN "${_lib_name}" EXCLUDE)
|
|
install(PROGRAMS "$<TARGET_FILE:${_target}>"
|
|
DESTINATION "${_bundle_dir}/Frameworks" RENAME "${_id}.dylib")
|
|
endforeach ()
|
|
else ()
|
|
foreach (_i RANGE ${_last})
|
|
list(GET _ids ${_i} _id)
|
|
list(GET _stages ${_i} _stage)
|
|
list(GET _lib_names ${_i} _lib_name)
|
|
install(DIRECTORY "${_stage}/" DESTINATION "${_bundle_dir}/Contents/Resources/mods/${_id}")
|
|
install(CODE "execute_process(COMMAND /usr/bin/codesign --force --sign - \"${_bundle_dir}/Contents/Resources/mods/${_id}/${_lib_name}\" COMMAND_ERROR_IS_FATAL ANY)")
|
|
endforeach ()
|
|
install(CODE "execute_process(COMMAND /usr/bin/codesign --force --sign - --entitlements \"${DUSK_ENTITLEMENTS}\" \"${_bundle_dir}\" COMMAND_ERROR_IS_FATAL ANY)")
|
|
endif ()
|
|
return ()
|
|
endif ()
|
|
|
|
if (DUSK_PACKAGE_INSTALL)
|
|
set(_mods_dest "${CMAKE_INSTALL_DATAROOTDIR}/dusklight/mods")
|
|
else ()
|
|
set(_mods_dest "${CMAKE_INSTALL_PREFIX}/mods")
|
|
endif ()
|
|
if (WIN32)
|
|
foreach (_target IN LISTS _targets)
|
|
install(FILES "${CMAKE_BINARY_DIR}/bundled_mods/${_target}.dusk" DESTINATION "${_mods_dest}")
|
|
endforeach ()
|
|
else ()
|
|
foreach (_i RANGE ${_last})
|
|
list(GET _ids ${_i} _id)
|
|
list(GET _stages ${_i} _stage)
|
|
install(DIRECTORY "${_stage}/" DESTINATION "${_mods_dest}/${_id}")
|
|
endforeach ()
|
|
endif ()
|
|
endfunction()
|