mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-01 20:29:00 +00:00
cmake(enhance):Enhanced full WASM library and application compilation
1.add complete compilation FLAGS for wasm toolchain 2.wasm build no longer traverses the native directory, saving build time 3.implement OPT and AOT process actions for wasm files 4.create a bridge interface for navtie build and wasm build Signed-off-by: xuxin19 <xuxin19@xiaomi.com>
This commit is contained in:
parent
2ad162d27d
commit
fb367c9792
5 changed files with 364 additions and 47 deletions
|
|
@ -20,17 +20,7 @@
|
|||
#
|
||||
# ##############################################################################
|
||||
|
||||
# Fake wasm_add_application function to suppress error from native build process
|
||||
function(wasm_add_application)
|
||||
|
||||
endfunction()
|
||||
|
||||
# Fake wasm_add_library function to suppress error from native build process
|
||||
function(wasm_add_library)
|
||||
|
||||
endfunction()
|
||||
|
||||
if(CONFIG_TOOLS_WASM_BUILD)
|
||||
if(CONFIG_TOOLS_WASM_BUILD OR CONFIG_INTERPRETERS_WAMR_BUILD_MODULES_FOR_NUTTX)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
|
|
@ -45,14 +35,22 @@ if(CONFIG_TOOLS_WASM_BUILD)
|
|||
# Get parent dir of CMAKE_CURRENT_SOURCE_DIR
|
||||
get_filename_component(APPDIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
|
||||
|
||||
# Get the Wasm build dirs from the wasm_interface target
|
||||
get_property(
|
||||
WASM_DIRS
|
||||
TARGET wasm_interface
|
||||
PROPERTY WASM_DIR)
|
||||
|
||||
# ensure the Wasm build dirs are unique
|
||||
list(REMOVE_DUPLICATES WASM_DIRS)
|
||||
# Configure and build the Wasm based application
|
||||
add_custom_target(
|
||||
configure_wasm_build
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -B${CMAKE_BINARY_DIR}/Wasm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Wasm -DAPPDIR=${APPDIR} -DTOPDIR=${TOPDIR}
|
||||
-DKCONFIG_FILE_PATH=${KCONFIG_FILE_PATH}
|
||||
-DWASI_SDK_PATH=$ENV{WASI_SDK_PATH})
|
||||
-DTOPBINDIR=${CMAKE_BINARY_DIR} -DKCONFIG_FILE_PATH=${KCONFIG_FILE_PATH}
|
||||
-DWASI_SDK_PATH=$ENV{WASI_SDK_PATH} -DWASM_DIRS=${WASM_DIRS})
|
||||
|
||||
add_custom_target(wasm_build COMMAND ${CMAKE_COMMAND} --build
|
||||
${CMAKE_BINARY_DIR}/Wasm)
|
||||
|
|
|
|||
|
|
@ -32,10 +32,24 @@
|
|||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
|
||||
# Include the NuttX kconfig parser to shared the configuration between the NuttX
|
||||
# build and the Wasm build. And then parse the input KCONFIG_FILE_PATH to get
|
||||
# the configuration.
|
||||
include(${TOPDIR}/cmake/nuttx_kconfig.cmake)
|
||||
|
||||
# Parse the input KCONFIG_FILE_PATH to get the configuration.
|
||||
nuttx_export_kconfig(${KCONFIG_FILE_PATH})
|
||||
|
||||
# Include the WASI-SDK.cmake file to setup the necessary flags for building
|
||||
include(WASI-SDK.cmake)
|
||||
|
||||
project(WasmApps)
|
||||
|
||||
# Add the wasm_interface library to hold all the Wasm libraries.
|
||||
add_library(wasm_interface INTERFACE)
|
||||
|
||||
# Check whether the APPDIR is defined or not. If not, then set it to the parent
|
||||
# directory of the current CMakeLists.txt file.
|
||||
if(NOT DEFINED APPDIR)
|
||||
|
|
@ -47,18 +61,21 @@ if(NOT DEFINED TOPDIR)
|
|||
message(FATAL_ERROR "TOPDIR is not defined")
|
||||
endif()
|
||||
|
||||
# Check wether the TOPBINDIR is defined or not. If not, then raise an error.
|
||||
if(NOT DEFINED TOPBINDIR)
|
||||
message(FATAL_ERROR "TOPBINDIR is not defined")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS ${TOPBINDIR}/wasm)
|
||||
file(MAKE_DIRECTORY ${TOPBINDIR}/wasm)
|
||||
endif()
|
||||
|
||||
# Check wether the KCONFIG_FILE_PATH is defined or not. If not, then raise an
|
||||
# error.
|
||||
if(NOT DEFINED KCONFIG_FILE_PATH)
|
||||
message(FATAL_ERROR "KCONFIG_FILE_PATH is not defined")
|
||||
endif()
|
||||
|
||||
# Include the NuttX kconfig parser to shared the configuration between the NuttX
|
||||
# build and the Wasm build. And then parse the input KCONFIG_FILE_PATH to get
|
||||
# the configuration.
|
||||
include(${TOPDIR}/cmake/nuttx_kconfig.cmake)
|
||||
nuttx_export_kconfig(${KCONFIG_FILE_PATH})
|
||||
|
||||
# Provide FAR macro from command line since it is not supported in wasi-sdk, but
|
||||
# it is used in NuttX code.
|
||||
# ~~~
|
||||
|
|
@ -78,25 +95,11 @@ function(nuttx_add_library)
|
|||
|
||||
endfunction()
|
||||
|
||||
# Recursively find all the CMakeLists.txt files in the ${APPDIR} and add it by
|
||||
# add_subdirectory, but exclude the CMakeLists.txt file in the ${APPDIR}/tools
|
||||
# directory.
|
||||
file(GLOB_RECURSE WASM_APPS ${APPDIR}/*/CMakeLists.txt)
|
||||
list(FILTER WASM_APPS EXCLUDE REGEX ".*/tools/.*")
|
||||
|
||||
# Read and check if wasm_add_application is called in the CMakeLists.txt file in
|
||||
# WASM_APPS If true, then add the directory to the build process
|
||||
foreach(WASM_APP ${WASM_APPS})
|
||||
file(READ ${WASM_APP} WASM_APP_CONTENTS)
|
||||
string(FIND "${WASM_APP_CONTENTS}" "wasm_add_application" WASM_APP_FOUND)
|
||||
string(FIND "${WASM_APP_CONTENTS}" "wasm_add_library" WASM_LIB_FOUND)
|
||||
if(WASM_APP_FOUND GREATER -1 OR WASM_LIB_FOUND GREATER -1)
|
||||
get_filename_component(WASM_APP_DIR ${WASM_APP} DIRECTORY)
|
||||
# Add subdirectory to the build process and put the build directory in the
|
||||
# current build directory with the name same as the relative path of the
|
||||
# ${APPDIR}
|
||||
string(REPLACE ${APPDIR} "" WASM_APP_BUILD_DIR ${WASM_APP_DIR})
|
||||
add_subdirectory(${WASM_APP_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/Wasm/${WASM_APP_BUILD_DIR})
|
||||
endif()
|
||||
# ~~~
|
||||
# Add all the Wasm apps to the build process.
|
||||
foreach(WASM_APP ${WASM_DIRS})
|
||||
string(REPLACE ${APPDIR} "" WASM_APP_BUILD_DIR ${WASM_APP})
|
||||
add_subdirectory(${WASM_APP}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/Wasm/${WASM_APP_BUILD_DIR})
|
||||
endforeach()
|
||||
# ~~~
|
||||
|
|
|
|||
|
|
@ -46,6 +46,43 @@ set(CMAKE_SYSTEM_PROCESSOR wasm32)
|
|||
set(CMAKE_C_COMPILER ${WASI_SDK_PATH}/bin/clang)
|
||||
set(CMAKE_CXX_COMPILER ${WASI_SDK_PATH}/bin/clang++)
|
||||
|
||||
# setup the flags for the compiler and linker
|
||||
|
||||
include_directories(${TOPDIR}/include ${TOPBINDIR}/include)
|
||||
|
||||
if(CONFIG_DEBUG_FULLOPT)
|
||||
add_compile_options(-Oz)
|
||||
elseif(CONFIG_DEBUG_CUSTOMOPT)
|
||||
add_compile_options(${CONFIG_DEBUG_OPTLEVEL})
|
||||
endif()
|
||||
|
||||
if(CONFIG_LTO_FULL OR CONFIG_LTO_THIN)
|
||||
add_compile_options(-flto)
|
||||
add_link_options(-flto)
|
||||
endif()
|
||||
|
||||
add_compile_options(--sysroot=${TOPDIR})
|
||||
add_compile_options(-nostdlib)
|
||||
add_compile_options(-D__NuttX__)
|
||||
|
||||
if(NOT CONFIG_LIBM)
|
||||
add_compile_options(-DCONFIG_LIBM=1)
|
||||
include_directories(${APPDIR}/include/wasm)
|
||||
endif()
|
||||
|
||||
add_link_options(-Wl,--export=main)
|
||||
add_link_options(-Wl,--export=__main_argc_argv)
|
||||
add_link_options(-Wl,--export=__heap_base)
|
||||
add_link_options(-Wl,--export=__data_end)
|
||||
add_link_options(-Wl,--no-entry)
|
||||
add_link_options(-Wl,--strip-all)
|
||||
add_link_options(-Wl,--allow-undefined)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} --print-libgcc-file-name
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
OUTPUT_VARIABLE WCC_COMPILER_RT_LIB)
|
||||
|
||||
# ~~~
|
||||
# Function "wasm_add_application" to add a WebAssembly application to the
|
||||
# build system.
|
||||
|
|
@ -71,11 +108,17 @@ function(wasm_add_application)
|
|||
# Parse the APP_NAME and APP_SRCS from the arguments
|
||||
set(APP_NAME "")
|
||||
set(APP_SRCS "")
|
||||
set(APP_STACK_SIZE 2048)
|
||||
set(APP_INITIAL_MEMORY_SIZE 65536)
|
||||
set(APP_STACK_SIZE "")
|
||||
set(APP_INITIAL_MEMORY_SIZE "")
|
||||
set(APP_INSTALL_NAME "")
|
||||
set(APP_WAMR_MODE INT)
|
||||
set(APP_WCFLAGS "")
|
||||
set(APP_WLDFLAGS "")
|
||||
set(APP_WINCLUDES "")
|
||||
|
||||
cmake_parse_arguments(APP "" "NAME;STACK_SIZE;INITIAL_MEMORY_SIZE" "SRCS"
|
||||
${ARGN})
|
||||
cmake_parse_arguments(
|
||||
APP "" "NAME;STACK_SIZE;INITIAL_MEMORY_SIZE;WAMR_MODE;INSTALL_NAME"
|
||||
"SRCS;WLDFLAGS;WCFLAGS;WINCLUDES" ${ARGN})
|
||||
|
||||
# Check if the APP_NAME (NAME) is provided
|
||||
if(NOT APP_NAME)
|
||||
|
|
@ -87,12 +130,219 @@ function(wasm_add_application)
|
|||
message(FATAL_ERROR "SRCS is not provided.")
|
||||
endif()
|
||||
|
||||
if(NOT APP_STACK_SIZE)
|
||||
set(APP_STACK_SIZE 2048)
|
||||
endif()
|
||||
|
||||
if(NOT APP_INITIAL_MEMORY_SIZE)
|
||||
set(APP_INITIAL_MEMORY_SIZE 65536)
|
||||
endif()
|
||||
|
||||
# Create the executable target for the application
|
||||
add_executable(${APP_NAME} ${APP_SRCS})
|
||||
|
||||
target_link_libraries(${APP_NAME} PRIVATE wasm_interface)
|
||||
target_include_directories(${APP_NAME} PRIVATE ${APP_WINCLUDES})
|
||||
target_compile_options(${APP_NAME} PRIVATE ${APP_WCFLAGS})
|
||||
target_link_options(${APP_NAME} PRIVATE -z stack-size=${APP_STACK_SIZE})
|
||||
target_link_options(${APP_NAME} PRIVATE
|
||||
-Wl,--initial-memory=${APP_INITIAL_MEMORY_SIZE})
|
||||
target_link_options(${APP_NAME} PRIVATE ${APP_WLD_FLAGS})
|
||||
|
||||
target_link_libraries(${APP_NAME} PRIVATE ${WCC_COMPILER_RT_LIB})
|
||||
# Set the target properties
|
||||
set_target_properties(${APP_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}.wasm)
|
||||
|
||||
# do WASM OPTIMIZATION
|
||||
add_custom_target(
|
||||
${APP_NAME}_OPT ALL
|
||||
COMMAND ${WASI_SDK_PATH}/wasm-opt -Oz --enable-bulk-memory -o
|
||||
${APP_NAME}.wasm ${APP_NAME}.wasm
|
||||
DEPENDS ${APP_NAME}
|
||||
COMMENT "WASM build:Optimizing ${APP_NAME}")
|
||||
|
||||
# armv7a
|
||||
if(CONFIG_ARCH_ARMV7A)
|
||||
if(CONFIG_ARCH_CORTEXA5)
|
||||
set(LLVM_CPUTYPE cortex-a5)
|
||||
elseif(CONFIG_ARCH_CORTEXA7)
|
||||
set(LLVM_CPUTYPE cortex-a7)
|
||||
elseif(CONFIG_ARCH_CORTEXA8)
|
||||
set(LLVM_CPUTYPE cortex-a8)
|
||||
elseif(CONFIG_ARCH_CORTEXA9)
|
||||
set(LLVM_CPUTYPE cortex-a9)
|
||||
endif()
|
||||
if(CONFIG_ARM_THUMB)
|
||||
set(LLVM_ARCHTYPE thumbv7)
|
||||
else()
|
||||
set(LLVM_ARCHTYPE armv7a)
|
||||
endif()
|
||||
if(CONFIG_ARCH_FPU)
|
||||
set(LLVM_ABITYPE eabihf)
|
||||
else()
|
||||
set(LLVM_ABITYPE eabi)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# armv7m
|
||||
if(CONFIG_ARCH_ARMV7M)
|
||||
if(CONFIG_ARCH_CORTEXM4)
|
||||
set(LLVM_CPUTYPE cortex-m4)
|
||||
elseif(CONFIG_ARCH_CORTEXM7)
|
||||
set(LLVM_CPUTYPE cortex-m7)
|
||||
else()
|
||||
set(LLVM_CPUTYPE cortex-m3)
|
||||
endif()
|
||||
if(CONFIG_ARCH_CORTEXM3)
|
||||
set(LLVM_ARCHTYPE thumbv7m)
|
||||
else()
|
||||
set(LLVM_ARCHTYPE thumbv7em)
|
||||
endif()
|
||||
if(CONFIG_ARCH_FPU)
|
||||
set(LLVM_ABITYPE eabihf)
|
||||
else()
|
||||
set(LLVM_ABITYPE eabi)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# armv8m
|
||||
if(CONFIG_ARCH_ARMV8M)
|
||||
if(CONFIG_ARM_DS)
|
||||
set(EXTCPUFLAGS +dsp)
|
||||
endif()
|
||||
if(CONFIG_ARM_PACBTI)
|
||||
set(EXTCPUFLAGS ${EXTCPUFLAGS}+pacbti)
|
||||
endif()
|
||||
if(CONFIG_ARM_HAVE_MVE)
|
||||
set(EXTCPUFLAGS ${EXTCPUFLAGS}+mve.fp+fp.dp)
|
||||
endif()
|
||||
if(CONFIG_ARCH_CORTEXM23)
|
||||
set(LLVM_CPUTYPE cortex-m23)
|
||||
elseif(CONFIG_ARCH_CORTEXM33)
|
||||
set(LLVM_CPUTYPE cortex-m33)
|
||||
elseif(CONFIG_ARCH_CORTEXM35P)
|
||||
set(LLVM_CPUTYPE cortex-m35p)
|
||||
elseif(CONFIG_ARCH_CORTEXM55)
|
||||
set(LLVM_CPUTYPE cortex-m55)
|
||||
elseif(CONFIG_ARCH_CORTEXM85)
|
||||
set(LLVM_CPUTYPE cortex-m85)
|
||||
endif()
|
||||
set(LLVM_ARCHTYPE thumbv8m.main${EXTCPUFLAGS})
|
||||
if(CONFIG_ARCH_FPU)
|
||||
set(LLVM_ABITYPE eabihf)
|
||||
else()
|
||||
set(LLVM_ABITYPE eabi)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# armv7r
|
||||
if(CONFIG_ARCH_ARMV7R)
|
||||
if(CONFIG_ARCH_CORTEXR4)
|
||||
set(LLVM_CPUTYPE cortex-r4)
|
||||
elseif(CONFIG_ARCH_CORTEXR5)
|
||||
set(LLVM_CPUTYPE cortex-r5)
|
||||
elseif(CONFIG_ARCH_CORTEXR7)
|
||||
set(LLVM_CPUTYPE cortex-r7)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ARM_THUMB)
|
||||
set(LLVM_ARCHTYPE thumbv7r)
|
||||
else()
|
||||
set(LLVM_ARCHTYPE armv7r)
|
||||
endif()
|
||||
if(CONFIG_ARCH_FPU)
|
||||
set(LLVM_ABITYPE eabihf)
|
||||
else()
|
||||
set(LLVM_ABITYPE eabi)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# armv6m
|
||||
if(CONFIG_ARCH_ARMV6M)
|
||||
set(LLVM_CPUTYPE cortex-m0)
|
||||
set(LLVM_ARCHTYPE thumbv6m)
|
||||
set(LLVM_ABITYPE eabi)
|
||||
endif()
|
||||
|
||||
set(RCFLAGS)
|
||||
set(WRC wamrc)
|
||||
|
||||
if(CONFIG_ARCH_XTENSA)
|
||||
set(WTARGET "xtensa")
|
||||
elseif(CONFIG_ARCH_X86_64)
|
||||
set(WTARGET "x86_64")
|
||||
elseif(CONFIG_ARCH_X86)
|
||||
set(WTARGET "i386")
|
||||
elseif(CONFIG_ARCH_MIPS)
|
||||
set(WTARGET "mips")
|
||||
elseif(CONFIG_ARCH_SIM)
|
||||
list(APPEND RCFLAGS --disable-simd)
|
||||
if(CONFIG_SIM_M32)
|
||||
set(WTARGET "i386")
|
||||
else()
|
||||
set(WTARGET "x86_64")
|
||||
endif()
|
||||
elseif(LLVM_ARCHTYPE MATCHES "thumb")
|
||||
string(FIND "${LLVM_ARCHTYPE}" "+" PLUS_INDEX)
|
||||
if(PLUS_INDEX EQUAL -1)
|
||||
set(WTARGET "${LLVM_ARCHTYPE}")
|
||||
else()
|
||||
string(SUBSTRING "${LLVM_ARCHTYPE}" 0 ${PLUS_INDEX} WTARGET)
|
||||
endif()
|
||||
else()
|
||||
set(WTARGET ${LLVM_ARCHTYPE})
|
||||
endif()
|
||||
|
||||
set(WCPU ${LLVM_CPUTYPE})
|
||||
|
||||
if("${LLVM_ABITYPE}" STREQUAL "eabihf")
|
||||
set(WABITYPE "gnueabihf")
|
||||
else()
|
||||
set(WABITYPE "${LLVM_ABITYPE}")
|
||||
endif()
|
||||
|
||||
list(APPEND RCFLAGS --target=${WTARGET})
|
||||
list(APPEND RCFLAGS --cpu=${WCPU})
|
||||
list(APPEND RCFLAGS --target-abi=${WABITYPE})
|
||||
|
||||
if(CONFIG_INTERPRETERS_WAMR_AOT)
|
||||
if("${APP_WAMR_MODE}" STREQUAL "AOT")
|
||||
# generate AoT
|
||||
add_custom_target(
|
||||
${APP_NAME}_AOT ALL
|
||||
COMMAND ${WRC} ${RCFLAGS} -o ${APP_NAME}.aot ${APP_NAME}.wasm
|
||||
DEPENDS ${APP_NAME}_OPT
|
||||
COMMENT "Wamrc Generate AoT: ${APP_NAME}.aot")
|
||||
set(APP_INSTALL_BIN ${APP_NAME}.aot)
|
||||
if(NOT APP_INSTALL_NAME)
|
||||
set(APP_INSTALL_NAME ${APP_NAME}.aot)
|
||||
endif()
|
||||
elseif("${APP_WAMR_MODE}" STREQUAL "XIP")
|
||||
# generate XIP
|
||||
add_custom_target(
|
||||
${APP_NAME}_AOT ALL
|
||||
COMMAND ${WRC} ${RCFLAGS} --enable-indirect-mode
|
||||
--disable-llvm-intrinsics -o ${APP_NAME}.xip ${APP_NAME}.wasm
|
||||
DEPENDS ${APP_NAME}_OPT
|
||||
COMMENT "Wamrc Generate XIP: ${APP_NAME}.xip")
|
||||
set(APP_INSTALL_BIN ${APP_NAME}.xip)
|
||||
if(NOT APP_INSTALL_NAME)
|
||||
set(APP_INSTALL_NAME ${APP_NAME}.xip)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
set(APP_INSTALL_BIN ${APP_NAME}.wasm)
|
||||
if(NOT APP_INSTALL_NAME)
|
||||
set(APP_INSTALL_NAME ${APP_NAME}.wasm)
|
||||
endif()
|
||||
endif()
|
||||
# install WASM BIN
|
||||
add_custom_target(
|
||||
${APP_NAME}_INSTALL ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${APP_INSTALL_BIN}
|
||||
${TOPBINDIR}/wasm/${APP_INSTALL_NAME}
|
||||
DEPENDS ${APP_NAME}_OPT
|
||||
COMMENT "Install WASM BIN: ${APP_INSTALL_NAME}")
|
||||
endfunction()
|
||||
|
||||
# ~~~
|
||||
|
|
@ -115,8 +365,10 @@ function(wasm_add_library)
|
|||
# Parse the LIB_NAME and LIB_SRCS from the arguments
|
||||
set(LIB_NAME "")
|
||||
set(LIB_SRCS "")
|
||||
set(APP_WCFLAGS "")
|
||||
set(APP_WINCLUDES "")
|
||||
|
||||
cmake_parse_arguments(LIB "" "NAME" "SRCS" ${ARGN})
|
||||
cmake_parse_arguments(LIB "" "NAME" "SRCS;WCFLAGS;WINCLUDES" ${ARGN})
|
||||
|
||||
# Check if the LIB_NAME (NAME) is provided
|
||||
if(NOT LIB_NAME)
|
||||
|
|
@ -131,7 +383,12 @@ function(wasm_add_library)
|
|||
# Create the static library target for the library
|
||||
add_library(${LIB_NAME} STATIC ${LIB_SRCS})
|
||||
|
||||
target_include_directories(${LIB_NAME} PRIVATE ${LIB_WINCLUDES})
|
||||
target_compile_options(${LIB_NAME} PRIVATE ${LIB_WCFLAGS})
|
||||
|
||||
add_dependencies(wasm_interface ${LIB_NAME})
|
||||
target_link_libraries(wasm_interface INTERFACE ${LIB_NAME})
|
||||
# Set the target properties
|
||||
set_target_properties(${LIB_NAME} PROPERTIES OUTPUT_NAME lib${LIB_NAME}.a)
|
||||
set_target_properties(${LIB_NAME} PROPERTIES OUTPUT_NAME ${LIB_NAME})
|
||||
|
||||
endfunction()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue