# Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#
# This source file is part of the Cangjie project, licensed under Apache-2.0
# with Runtime Library Exception.
#
# See https://cangjie-lang.cn/pages/LICENSE for license information.
macro(to_link_library_option lib_name)
if(DARWIN)
set(${lib_name} -l${${lib_name}})
else()
set(${lib_name} -l:lib${${lib_name}}${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endmacro()
function(make_cangjie_lib target_name)
set(options IS_SHARED IS_MACRO ALLOW_UNDEFINED)
set(oneValueArgs)
set(multiValueArgs
DEPENDS
OBJECTS
FORCE_LINK_ARCHIVES
FLAGS
CANGJIE_STD_LIB_LINK
CANGJIE_STDX_LIB_DEPENDS
CANGJIE_STDX_LIB_INDIRECT_DEPENDS)
cmake_parse_arguments(
CANGJIE_LIBRARY
"${options}"
"${oneValueArgs}"
"${multiValueArgs}"
${ARGN})
if(CMAKE_BUILD_STAGE STREQUAL "postBuild")
list(FILTER CANGJIE_LIBRARY_DEPENDS EXCLUDE REGEX "^cangjieCJNATIVE")
endif()
string(CONCAT make_lib_task "make_lib_for_" ${target_name})
set(clang_compiler "${CMAKE_C_COMPILER}")
if(OHOS)
list(APPEND flags_to_compile -z relro -z now -z noexecstack)
# Unfortunately, a bug in library `winpthread` our ld.lld depends causes deadlock occasionally
# while linking. We disable multi-threading for now until we have a work-around.
list(APPEND flags_to_compile --threads=1)
set(clang_compiler "$ENV{CANGJIE_HOME}/third_party/llvm/bin/ld.lld")
set(linker_ld_library_path "$ENV{CANGJIE_HOME}/third_party/llvm/lib")
elseif(DARWIN)
set(clang_compiler "$ENV{CANGJIE_HOME}/third_party/llvm/bin/ld64.lld")
set(linker_ld_library_path "$ENV{CANGJIE_HOME}/third_party/llvm/lib")
elseif(NOT MINGW)
list(APPEND flags_to_compile "-Wl,-z,relro,-z,now,-z,noexecstack")
endif()
if(ANDROID)
# we need use our lld since we have modification to make it link properly
# otherwise it will has false alignment and finally lead to a SEGV when load std-lib.
list(APPEND flags_to_compile "-fuse-ld=$ENV{CANGJIE_HOME}/third_party/llvm/bin/ld.lld")
endif()
if(MINGW)
list(APPEND flags_to_compile "${LINKER_OPTION_PREFIX}--no-insert-timestamp")
list(APPEND flags_to_compile "${LINKER_OPTION_PREFIX}--export-all-symbols")
endif()
if(DARWIN)
if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64")
list(APPEND flags_to_compile -arch arm64)
elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64")
list(APPEND flags_to_compile -arch x86_64)
endif()
if(IOS)
if(IOS_PLATFORM MATCHES "SIMULATOR")
list(APPEND flags_to_compile -syslibroot "${CMAKE_IOS_SDK_ROOT}")
list(APPEND flags_to_compile -platform_version ios-simulator 17.5.0 17.5)
else()
list(APPEND flags_to_compile -syslibroot "${CMAKE_IOS_SDK_ROOT}")
list(APPEND flags_to_compile -platform_version ios 17.5.0 17.5)
endif()
else()
list(APPEND flags_to_compile -syslibroot "${CANGJIE_MACOSX_SDK_PATH}")
list(APPEND flags_to_compile -platform_version macos 12.0.0 "${CANGJIE_MACOSX_SDK_VERSION}")
endif()
endif()
set(target_lib_full_name)
string(TOLOWER ${TARGET_TRIPLE_DIRECTORY_PREFIX}_${CJNATIVE_BACKEND} output_cj_lib_dir)
string(TOLOWER ${TARGET_TRIPLE_DIRECTORY_PREFIX}_${CJNATIVE_BACKEND} output_stdx_cj_lib_dir)
set(output_cj_lib_dir ${output_cj_lib_dir}${SANITIZER_SUBPATH})
set(output_stdx_cj_lib_dir ${output_stdx_cj_lib_dir}${SANITIZER_SUBPATH})
if(CANGJIE_LIBRARY_IS_SHARED)
if(CANGJIE_CODEGEN_CJNATIVE_BACKEND)
set(RUNTIME_LIB_DIR $ENV{CANGJIE_HOME}/lib/${output_cj_lib_dir})
if(NOT OHOS AND NOT DARWIN AND NOT MINGW)
if(CANGJIE_DISCARD_EH_FRAME)
list(APPEND flags_to_compile "-Wl,-T" ${RUNTIME_LIB_DIR}/discard_eh_frame.lds)
endif()
list(APPEND flags_to_compile "-Wl,-T,${RUNTIME_LIB_DIR}/cjld.shared.lds")
elseif(NOT DARWIN AND NOT MINGW)
list(APPEND flags_to_compile "-T" ${RUNTIME_LIB_DIR}/cjld.shared.lds)
endif()
if(DARWIN OR MINGW)
list(APPEND flags_to_compile "${RUNTIME_LIB_DIR}/section.o")
endif()
list(APPEND flags_to_compile "${RUNTIME_LIB_DIR}/cjstart.o")
if(CANGJIE_BUILD_STDLIB_WITH_COVERAGE)
list(APPEND flags_to_compile "$ENV{CANGJIE_HOME}/lib/${output_cj_lib_dir}/libclang_rt-profile.a")
endif()
set(runtime_link_option "cangjie-runtime")
to_link_library_option(runtime_link_option)
list(APPEND flags_to_compile "${runtime_link_option}")
endif()
# Hot reload relies on .gnu.hash section.
if(MINGW)
list(APPEND flags_to_compile "-static")
# Use -fstack-protector-all to let gcc help judge which libssp to link against. Don't use simply -lssp.
list(APPEND flags_to_compile "-fstack-protector-all")
elseif(NOT DARWIN)
# MinGW ld doesn't support --hash-style
list(APPEND flags_to_compile "${LINKER_OPTION_PREFIX}--hash-style=both")
endif()
foreach(libpath ${CANGJIE_TARGET_LIB})
list(APPEND flags_to_compile "-L${libpath}")
endforeach()
if(MINGW)
list(APPEND flags_to_compile "-L${CMAKE_BINARY_DIR}/bin")
endif()
list(APPEND flags_to_compile "-L${CMAKE_BINARY_DIR}/lib/${output_stdx_cj_lib_dir}")
list(APPEND flags_to_compile "-L$ENV{CANGJIE_HOME}/runtime/lib/${output_cj_lib_dir}")
list(APPEND flags_to_compile "-L$ENV{CANGJIE_HOME}/lib/${output_cj_lib_dir}")
if(NOT DARWIN AND NOT MINGW)
# In cross-compilation, we need -L (above one) for library searching and -rpath-link (below one) for
# secondary dependencies (DSOs) searching.
list(APPEND flags_to_compile
"${LINKER_OPTION_PREFIX}-rpath-link=$ENV{CANGJIE_HOME}/runtime/lib/${output_cj_lib_dir}")
endif()
list(APPEND flags_to_compile "-L${CMAKE_BINARY_DIR}/lib")
if(WIN32)
# For Windows target, CMake will place dll in "bin" directory.
list(APPEND flags_to_compile "-L${CMAKE_BINARY_DIR}/bin")
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
if(OHOS)
list(APPEND flags_to_compile "-lclang_rt.builtins")
elseif(IOS)
if(IOS_PLATFORM MATCHES "SIMULATOR")
list(APPEND flags_to_compile "-lclang_rt.iossim")
else()
list(APPEND flags_to_compile "-lclang_rt.ios")
endif()
elseif(DARWIN)
# If native code (C or C++) uses compiler built-in features, the following library needs to be linked.
# For example for `__builtin_cpu_init()` function, clang will generate references to `___cpu_model`
# symbol, which is supplied by libclang_rt.osx.a.
list(APPEND flags_to_compile "-lclang_rt.osx")
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64")
list(APPEND flags_to_compile "-lclang_rt-builtins")
endif()
endif()
if(MINGW)
list(APPEND flags_to_compile "-lclang_rt-builtins")
endif()
set(boundscheck_link_option "boundscheck")
to_link_library_option(boundscheck_link_option)
list(APPEND flags_to_compile "${boundscheck_link_option}")
if(NOT DARWIN)
list(APPEND flags_to_compile "-lm")
endif()
if(OHOS)
list(APPEND flags_to_compile "-lc")
list(APPEND flags_to_compile "-lunwind")
endif()
if(NOT DARWIN AND NOT CANGJIE_LIBRARY_ALLOW_UNDEFINED)
# Extra checkes when generating cangjie shared libraries. If symbols are used in Cangjie but not
# defined in the shared library, an error will be reported. If any of such errors are reported,
# we are likely missing some dependencies to link or functions to implement (e.g. using an
# unimplemented native function in Cangjie).
list(APPEND flags_to_compile "${LINKER_OPTION_PREFIX}--no-undefined")
endif()
# Undefined reference is not allow while linking MachO shared library by default. The following options
# could suppress ld64 from reporting undefined reference errors.
if(DARWIN AND CANGJIE_LIBRARY_ALLOW_UNDEFINED)
list(APPEND flags_to_compile "-undefined" "suppress" "-flat_namespace")
endif()
# For bep, in debug mode, we cannot have the symbol of object's absolute path
if(NOT DARWIN)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
list(APPEND flags_to_compile "-g")
else()
list(APPEND flags_to_compile "-s")
endif()
endif()
if(CANGJIE_LIBRARY_IS_SHARED)
if(DARWIN)
list(APPEND flags_to_compile "-dylib")
else()
list(APPEND flags_to_compile "-shared")
endif()
if("${target_name}" STREQUAL "cangjieStdx")
set(target_lib_full_name
${CMAKE_BINARY_DIR}/lib/${output_stdx_cj_lib_dir}/libstdx${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
if (CANGJIE_LIBRARY_IS_MACRO)
set(target_lib_full_name
${CMAKE_BINARY_DIR}/lib/${output_stdx_cj_lib_dir}/lib-macro_stdx.${target_name}${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(target_lib_full_name
${CMAKE_BINARY_DIR}/lib/${output_stdx_cj_lib_dir}/libstdx.${target_name}${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endif()
else()
set(target_lib_full_name ${CMAKE_BINARY_DIR}/bin/${target_name}${CMAKE_EXECUTABLE_SUFFIX})
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
if(OHOS)
list(APPEND flags_to_compile "-m")
if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL aarch64)
list(APPEND flags_to_compile "aarch64linux")
else()
list(APPEND flags_to_compile "elf_x86_64")
endif()
elseif(NOT DARWIN)
list(APPEND flags_to_compile "--target=${TRIPLE}")
endif()
endif()
if(CANGJIE_TARGET_TOOLCHAIN AND NOT OHOS AND NOT IOS)
list(APPEND flags_to_compile "-B${CANGJIE_TARGET_TOOLCHAIN}")
endif()
if(OHOS)
list(APPEND flags_to_compile "-L${CMAKE_SYSROOT}/usr/lib/${CMAKE_SYSTEM_PROCESSOR}-linux-ohos")
endif()
if(TRIPLE STREQUAL "arm-linux-android23")
set(NDK_PREBUILT_ARCH "linux-x86_64")
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
set(NDK_PREBUILT_ARCH "darwin-x86_64")
endif()
list(APPEND flags_to_compile "-L${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/${NDK_PREBUILT_ARCH}/lib/clang/18/lib/linux/arm")
endif()
if(CMAKE_SYSROOT AND NOT IOS)
list(APPEND flags_to_compile "--sysroot=${CMAKE_SYSROOT}")
endif()
if(DARWIN)
list(APPEND flags_to_compile "-lSystem")
endif()
# All shared libraries of standard library are placed in the same location. Adding $ORIGIN into RPATH so
# standard libraries can find each other.
if(NOT CMAKE_SKIP_RPATH AND ${CMAKE_SYSTEM_NAME} MATCHES "Linux")
list(APPEND flags_to_compile ${LINKER_OPTION_PREFIX}--disable-new-dtags ${LINKER_OPTION_PREFIX}-rpath="\\$$ORIGIN")
endif()
if(DARWIN)
if("${target_name}" STREQUAL "cangjieStdx")
list(APPEND flags_to_compile -install_name "@rpath/libstdx${CMAKE_SHARED_LIBRARY_SUFFIX}")
else()
if (CANGJIE_LIBRARY_IS_MACRO)
list(APPEND flags_to_compile -install_name "@rpath/lib-macro_stdx.${target_name}${CMAKE_SHARED_LIBRARY_SUFFIX}")
else()
list(APPEND flags_to_compile -install_name "@rpath/libstdx.${target_name}${CMAKE_SHARED_LIBRARY_SUFFIX}")
endif()
endif()
list(APPEND flags_to_compile -rpath "@loader_path")
endif()
if(WIN32)
list(APPEND set_env_path "LIBRARY_PATH=${CMAKE_BINARY_DIR}/lib/;${ENV_LIBRARY_PATH}")
else()
list(APPEND set_env_path "LIBRARY_PATH=${CMAKE_BINARY_DIR}/lib/:${ENV_LIBRARY_PATH}")
list(APPEND set_env_path "LD_LIBRARY_PATH=${linker_ld_library_path}:${ENV_LD_LIBRARY_PATH}")
endif()
set(link_std_libraries_flag)
if(ANDROID)
list(APPEND link_std_libraries_flag ${ANDROID_16K_LINKER_FLAGS})
endif()
foreach(library_name ${CANGJIE_LIBRARY_CANGJIE_STDX_LIB_DEPENDS})
set(lib_link_option "stdx.${library_name}")
to_link_library_option(lib_link_option)
list(APPEND link_std_libraries_flag "${lib_link_option}")
endforeach()
foreach(library_name ${CANGJIE_LIBRARY_CANGJIE_STD_LIB_LINK})
if(DARWIN)
list(APPEND link_std_libraries_flag
-L$ENV{CANGJIE_HOME}/runtime/lib/${output_cj_lib_dir} -lcangjie-${library_name})
else()
list(APPEND link_std_libraries_flag -L $ENV{CANGJIE_HOME}/runtime/lib/${output_cj_lib_dir} -l:libcangjie-${library_name}${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endforeach()
foreach(indirect_library_name ${CANGJIE_LIBRARY_CANGJIE_STDX_LIB_INDIRECT_DEPENDS})
if(DARWIN)
list(APPEND link_std_libraries_flag
-lcangjie-${indirect_library_name})
elseif(NOT MINGW)
list(APPEND link_std_libraries_flag ${LINKER_OPTION_PREFIX}--as-needed)
list(APPEND link_std_libraries_flag
-l:libcangjie-${indirect_library_name}${CMAKE_SHARED_LIBRARY_SUFFIX})
list(APPEND link_std_libraries_flag ${LINKER_OPTION_PREFIX}--no-as-needed)
else()
list(APPEND link_std_libraries_flag -l:libcangjie-${indirect_library_name}${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endforeach()
set(force_link_archives_option)
foreach(force_link_archive ${CANGJIE_LIBRARY_FORCE_LINK_ARCHIVES})
set(archive_path "${force_link_archive}")
if(TARGET ${force_link_archive})
get_target_property(target_location ${force_link_archive} COMBINED_STATIC_LIB_LOC)
if(NOT target_location)
set(archive_path $<TARGET_FILE:${force_link_archive}>)
else()
set(archive_path "${target_location}")
endif()
endif()
if(DARWIN)
list(APPEND force_link_archives_option "-force_load")
list(APPEND force_link_archives_option "${archive_path}")
else()
list(APPEND force_link_archives_option "${LINKER_OPTION_PREFIX}--whole-archive")
list(APPEND force_link_archives_option "${archive_path}")
list(APPEND force_link_archives_option "${LINKER_OPTION_PREFIX}--no-whole-archive")
endif()
endforeach()
cj_resolve_depends(resolved_depends
${CANGJIE_LIBRARY_DEPENDS}
${CANGJIE_LIBRARY_CANGJIE_STDX_LIB_DEPENDS}
${CANGJIE_LIBRARY_CANGJIE_STDX_LIB_INDIRECT_DEPENDS})
add_custom_command(
OUTPUT ${target_lib_full_name}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/lib/${output_stdx_cj_lib_dir}
COMMAND ${CMAKE_COMMAND} -E env ${set_env_path} ${clang_compiler} ${CANGJIE_LIBRARY_OBJECTS} ${force_link_archives_option}
${CANGJIE_LIBRARY_FLAGS} ${link_std_libraries_flag} ${flags_to_compile} -o ${target_lib_full_name}
DEPENDS
${resolved_depends}
${CANGJIE_LIBRARY_OBJECTS}
boundscheck
COMMENT "Generating ${target_lib_full_name}")
add_custom_target(
${target_name} ALL
DEPENDS ${target_lib_full_name}
COMMENT "Target executed ${target_lib_full_name}"
)
set_target_properties(${target_name} PROPERTIES CJ_LIB_OUTPUT_FILE ${target_lib_full_name})
else()
message(FATAL_ERROR "only support SHARED or EXE for now")
endif()
if(CANGJIE_LIBRARY_IS_SHARED)
install(FILES ${target_lib_full_name} DESTINATION ${output_stdx_cj_lib_dir}/dynamic/stdx)
if(CANGJIE_LIBRARY_IS_MACRO)
install(FILES ${target_lib_full_name} DESTINATION ${output_stdx_cj_lib_dir}/static/stdx)
endif()
endif()
endfunction()