#[=======================================================================[ This CMake script contains functions to build an Android APK. It is (currently) limited to packaging binaries for a single architecture. #]=======================================================================] cmake_minimum_required(VERSION 3.7) if(NOT PROJECT_NAME MATCHES "^SDL.*") message(WARNING "This module is internal to SDL and is currently not supported.") endif() function(_sdl_create_outdir_for_target OUTDIRECTORY TARGET) set(outdir "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TARGET}.dir") # Some CMake versions have a slow `cmake -E make_directory` implementation if(NOT IS_DIRECTORY "${outdir}") execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}") endif() set("${OUTDIRECTORY}" "${outdir}" PARENT_SCOPE) endfunction() function(sdl_create_android_debug_keystore TARGET) set(output "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_debug.keystore") add_custom_command(OUTPUT ${output} COMMAND ${CMAKE_COMMAND} -E rm -f "${output}" COMMAND SdlAndroid::keytool -genkey -keystore "${output}" -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "C=US, O=Android, CN=Android Debug" ) add_custom_target(${TARGET} DEPENDS "${output}") set_property(TARGET ${TARGET} PROPERTY OUTPUT "${output}") endfunction() function(sdl_android_compile_resources TARGET) cmake_parse_arguments(arg "" "RESFOLDER" "RESOURCES" ${ARGN}) if(NOT arg_RESFOLDER AND NOT arg_RESOURCES) message(FATAL_ERROR "Missing RESFOLDER or RESOURCES argument (need one or both)") endif() _sdl_create_outdir_for_target(outdir "${TARGET}") set(out_files "") set(res_files "") if(arg_RESFOLDER) get_filename_component(arg_RESFOLDER "${arg_RESFOLDER}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") file(GLOB_RECURSE res_folder_files "${arg_RESFOLDER}/*") list(APPEND res_files ${res_folder_files}) foreach(res_file IN LISTS res_files) file(RELATIVE_PATH rel_res_file "${arg_RESFOLDER}" "${res_file}") string(REPLACE "/" "_" rel_comp_path "${rel_res_file}") if(res_file MATCHES ".*res/values.*\\.xml$") string(REGEX REPLACE "\\.xml" ".arsc" rel_comp_path "${rel_comp_path}") endif() set(comp_path "${outdir}/${rel_comp_path}.flat") add_custom_command( OUTPUT "${comp_path}" COMMAND SdlAndroid::aapt2 compile -o "${outdir}" "${res_file}" DEPENDS ${res_file} ) list(APPEND out_files "${comp_path}") endforeach() endif() if(arg_RESOURCES) list(APPEND res_files ${arg_RESOURCES}) foreach(res_file IN LISTS arg_RESOURCES) string(REGEX REPLACE ".*/res/" "" rel_res_file ${res_file}) string(REPLACE "/" "_" rel_comp_path "${rel_res_file}") if(res_file MATCHES ".*res/values.*\\.xml$") string(REGEX REPLACE "\\.xml" ".arsc" rel_comp_path "${rel_comp_path}") endif() set(comp_path "${outdir}/${rel_comp_path}.flat") add_custom_command( OUTPUT "${comp_path}" COMMAND SdlAndroid::aapt2 compile -o "${outdir}" "${res_file}" DEPENDS ${res_file} ) list(APPEND out_files "${comp_path}") endforeach() endif() add_custom_target(${TARGET} DEPENDS ${out_files}) set_property(TARGET "${TARGET}" PROPERTY OUTPUTS "${out_files}") set_property(TARGET "${TARGET}" PROPERTY SOURCES "${res_files}") endfunction() function(sdl_android_link_resources TARGET) cmake_parse_arguments(arg "NO_DEBUG" "MIN_SDK_VERSION;TARGET_SDK_VERSION;ANDROID_JAR;OUTPUT_APK;MANIFEST;PACKAGE" "RES_TARGETS" ${ARGN}) if(arg_MANIFEST) get_filename_component(arg_MANIFEST "${arg_MANIFEST}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") else() message(FATAL_ERROR "sdl_add_android_link_resources_target requires a Android MANIFEST path (${arg_MANIFEST})") endif() if(NOT arg_PACKAGE) file(READ "${arg_MANIFEST}" manifest_contents) string(REGEX MATCH "package=\"([a-zA-Z0-9_.]+)\"" package_match "${manifest_contents}") if(NOT package_match) message(FATAL_ERROR "Could not extract package from Android manifest (${arg_MANIFEST})") endif() set(arg_PACKAGE "${CMAKE_MATCH_1}") endif() set(depends "") _sdl_create_outdir_for_target(outdir "${TARGET}") string(REPLACE "." "/" java_r_path "${arg_PACKAGE}") get_filename_component(java_r_path "${java_r_path}" ABSOLUTE BASE_DIR "${outdir}") set(java_r_path "${java_r_path}/R.java") set(command SdlAndroid::aapt2 link) if(NOT arg_NO_DEBUG) list(APPEND command --debug-mode) endif() if(arg_MIN_SDK_VERSION) list(APPEND command --min-sdk-version ${arg_MIN_SDK_VERSION}) endif() if(arg_TARGET_SDK_VERSION) list(APPEND command --target-sdk-version ${arg_TARGET_SDK_VERSION}) endif() if(arg_ANDROID_JAR) list(APPEND command -I "${arg_ANDROID_JAR}") else() list(APPEND command -I "${SDL_ANDROID_PLATFORM_ANDROID_JAR}") endif() if(NOT arg_OUTPUT_APK) set(arg_OUTPUT_APK "${TARGET}.apk") endif() get_filename_component(arg_OUTPUT_APK "${arg_OUTPUT_APK}" ABSOLUTE BASE_DIR "${outdir}") list(APPEND command -o "${arg_OUTPUT_APK}") list(APPEND command --java "${outdir}") list(APPEND command --manifest "${arg_MANIFEST}") foreach(res_target IN LISTS arg_RES_TARGETS) list(APPEND command $) list(APPEND depends $) endforeach() add_custom_command( OUTPUT "${arg_OUTPUT_APK}" "${java_r_path}" COMMAND ${command} DEPENDS ${depends} ${arg_MANIFEST} COMMAND_EXPAND_LISTS VERBATIM ) add_custom_target(${TARGET} DEPENDS "${arg_OUTPUT_APK}" "${java_r_path}") set_property(TARGET ${TARGET} PROPERTY OUTPUT "${arg_OUTPUT_APK}") set_property(TARGET ${TARGET} PROPERTY JAVA_R "${java_r_path}") set_property(TARGET ${TARGET} PROPERTY OUTPUTS "${${arg_OUTPUT_APK}};${java_r_path}") endfunction() function(sdl_add_to_apk_unaligned TARGET) cmake_parse_arguments(arg "" "APK_IN;NAME;OUTDIR" "ASSETS;NATIVE_LIBS;DEX" ${ARGN}) if(NOT arg_APK_IN) message(FATAL_ERROR "Missing APK_IN argument") endif() if(NOT TARGET ${arg_APK_IN}) message(FATAL_ERROR "APK_IN (${arg_APK_IN}) must be a target providing an apk") endif() _sdl_create_outdir_for_target(workdir ${TARGET}) if(NOT arg_OUTDIR) set(arg_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}") endif() if(NOT arg_NAME) string(REGEX REPLACE "[:-]+" "." arg_NAME "${TARGET}") if(NOT arg_NAME MATCHES "\\.apk") set(arg_NAME "${arg_NAME}.apk") endif() endif() get_filename_component(apk_file "${arg_NAME}" ABSOLUTE BASE_DIR "${arg_OUTDIR}") set(apk_libdir "lib/${ANDROID_ABI}") set(depends "") set(commands COMMAND "${CMAKE_COMMAND}" -E remove_directory -rf "${apk_libdir}" "assets" COMMAND "${CMAKE_COMMAND}" -E make_directory "${apk_libdir}" "assets" COMMAND "${CMAKE_COMMAND}" -E copy "$" "${apk_file}" ) set(dex_i "1") foreach(dex IN LISTS arg_DEX) set(suffix "${dex_i}") if(suffix STREQUAL "1") set(suffix "") endif() list(APPEND commands COMMAND "${CMAKE_COMMAND}" -E copy "$" "classes${suffix}.dex" COMMAND SdlAndroid::zip -u -q -j "${apk_file}" "classes${suffix}.dex" ) math(EXPR dex_i "${dex_i}+1") list(APPEND depends "$") endforeach() foreach(native_lib IN LISTS arg_NATIVE_LIBS) list(APPEND commands COMMAND "${CMAKE_COMMAND}" -E copy $ "${apk_libdir}/$" COMMAND SdlAndroid::zip -u -q "${apk_file}" "${apk_libdir}/$" ) endforeach() if(arg_ASSETS) list(APPEND commands COMMAND "${CMAKE_COMMAND}" -E copy ${arg_ASSETS} "assets" COMMAND SdlAndroid::zip -u -r -q "${apk_file}" "assets" ) endif() add_custom_command(OUTPUT "${apk_file}" ${commands} DEPENDS ${arg_NATIVE_LIBS} ${depends} "$" WORKING_DIRECTORY "${workdir}" ) add_custom_target(${TARGET} DEPENDS "${apk_file}") set_property(TARGET ${TARGET} PROPERTY OUTPUT "${apk_file}") endfunction() function(sdl_apk_align TARGET APK_IN) cmake_parse_arguments(arg "" "NAME;OUTDIR" "" ${ARGN}) if(NOT TARGET ${arg_APK_IN}) message(FATAL_ERROR "APK_IN (${arg_APK_IN}) must be a target providing an apk") endif() if(NOT arg_OUTDIR) set(arg_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}") endif() if(NOT arg_NAME) string(REGEX REPLACE "[:-]+" "." arg_NAME "${TARGET}") if(NOT arg_NAME MATCHES "\\.apk") set(arg_NAME "${arg_NAME}.apk") endif() endif() get_filename_component(apk_file "${arg_NAME}" ABSOLUTE BASE_DIR "${arg_OUTDIR}") add_custom_command(OUTPUT "${apk_file}" COMMAND SdlAndroid::zipalign -f 4 "$" "${apk_file}" DEPENDS "$" ) add_custom_target(${TARGET} DEPENDS "${apk_file}") set_property(TARGET ${TARGET} PROPERTY OUTPUT "${apk_file}") endfunction() function(sdl_apk_sign TARGET APK_IN) cmake_parse_arguments(arg "" "OUTPUT;KEYSTORE" "" ${ARGN}) if(NOT TARGET ${arg_APK_IN}) message(FATAL_ERROR "APK_IN (${arg_APK_IN}) must be a target providing an apk") endif() if(NOT TARGET ${arg_KEYSTORE}) message(FATAL_ERROR "APK_KEYSTORE (${APK_KEYSTORE}) must be a target providing a keystore") endif() if(NOT arg_OUTPUT) string(REGEX REPLACE "[:-]+" "." arg_OUTPUT "${TARGET}") if(NOT arg_OUTPUT MATCHES "\\.apk") set(arg_OUTPUT "${arg_OUTPUT}.apk") endif() endif() get_filename_component(apk_file "${arg_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") add_custom_command(OUTPUT "${apk_file}" COMMAND SdlAndroid::apksigner sign --ks "$" --ks-pass pass:android --in "$" --out "${apk_file}" DEPENDS "$" "$" BYPRODUCTS "${apk_file}.idsig" ) add_custom_target(${TARGET} DEPENDS "${apk_file}") set_property(TARGET ${TARGET} PROPERTY OUTPUT "${apk_file}") endfunction()