Skip to content

System.Globalization.Native: configure checks ignore CMAKE_ICU_DIR's headers on Unix targets other than wasm #135571

Description

@caraioniurie47

Description

When CMAKE_ICU_DIR is passed for a Unix target other than Android, Apple, Browser and WASI, the two configure checks in System.Globalization.Native/configure.cmake compile without ${CMAKE_ICU_DIR}/include. Without system ICU headers both fail, config.h gets 0 for both, and the build still succeeds. With system ICU headers installed, the checks read those instead.

The bug is latent upstream. Only eng/native.wasm.targets passes CMAKE_ICU_DIR, for wasm, and wasm takes the hard-coded branch at the top of configure.cmake. Other Unix targets hit the bug only with -cmakeargs -DCMAKE_ICU_DIR=<dir> passed by hand.

With CMAKE_ICU_DIR defined, CMakeLists.txt skips find_path(UCURR_H ...), so UCURR_H is empty here:

configure.cmake:14-15

    if (CLR_CMAKE_TARGET_UNIX)
        set(CMAKE_REQUIRED_INCLUDES ${UCURR_H} ${ICU_HOMEBREW_INC_PATH})

${CMAKE_ICU_DIR}/include is added only after include(configure.cmake) (CMakeLists.txt:59):

CMakeLists.txt:86-87

if (NOT CLR_CMAKE_TARGET_APPLE_MOBILE AND DEFINED CMAKE_ICU_DIR)
    include_directories(${CMAKE_ICU_DIR}/include)

Reproduction Steps

On linux-x64 with no ICU development headers installed, from the root of a clone at d148189:

(mkdir -p artifacts/icu && cd artifacts/icu \
  && curl -sSLO https://github.com/unicode-org/icu/releases/download/release-78.3/icu4c-78.3-sources.tgz \
  && tar xzf icu4c-78.3-sources.tgz && cd icu/source \
  && ./configure --prefix="$PWD/../../static" --enable-static --disable-shared --disable-tests --disable-samples \
  && make -j"$(nproc)" && make install) > icu-build.log
./build.sh libs.native -c Release -cmakeargs "-DCMAKE_ICU_DIR=$PWD/artifacts/icu/static" > native-build.log; echo "build exit code: $?"
grep HAVE_ artifacts/obj/native/net11.0-linux-Release-x64/System.Globalization.Native/config.h

Expected behavior

ICU 78.3 has both UDAT_STANDALONE_SHORTER_WEEKDAYS and ucol_clone:

build exit code: 0
#define HAVE_UDAT_STANDALONE_SHORTER_WEEKDAYS 1
#define HAVE_UCOL_CLONE 1

Actual behavior

build exit code: 0
#define HAVE_UDAT_STANDALONE_SHORTER_WEEKDAYS 0
#define HAVE_UCOL_CLONE 0

artifacts/obj/native/net11.0-linux-Release-x64/CMakeFiles/CMakeConfigureLog.yaml (paths shortened):

src.c:2:22: fatal error: 'unicode/udat.h' file not found
CheckSymbolExists.c:2:10: fatal error: 'unicode/ucol.h' file not found

With HAVE_UDAT_STANDALONE_SHORTER_WEEKDAYS 0, super-short day names use the narrow ones:

pal_calendarData.c:517-521

#if HAVE_UDAT_STANDALONE_SHORTER_WEEKDAYS
            return EnumSymbols(locale, calendarId, UDAT_STANDALONE_SHORTER_WEEKDAYS, 1, callback, context);
#else
            return EnumSymbols(locale, calendarId, UDAT_STANDALONE_NARROW_WEEKDAYS, 1, callback, context);
#endif

HAVE_UCOL_CLONE is read only under STATIC_ICU, which the LOCAL_BUILD configuration of src/native/libs/build-local.sh defines:

pal_collation.c:337-341

#if HAVE_UCOL_CLONE
        pClonedCollator = ucol_clone(pCollator, pErr);
#else
        pClonedCollator = ucol_safeClone(pCollator, NULL, NULL, pErr);
#endif // HAVE_UCOL_CLONE

Configured that way with CMAKE_ICU_DIR added by hand, both values are 0 again, and with -DCMAKE_C_FLAGS=-Werror the build fails:

CC=clang cmake -S src/native/libs/System.Globalization.Native -B <build-dir> -DLOCAL_BUILD:STRING=1 -DCLR_CMAKE_TARGET_UNIX:STRING=1 -DCMAKE_ICU_DIR=<icu-prefix> -DCMAKE_C_FLAGS=-Werror
make -C <build-dir>
pal_collation.c:340:27: error: 'ucol_safeClone_78' is deprecated [-Werror,-Wdeprecated-declarations]

Regression?

No: CMAKE_ICU_DIR first appears in CMakeLists.txt in 0fc8978 (#82393), and configure.cmake has never named it.

Known Workarounds

System ICU headers: the checks then describe that ICU, not the static one.

Configuration

Ubuntu 24.04.4 LTS, x64, clang 18.1.3, CMake 3.28.3, static ICU 78.3, without and with libicu-dev 74.2; ./build.sh libs.native -c Release. Not tried on other OSes.

Other information

Proposed fix: append ${CMAKE_ICU_DIR}/include to CMAKE_REQUIRED_INCLUDES in configure.cmake when CMAKE_ICU_DIR is defined. With it the reproduction prints both values as 1, the -Werror LOCAL_BUILD builds without warnings, and a normal build produces a byte-identical libSystem.Globalization.Native.so.

Options not taken:

  • Treat CMAKE_ICU_DIR as wasm-only: nothing in the repo says so, and CMakeLists.txt:86-88 applies it to every target but Apple mobile.
  • Move include_directories(${CMAKE_ICU_DIR}/include) above include(configure.cmake): CMake ignores the INCLUDE_DIRECTORIES directory property in these checks; only CMAKE_REQUIRED_INCLUDES counts.
  • Set UCURR_H to ${CMAKE_ICU_DIR}/include in CMakeLists.txt: would also work, but gives the find_path result a second meaning, away from the checks.

I have a fix ready and will open a PR for it shortly. Could this be assigned to me?

Note

AI-generated, written at my direction and reviewed by me before posting.
Measured on dotnet/runtime d148189 with the Configuration above: the reproduction block, with and without the fix; the same build with libicu-dev installed, reading the checks' compile lines in CMakeConfigureLog.yaml; the LOCAL_BUILD commands; ./build.sh libs.native -c Release with and without the fix, comparing sha256sum of the .so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-Infrastructure-coreclrOnly use for closed issuesuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions