[✔️]cocos creator 构建原生插件原理

509 阅读1分钟

在构建的时候会执行cmake命令

3.6.3/resources/tools/cmake/bin/cmake.exe 
    -S"E:/proj/cocos/NewProject/native/engine/win64" 
    -B"E:/proj/cocos/NewProject/build/windows/proj" 
    -DRES_DIR="E:/proj/cocos/NewProject/build/windows" 
    -DAPP_NAME="NewProject" 

如果你对cmake比较熟悉,就会看明白命令行的选项都什么意思,可以参考这篇文章CMake命令行使用姿势

一切的起点

  • native\engine\win64\CMakeLists.txt
cmake_minimum_required(VERSION 3.8)

set(APP_NAME "NewProject" CACHE STRING "Project Name")
project(${APP_NAME} CXX)
set(CC_PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR})
set(CC_UI_RESOURCES)
set(CC_PROJ_SOURCES)
set(CC_COMMON_SOURCES)
set(CC_ALL_SOURCES)
include(${CC_PROJECT_DIR}/../common/CMakeLists.txt) # 引入外部的CMake
set(EXECUTABLE_NAME ${APP_NAME})

# 重点关注的逻辑在这里
cc_windows_before_target(${EXECUTABLE_NAME})
add_executable(${EXECUTABLE_NAME}
    ${CC_ALL_SOURCES}
)
cc_windows_after_target(${EXECUTABLE_NAME})
  • native\engine\common\CMakeLists.txt
# .. 省略了很多set变量逻辑
if(NOT RES_DIR) # 对应外部设置的RES_DIR变量
    message(FATAL_ERROR "RES_DIR is not set!")
endif()

# 这个文件包含了大量的set,没有任何的逻辑
include(${RES_DIR}/proj/cfg.cmake)

# localCfg.cmake 是空逻辑
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/localCfg.cmake)
    include(${CMAKE_CURRENT_LIST_DIR}/localCfg.cmake)
endif()
# 这个变量定义在cfg.cmake,指向的是3.6.3/resources/resources/3d/engine/native
if(NOT COCOS_X_PATH)
    message(FATAL_ERROR "COCOS_X_PATH is not set!")
endif()

# engine相关的逻辑
include(${COCOS_X_PATH}/CMakeLists.txt)

# 入口源代码
list(APPEND CC_COMMON_SOURCES
    ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.h
    ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.cpp
)

从log可以看到,中间有执行:

node.exe plugin_parser.js

那剩下的问题就是cc_windows_before_target在哪里?在engine/common里面有加载engine相关的逻辑,我们看下engine大概处理了哪些

  • ${COCOS_X_PATH}/CMakeLists.txt

第一行就有

include(${CMAKE_CURRENT_LIST_DIR}/cmake/predefine.cmake)

在predefine.cmake中有

## predefined configurations for game applications
include(${CMAKE_CURRENT_LIST_DIR}/../../templates/cmake/common.cmake)
if(APPLE)
    include(${CMAKE_CURRENT_LIST_DIR}/../../templates/cmake/apple.cmake)
elseif(WINDOWS)
    include(${CMAKE_CURRENT_LIST_DIR}/../../templates/cmake/windows.cmake)
elseif(LINUX)
    include(${CMAKE_CURRENT_LIST_DIR}/../../templates/cmake/linux.cmake)
elseif(ANDROID)
    include(${CMAKE_CURRENT_LIST_DIR}/../../templates/cmake/android.cmake)
elseif(OHOS)
    include(${CMAKE_CURRENT_LIST_DIR}/../../templates/cmake/ohos.cmake)
elseif(QNX)
elseif(EMSCRIPTEN)
else()
    message(FATAL_ERROR "Unhandled platform specified cmake utils!")
endif()

所以逻辑就又分发到了templates里面,其实里面的逻辑也就刚好只有这2个marco,而这2个marco也就刚好就是我们要找的marco

