Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/msvc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ jobs:
-DCMAKE_INSTALL_PREFIX:PATH="${{ github.workspace }}\dist\opencc-msvc-${{ matrix.package_suffix }}"
cmake --build build-release --config Release --target install

- name: Build ARM64 compile-only targets
- name: Build ARM64 targets (data generation is skipped automatically)
if: ${{ !matrix.can_run_binaries }}
shell: pwsh
run: |
cmake -S . -B build-release `
-A "${{ matrix.cmake_arch }}" `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_OPENCC_JIEBA_PLUGIN=ON
cmake --build build-release --config Release --target libopencc opencc opencc_dict opencc_phrase_extract opencc_jieba
cmake --build build-release --config Release

- name: Build and test debug
if: ${{ matrix.can_run_binaries }}
Expand Down
108 changes: 107 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,105 @@ option(BUILD_SHARED_LIBS "Build opencc as shared library" ${_OPENCC_DEFAULT_BUIL
option(OPENCC_ENABLE_INSTALL "Generate install targets" ${_OPENCC_DEFAULT_ENABLE_INSTALL})
option(ENABLE_GTEST "Build all tests." OFF)
option(ENABLE_BENCHMARK "Build benchmark tests." OFF)
option(OPENCC_BUILD_TOOLS "Build OpenCC command-line tools" ON)

# Host-built tool executables used to generate the dictionaries when the
# host cannot execute the just-built target tools (cross compiling without
# emulation). Dictionaries are architecture-independent data, so generating
# them with host tools produces the same result as generating them on the
# target. Both tools must be built from the same OpenCC source tree as this
# build: dictionaries are generated by opencc_dict and the ST phrase
# generator executes the opencc CLI, and other versions may produce
# inconsistent dictionaries or fail halfway.
set(OPENCC_DICT_EXECUTABLE "" CACHE FILEPATH "Path to a host-built opencc_dict executable used to generate dictionaries when cross compiling; must be built from the same OpenCC source version as this build")
set(OPENCC_CLI_EXECUTABLE "" CACHE FILEPATH "Path to a host-built opencc CLI executable used to generate dictionaries when cross compiling; must be built from the same OpenCC source version as this build")

# Building the dictionaries executes the just-built target tools (opencc_dict
# and the opencc CLI) at build time. When the host cannot execute them and no
# host tools are provided, default to skipping the data build instead of
# failing mid-build. The default can be overridden explicitly, e.g. when the
# host can emulate the target (Rosetta 2).
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/OpenCCRunnability.cmake")

if(OPENCC_DICT_EXECUTABLE OR OPENCC_CLI_EXECUTABLE)
if(NOT OPENCC_DICT_EXECUTABLE OR NOT OPENCC_CLI_EXECUTABLE)
message(FATAL_ERROR
"OPENCC_DICT_EXECUTABLE and OPENCC_CLI_EXECUTABLE must be provided "
"together: building the dictionaries runs both the opencc_dict tool "
"and the opencc CLI.")
endif()
foreach(_opencc_host_tool_var OPENCC_DICT_EXECUTABLE OPENCC_CLI_EXECUTABLE)
if(NOT EXISTS "${${_opencc_host_tool_var}}")
message(FATAL_ERROR
"${_opencc_host_tool_var} does not exist: ${${_opencc_host_tool_var}}")
endif()
endforeach()
# The host tools must at least be runnable on this host (existence above
# says nothing about their dynamic libraries); probe opencc_dict --help
# like the standalone Jieba plugin build does for its installed tool.
opencc_host_tool_command(_opencc_host_dict_command "${OPENCC_DICT_EXECUTABLE}")
execute_process(
COMMAND ${_opencc_host_dict_command} --help
RESULT_VARIABLE _opencc_host_dict_help_result
OUTPUT_VARIABLE _opencc_host_dict_help
ERROR_VARIABLE _opencc_host_dict_help_err
)
if(NOT _opencc_host_dict_help_result EQUAL 0)
message(FATAL_ERROR
"Failed to run host tool ${OPENCC_DICT_EXECUTABLE} (exit status "
"${_opencc_host_dict_help_result}): ${_opencc_host_dict_help_err}")
endif()
set(OPENCC_USING_HOST_TOOLS ON)
else()
set(OPENCC_USING_HOST_TOOLS OFF)
endif()

# Whether OPENCC_BUILD_DATA was provided explicitly — a plain variable
# already defined (a parent project setting it before add_subdirectory(),
# which CMP0077 option() semantics would honor) or a cache entry (-D or a
# previous configure) — so that the warning only fires when the default was
# flipped automatically rather than chosen by the user. When not provided,
# the value stays a plain variable recomputed on every configure: caching it
# would make the first automatic OFF look user-provided on later configures
# (e.g. after host tools are added) and silently ignore them.
get_property(_OPENCC_BUILD_DATA_CACHED CACHE OPENCC_BUILD_DATA PROPERTY TYPE SET)
if(DEFINED OPENCC_BUILD_DATA OR _OPENCC_BUILD_DATA_CACHED)
set(_OPENCC_BUILD_DATA_PROVIDED ON)
else()
set(_OPENCC_BUILD_DATA_PROVIDED OFF)
endif()
set(_OPENCC_BUILD_DATA_AUTO_OFF OFF)
if(NOT _OPENCC_BUILD_DATA_PROVIDED)
if(NOT OPENCC_CAN_RUN_TARGET_TOOLS
AND NOT OPENCC_DICT_EXECUTABLE AND NOT OPENCC_CLI_EXECUTABLE)
set(OPENCC_BUILD_DATA OFF)
set(_OPENCC_BUILD_DATA_AUTO_OFF ON)
else()
set(OPENCC_BUILD_DATA ON)
endif()
endif()
if(_OPENCC_BUILD_DATA_AUTO_OFF)
message(WARNING
"Cross compiling for an architecture the host cannot execute: "
"OPENCC_BUILD_DATA defaulted to OFF, so no dictionaries or config "
"files will be built or installed. Provide host-built tools via "
"OPENCC_DICT_EXECUTABLE and OPENCC_CLI_EXECUTABLE to build them "
"anyway, or set OPENCC_BUILD_DATA=ON explicitly if the host can run "
"the target binaries (e.g. through Rosetta 2 or "
"CMAKE_CROSSCOMPILING_EMULATOR).")
endif()
if(OPENCC_USING_HOST_TOOLS AND _OPENCC_BUILD_DATA_PROVIDED AND NOT OPENCC_BUILD_DATA)
message(WARNING
"Host tools are provided (OPENCC_DICT_EXECUTABLE and "
"OPENCC_CLI_EXECUTABLE) but OPENCC_BUILD_DATA is explicitly OFF: they "
"will not be used and no dictionaries or config files will be built.")
endif()
if(OPENCC_BUILD_DATA AND NOT OPENCC_BUILD_TOOLS AND NOT OPENCC_USING_HOST_TOOLS)
message(FATAL_ERROR
"OPENCC_BUILD_DATA requires OPENCC_BUILD_TOOLS, or host tools via "
"OPENCC_DICT_EXECUTABLE and OPENCC_CLI_EXECUTABLE.")
endif()

# Top-level macOS builds enable the Jieba plugin by default so package
# managers that build with default options (e.g. Homebrew) ship it. Other
# platforms and subproject builds (FetchContent / add_subdirectory) keep it
Expand Down Expand Up @@ -383,13 +482,20 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doc/CMakeLists.txt")
elseif(BUILD_DOCUMENTATION)
message(FATAL_ERROR "BUILD_DOCUMENTATION requires the doc source directory.")
endif()
add_subdirectory(data)
if(OPENCC_BUILD_DATA)
add_subdirectory(data)
elseif(ENABLE_GTEST)
message(FATAL_ERROR "ENABLE_GTEST requires OPENCC_BUILD_DATA: the conversion tests run against the built dictionaries.")
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/plugins/CMakeLists.txt")
add_subdirectory(plugins)
elseif(BUILD_OPENCC_JIEBA_PLUGIN)
message(FATAL_ERROR "BUILD_OPENCC_JIEBA_PLUGIN requires the plugins source directory.")
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
if(ENABLE_GTEST AND NOT OPENCC_BUILD_TOOLS)
message(FATAL_ERROR "ENABLE_GTEST requires OPENCC_BUILD_TOOLS: the conversion tests execute the opencc CLI.")
endif()
add_subdirectory(test)
elseif(ENABLE_GTEST)
message(FATAL_ERROR "ENABLE_GTEST requires the test source directory.")
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
* 新增 Unicode 18.0 篆書區塊(Seal,U+3D000..U+3FC3F,共 11,328 字)與現代漢字的雙向對照。`SealCharacters.txt`(小篆 → 漢字)取自 UCD `SealSources.txt` 的 `kSEAL_MCJK` 屬性,反向詞典 `SealCharactersRev`(漢字 → 小篆)與 `TWVariantsRev` 等一樣於建置時生成。一個漢字對應多個小篆時(共 94 字),反向預設取碼位最小者(通常為《說文解字》正篆,重文在後),另有 4 字以 `@reverse-prefer` 改取版本來源(藤花榭本、陳昌治本、祁寯藻本《說文解字繫傳》、段注本)更完整者。
* 新增 `SealVariants.txt`(現代標準字 → 《說文》隸定字,約 1,000 字,如 `年 → 秊`、`前 → 歬`、`小 → 𡭔`),銜接 `kSEAL_MCJK` 的隸定字形;`t2seal` 先經此表再查 `SealCharactersRev`,`seal2t` 則先查 `SealCharacters` 再以建置時生成的 `SealVariantsRev` 還原為標準字。此表由 shuowen.org 開放數據(Apache-2.0)的字頭序列與 `kSEAL_MCJK` 對齊,並輔以人工審閱的 Unihan 異體資料整理而成。另,《說文》「玉」(U+3D06D)的 `kSEAL_MCJK` 為「王」,與「王」(U+3D068)衝突,詞典中改對應「玉」。
* 新增配置 `t2seal.json`(繁體漢字 → 小篆)、`seal2t.json`(小篆 → 繁體漢字)與 `s2seal.json`(簡體 → 小篆,即 `s2t` 後接 `t2seal`),已加入 CLI 說明、npm CLI 內建配置表與測試案例;僅供探索性研究,顯示需搭配支援 Unicode 18.0 篆書區塊的字型。
* **交叉編譯**:
* 新增 `OPENCC_BUILD_TOOLS` 與 `OPENCC_BUILD_DATA` 選項(加 `OPENCC_` 前綴以免 FetchContent/`add_subdirectory` 場景繼承父專案的同名快取值),修復交叉編譯到宿主無法執行的目標架構(iOS、Android、Windows ARM64 等)時,因建置期執行剛編譯出的 `opencc_dict`/`opencc` 而必然失敗的問題([#615](https://github.com/BYVoid/OpenCC/issues/615))。宿主無法執行目標工具且未提供宿主工具時,`OPENCC_BUILD_DATA` 自動預設為 OFF 並輸出警告,僅產出程式庫與工具,且不安裝任何字典與配置 JSON——包括指向缺失字典的 jieba 配置(`*_jieba.json`);此自動預設值不寫入 cache,之後補傳宿主工具重新配置即恢復生成,明確設為 OFF 而又提供宿主工具時則另行警告。宿主可執行目標二進位的組態(如 x64 Windows 宿主建置 x86,含 Visual Studio `-A` 平台)維持生成字典;依賴 Rosetta 2 等模擬的組態可明確設定 `-DOPENCC_BUILD_DATA=ON`;設定 `CMAKE_CROSSCOMPILING_EMULATOR`(如 qemu)時 data 不自動關閉,建置期工具呼叫(含 `--opencc` 參數)自動冠上模擬器前綴。`ENABLE_GTEST` 現於 `OPENCC_BUILD_TOOLS` 或 `OPENCC_BUILD_DATA` 關閉時明確報錯(轉換測試需執行 opencc 並載入字典)。
* 新增 `OPENCC_DICT_EXECUTABLE`/`OPENCC_CLI_EXECUTABLE` 快取變數,指向預先為宿主架構建置的工具(須與本次建置出自同一份 OpenCC 源碼版本,配置時以 `opencc_dict --help` 探測可執行性);交叉編譯時提供這兩個路徑即可照常生成並安裝完整字典與 jieba 合併詞典(字典屬架構無關資料,宿主生成與目標生成內容一致)。非 Windows 宿主上,安裝在非預設前綴的宿主工具以 `LD_LIBRARY_PATH`/`DYLD_LIBRARY_PATH` 包裹執行以找到同安裝前綴的 libopencc,data/ 與 jieba 插件共用同一包裹輔助函式。可執行性判斷與 jieba 插件既有的 `OPENCC_CAN_RUN_CPPJIEBA_DICT` 機制共用,上提至 `cmake/OpenCCRunnability.cmake`。

## Version 1.4.2

Expand Down
111 changes: 111 additions & 0 deletions cmake/OpenCCRunnability.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Helpers to decide whether binaries built for the target architecture can
# be executed on the build host. The dictionary build (data/) and the Jieba
# plugin (plugins/jieba/) run the just-built tools at build time, so they
# need this to skip or redirect tool execution when cross compiling.
#
# Included from the top-level CMakeLists.txt (integrated builds) and from
# plugins/jieba/CMakeLists.txt (standalone plugin builds, with an inline
# fallback when the parent source tree is not available).

function(opencc_normalize_arch output_var input_value)
string(TOUPPER "${input_value}" _arch)
if (_arch STREQUAL "AMD64" OR _arch STREQUAL "X86_64")
set(_arch "X64")
elseif (_arch STREQUAL "AARCH64")
set(_arch "ARM64")
elseif (_arch MATCHES "^I[3-6]86$" OR _arch STREQUAL "X86" OR
_arch STREQUAL "WIN32")
set(_arch "X86")
endif()
set(${output_var} "${_arch}" PARENT_SCOPE)
endfunction()

function(opencc_can_run_target_on_host output_var host_arch target_arch)
set(_can_run FALSE)
if ("${host_arch}" STREQUAL "${target_arch}")
set(_can_run TRUE)
elseif ("${host_arch}" STREQUAL "X64" AND "${target_arch}" STREQUAL "X86")
# 32-bit Windows binaries run on x64 Windows hosts via WOW64.
set(_can_run TRUE)
endif()
set(${output_var} "${_can_run}" PARENT_SCOPE)
endfunction()

# opencc_host_tool_env: the command prefix that sets up the environment for
# running a host-built tool — "" on a Windows host (the installed layout
# keeps the DLLs next to the executable), otherwise `cmake -E env` plus
# LD_LIBRARY_PATH/DYLD_LIBRARY_PATH pointing at the lib directory of the
# installation the tool comes from (two levels above the executable), for
# tools installed to a non-default prefix that carry no rpath. The env
# variable must be chosen for the HOST platform, not the target: the tool
# runs on the host (e.g. building for a Linux target from macOS still needs
# DYLD_LIBRARY_PATH for the host opencc_dict). Used to wrap whole build-time
# invocations whose tool path is passed on as an argument (the ST phrase
# generator's --opencc): a multi-token command cannot be carried through
# such an argument, but the spawned tool inherits the environment.
function(opencc_host_tool_env output_var tool_path)
if(CMAKE_HOST_WIN32)
set(${output_var} "" PARENT_SCOPE)
else()
get_filename_component(_opencc_tool_bindir "${tool_path}" DIRECTORY)
get_filename_component(_opencc_tool_prefix "${_opencc_tool_bindir}" DIRECTORY)
if(CMAKE_HOST_APPLE)
set(_opencc_tool_libpath
"DYLD_LIBRARY_PATH=${_opencc_tool_prefix}/lib:$ENV{DYLD_LIBRARY_PATH}")
else()
set(_opencc_tool_libpath
"LD_LIBRARY_PATH=${_opencc_tool_prefix}/lib:$ENV{LD_LIBRARY_PATH}")
endif()
set(${output_var} ${CMAKE_COMMAND} -E env "${_opencc_tool_libpath}" PARENT_SCOPE)
endif()
endfunction()

# opencc_host_tool_command: a host-built tool executable wrapped into a
# ready-to-run command list (the env prefix above plus the tool itself).
function(opencc_host_tool_command output_var tool_path)
opencc_host_tool_env(_opencc_tool_env "${tool_path}")
set(${output_var} ${_opencc_tool_env} "${tool_path}" PARENT_SCOPE)
endfunction()

# OPENCC_CAN_RUN_TARGET_TOOLS: TRUE when the host can plausibly execute
# binaries built for the target architecture. This covers native builds,
# x86 targets on x64 Windows hosts (including the Visual Studio generator,
# which does not set CMAKE_CROSSCOMPILING for -A platforms), and cross
# builds with CMAKE_CROSSCOMPILING_EMULATOR (e.g. qemu); emulation such as
# Rosetta 2 (x64 targets on Apple Silicon) is deliberately not detected —
# set OPENCC_BUILD_DATA explicitly when relying on it.
if(DEFINED CMAKE_VS_PLATFORM_NAME AND NOT CMAKE_VS_PLATFORM_NAME STREQUAL "")
set(_OPENCC_TARGET_ARCH_RAW "${CMAKE_VS_PLATFORM_NAME}")
else()
set(_OPENCC_TARGET_ARCH_RAW "${CMAKE_SYSTEM_PROCESSOR}")
endif()
opencc_normalize_arch(OPENCC_TARGET_ARCH "${_OPENCC_TARGET_ARCH_RAW}")
opencc_normalize_arch(OPENCC_HOST_ARCH "${CMAKE_HOST_SYSTEM_PROCESSOR}")

set(OPENCC_CAN_RUN_TARGET_TOOLS TRUE)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WIN32 host/target arch comparison was outside the CMAKE_CROSSCOMPILING check in the original jieba code. VS generator + -A ARM64 on x64 leaves CMAKE_CROSSCOMPILING FALSE, so with this nesting OPENCC_CAN_RUN_TARGET_TOOLS stays TRUE and the ARM64 opencc_dict.exe is executed on the x64 runner (this is the MSVC ARM64 CI failure). Please move the WIN32 comparison out of this if to restore the previous semantics.

if(CMAKE_CROSSCOMPILING)
set(OPENCC_CAN_RUN_TARGET_TOOLS FALSE)
endif()
# The host/target architecture comparison must stay outside the
# CMAKE_CROSSCOMPILING check: the Visual Studio generator builds -A ARM64
# (or Win32) without setting CMAKE_CROSSCOMPILING, so the architectures are
# the only signal there. A mismatched architecture always downgrades to
# FALSE; a runnable one upgrades back to TRUE only on a Windows host, where
# the comparison means WOW64. Cross builds from other hosts to Windows
# (e.g. mingw triplets on Linux/macOS) produce PE binaries the host cannot
# execute even when the architectures match.
if(WIN32 AND OPENCC_TARGET_ARCH AND OPENCC_HOST_ARCH)
opencc_can_run_target_on_host(_opencc_can_run_target
"${OPENCC_HOST_ARCH}"
"${OPENCC_TARGET_ARCH}")
if(NOT _opencc_can_run_target)
set(OPENCC_CAN_RUN_TARGET_TOOLS FALSE)
elseif(CMAKE_HOST_WIN32)
set(OPENCC_CAN_RUN_TARGET_TOOLS TRUE)
endif()
endif()
# A cross-compiling emulator (e.g. qemu, set via CMAKE_CROSSCOMPILING_EMULATOR)
# lets the host execute target binaries as well.
if(CMAKE_CROSSCOMPILING_EMULATOR)
set(OPENCC_CAN_RUN_TARGET_TOOLS TRUE)
endif()
Loading
Loading