macro(cc_windows_before_target target_name)
    if(${CMAKE_SIZEOF_VOID_P} STREQUAL "4")
        message(FATAL_ERROR "Win32 architecture is no more supported!!!")
    endif()
    list(APPEND CC_UI_RESOURCES
        ${CC_PROJECT_DIR}/game.rc
    )
    list(APPEND CC_PROJ_SOURCES
        ${CC_PROJECT_DIR}/main.cpp
        ${CC_PROJECT_DIR}/resource.h
        ${CC_UI_RESOURCES}
    )
    # 收集资源,实现逻辑在cmake/common.cmake
    cc_include_resources(${RES_DIR}/data CC_ASSET_FILES)
    list(APPEND CC_ALL_SOURCES ${CC_PROJ_SOURCES} ${CC_COMMON_SOURCES} ${CC_ASSET_FILES})
    # 很重要的一个函数,实现逻辑也在cmake/common.cmake
    cc_common_before_target(${target_name})
endmacro()

macro(cc_windows_after_target target_name)
        
    source_group(TREE ${RES_DIR}/data PREFIX "Resources" FILES ${CC_ASSET_FILES})
    source_group(TREE ${CC_PROJECT_DIR} PREFIX "Source Files" FILES ${CC_PROJ_SOURCES})
    source_group(TREE ${CC_PROJECT_DIR}/../common PREFIX "Source Files" FILES ${CC_COMMON_SOURCES})
    
    
    target_link_libraries(${target_name} ${ENGINE_NAME})
    target_include_directories(${target_name} PRIVATE
        ${CC_PROJECT_DIR}/../common/Classes
    )

    set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${target_name})
    cc_common_after_target(${target_name})
    if(EXISTS ${RES_DIR}/data/jsb-adapter)
        set(bin_dir ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR})
        add_custom_target(copy_resource ALL
            COMMAND ${CMAKE_COMMAND} -E echo "Copying resources to ${bin_dir}"
            COMMAND ${CMAKE_COMMAND} -E make_directory ${bin_dir}/Resources
            COMMAND robocopy "${RES_DIR}/data/" "${bin_dir}/Resources/" /MIR || (exit 0)
            COMMAND ${CMAKE_COMMAND} -E echo "Copying resources done!"
        )
        add_dependencies(${target_name} copy_resource)
        set_target_properties(copy_resource PROPERTIES FOLDER Utils)
    endif()

    if(MSVC)
        foreach(item ${WINDOWS_DLLS})
            get_filename_component(filename ${item} NAME)
            get_filename_component(abs ${item} ABSOLUTE)
            add_custom_command(TARGET ${target_name} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${abs} $<TARGET_FILE_DIR:${target_name}>/${filename}
            )
        endforeach()
        foreach(item ${V8_DLLS})
            get_filename_component(filename ${item} NAME)
            add_custom_command(TARGET ${target_name} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${V8_DIR}/$<IF:$<BOOL:$<CONFIG:RELEASE>>,Release,Debug>/${filename} $<TARGET_FILE_DIR:${target_name}>/${filename}
            )
        endforeach()
        target_link_options(${target_name} PRIVATE /SUBSYSTEM:WINDOWS)
    endif()

endmacro()

  • cmake/common.cmake
macro(cc_common_before_target target_name)
    set(CC_TARGET_NAME ${target_name})
    if(NOT CC_TARGET_NAME)
        message(FATAL_ERROR "CC_TARGET_NAME is not set!")
    endif()
    if(NOT SKIP_SCAN_PLUGINS AND USE_PLUGINS)
      # 里面有execute_process,调用nodejs脚本plugins_parser.js
      cc_gen_plugin_cmake_hook() 
    else()
        message(STATUS " Skip search plugins")
    endif()
    cc_load_hooks("Pre")
    if(USE_PLUGINS)
        # 加载ProjDir/Pre*.cmake、ProjDir/*Pre.cmake脚本
        cc_plugin_entry() 
    endif()
endmacro()

总结

总体来说,如果对cmake比较熟悉的话,还是非常容易看明白大概的实现思路,cmake的学习曲线本身也没有那么高。