Skip to content

Commit 8af36d6

Browse files
author
Eric Wing
committed
Android: Changes to allow libicu be statically linked as well as libc++_static
This merges with prior FindICU.cmake changes which allow for ICU to be static libraries. This change is needed for Android because dynamically linking libicu is not reliable on Android. There are multiple problems: - libICU is sometimes an internal/private component on Android. In some cases it appears that tryiing to LoadLibrary on libICU will silently skip because the OS already has one loaded. If the symbol names don't match, you get unresolved symbol errors at runtime resulting from a crash without a good understanding why. (This happened to me on my Nexus 7 2013 with 5.0.2). And if the symbols match, but the versions are different, you may have even tougher problems. - If your application needs ICU, you now run the risk of having conflicting versions of ICU. This is a particular risk for middleware where the user may have not built Swift themselves. - Dealing with loading the ICU dependencies is annoying on Android because you must write Java code to load all the libraries, and in the correct order. And your build process must also be aware of all the depenencies so all the .so's get packaged. So static linking ICU solves all these problems. But additionally, the issue of linking the C++ standard library becomes an issue. Again, there are similar issues with dynamically linking because Android doesn't provide a pre-installed system-wide one we can rely on. Additionally: - Android actually provides 3 or 4 different C++ standard libraries you can pick from, all of which are incompatible with each other. - Every new NDK release risks a new version of each of the standard libraries which breaks binary compatibility. If the user is just using the Swift compiler and doesn't think about these issues, this may cause hard problems. - Also, statically linking ICU caused building command line executables (tests) to fail with unresolved symbols. Statically linking C++ fixes this problem and the original behavior is preserved. So static linking solves these problems too. Two new switches are needed to build for Android: --android-icu-data-include /path/to/include --android-icu-data /path/to/libicudata
2 parents 354f974 + f9cbb94 commit 8af36d6

File tree

7 files changed

+92
-19
lines changed

7 files changed

+92
-19
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ set(SWIFT_ANDROID_ICU_I18N "" CACHE STRING
139139
"Path to a directory containing libicui18n.so")
140140
set(SWIFT_ANDROID_ICU_I18N_INCLUDE "" CACHE STRING
141141
"Path to a directory containing headers libicui18n")
142+
set(SWIFT_ANDROID_ICU_DATA "" CACHE STRING
143+
"Path to a directory containing libicudata.so")
144+
set(SWIFT_ANDROID_ICU_DATA_INCLUDE "" CACHE STRING
145+
"Path to a directory containing headers libicudata")
142146

143147
#
144148
# User-configurable Darwin-specific options.

cmake/modules/AddSwift.cmake

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,11 @@ function(_add_variant_link_flags
206206
list(APPEND result
207207
"-ldl"
208208
"-L${SWIFT_ANDROID_NDK_PATH}/toolchains/arm-linux-androideabi-${SWIFT_ANDROID_NDK_TOOLCHAIN_VERSION}/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/${SWIFT_ANDROID_NDK_TOOLCHAIN_VERSION}"
209-
"${SWIFT_ANDROID_NDK_PATH}/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_shared.so"
210-
"-L${SWIFT_ANDROID_ICU_UC}" "-L${SWIFT_ANDROID_ICU_I18N}")
209+
"-L${SWIFT_ANDROID_ICU_UC}" "-L${SWIFT_ANDROID_ICU_I18N}" "-L${SWIFT_ANDROID_ICU_DATA}"
210+
# FIXME: This is to find libc++_static.a. This will need to be more flexible for different architectures.
211+
# NOTE: I'm not sure why I need both lines (to the file and to the path). But without both, the link failed.
212+
"-L${SWIFT_ANDROID_NDK_PATH}/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a"
213+
"-L${SWIFT_ANDROID_NDK_PATH}/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a")
211214
else()
212215
list(APPEND result "-lobjc")
213216
endif()
@@ -1190,6 +1193,18 @@ function(_add_swift_library_single target name)
11901193
_list_escape_for_shell("${c_compile_flags}" c_compile_flags)
11911194
_list_escape_for_shell("${link_flags}" link_flags)
11921195

1196+
1197+
# ANDROID hack: We want to statically link libc++ to avoid potential downstream conflicts with user binaries.
1198+
# Static linking flags are order sensitive and this must come after all the C++ that uses it.
1199+
# More specifically, this must come after the libicu link flags, though if other C++ libraries are linked, this must always be at the end.
1200+
# Because the order is tricky, there wasn't a very natural place for this block of code, so this seemed like the best place.
1201+
# Note that this must be linked in the target/client library, not the host compiler.
1202+
if("${SWIFTLIB_SINGLE_SDK}" STREQUAL "ANDROID")
1203+
list(APPEND SWIFTLIB_SINGLE_PRIVATE_LINK_LIBRARIES
1204+
"-lc++_static")
1205+
endif()
1206+
1207+
11931208
# Set compilation and link flags.
11941209
set_property(TARGET "${target}" APPEND_STRING PROPERTY
11951210
COMPILE_FLAGS " ${c_compile_flags}")

cmake/modules/FindICU.cmake

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Find libicu's libraries
22

3+
# For the given usage:
4+
# find_package(ICU REQUIRED COMPONENTS uc i18n data)
5+
#
6+
# This will set the following variables:
7+
# ICU_I18N_INCLUDE_DIR
8+
# ICU_UC_INCLUDE_DIR
9+
# ICU_DATA_INCLUDE_DIR
10+
# ICU_I18N_LIBRARY
11+
# ICU_UC_LIBRARY
12+
# ICU_DATA_LIBRARY
13+
#
14+
# Additionally, this module supports specifying ICU_ROOT,
15+
# following the convention laid out by a more popular and rigorous FindICU.cmake
16+
# Two different variations are supported:
17+
# (1) Setting an explicit value in CMake, e.g. cmake -DICU_ROOT=/path/to/icu_root
18+
# (2) Setting an environmental variable called ICU_ROOT.
19+
# The explicit setting takes precedent over the environmental variable.
20+
# Both take precedent over the standard system locations.
21+
# This is useful when you need to replace your version of ICU (e.g. different version, supply a static library version)
22+
323
include(FindPackageHandleStandardArgs)
424

525
find_package(PkgConfig)
@@ -11,18 +31,21 @@ foreach(MODULE ${ICU_FIND_COMPONENTS})
1131
list(APPEND ICU_REQUIRED
1232
ICU_${MODULE}_INCLUDE_DIR ICU_${MODULE}_LIBRARIES)
1333

14-
pkg_check_modules(PC_ICU_${MODULE} QUIET icu-${module})
15-
if(${PC_ICU_${MODULE}_FOUND})
16-
set(ICU_${MODULE}_DEFINITIONS ${PC_ICU_${MODULE}_CFLAGS_OTHER})
17-
18-
find_path(ICU_${MODULE}_INCLUDE_DIR unicode
19-
HINTS ${PC_ICU_${MODULE}_INCLUDEDIR} ${PC_ICU_${MODULE}_INCLUDE_DIRS})
20-
set(ICU_${MODULE}_INCLUDE_DIR ${ICU_${MODULE}_INCLUDE_DIR})
21-
22-
find_library(ICU_${MODULE}_LIBRARY NAMES icu${module}
23-
HINTS ${PC_ICU_${MODULE}_LIBDIR} ${PC_ICU_${MODULE}_LIBRARY_DIRS})
24-
set(ICU_${MODULE}_LIBRARIES ${ICU_${MODULE}_LIBRARY})
25-
endif()
34+
# We are not using pkg-config because some systems do not ship with one for ICU (e.g. Ubuntu 12.04, Windows?)
35+
# and not all systems even provide libICU so there may not be a pkg-config that is availble depending how it was built/installed.
36+
# Cross-compiling is another potential pitfall.
37+
# CMake's built in find_ mechanisms are generally more than sufficient and tend to handle the above issues better.
38+
# Also the original pkg-config code could not handle the libicudata case.
39+
40+
find_path(ICU_${MODULE}_INCLUDE_DIR unicode/utypes.h
41+
HINTS ${ICU_ROOT} $ENV{ICU_ROOT}
42+
PATH_SUFFIXES include)
43+
set(ICU_${MODULE}_INCLUDE_DIR ${ICU_${MODULE}_INCLUDE_DIR})
44+
45+
find_library(ICU_${MODULE}_LIBRARY NAMES icu${module}
46+
HINTS ${ICU_ROOT} $ENV{ICU_ROOT}
47+
PATH_SUFFIXES lib)
48+
set(ICU_${MODULE}_LIBRARIES ${ICU_${MODULE}_LIBRARY})
2649
endforeach()
2750

2851
if(NOT "${SWIFT_ANDROID_ICU_UC_INCLUDE}" STREQUAL "")
@@ -31,6 +54,9 @@ endif()
3154
if(NOT "${SWIFT_ANDROID_ICU_I18N_INCLUDE}" STREQUAL "")
3255
set(ICU_I18N_INCLUDE_DIR "${SWIFT_ANDROID_ICU_I18N_INCLUDE}")
3356
endif()
57+
if(NOT "${SWIFT_ANDROID_ICU_DATA_INCLUDE}" STREQUAL "")
58+
set(ICU_DATA_INCLUDE_DIR "${SWIFT_ANDROID_ICU_DATA_INCLUDE}")
59+
endif()
3460

3561
find_package_handle_standard_args(ICU DEFAULT_MSG ${ICU_REQUIRED})
3662
mark_as_advanced(${ICU_REQUIRED})

stdlib/public/core/CMakeLists.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,18 @@ else()
151151
#set(LINK_FLAGS
152152
# -Wl,--whole-archive swiftRuntime -Wl,--no-whole-archive)
153153
list(APPEND swift_core_private_link_libraries swiftRuntime swiftStdlibStubs)
154-
find_package(ICU REQUIRED COMPONENTS uc i18n)
154+
find_package(ICU REQUIRED COMPONENTS uc i18n data)
155+
# If linking against static library versions of ICU, the order matters.
156+
# i18N needs uc and data
157+
# uc needs data
158+
# We need to link against i18n and uc explicitly for dynamic linking.
159+
# We also need to link against data for static libraries.
160+
# TODO: Detect if the library is static and conditionally link.
161+
# NOTE: If ICU is built with data archive, then the data library is a stub.
162+
# (This explicit link is harmless in the dynamic library case.)
163+
# NOTE: If ICU is built with data archive, then the data library still exists as a stub.
155164
list(APPEND swift_core_private_link_libraries
156-
${ICU_UC_LIBRARY} ${ICU_I18N_LIBRARY})
165+
${ICU_I18N_LIBRARY} ${ICU_UC_LIBRARY} ${ICU_DATA_LIBRARY})
157166
endif()
158167

159168
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT "${SWIFT_PRIMARY_VARIANT_SDK}" STREQUAL "ANDROID")

stdlib/public/stubs/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ else()
1414
set(swift_stubs_unicode_normalization_sources
1515
UnicodeNormalization.cpp)
1616
set(swift_stubs_link_libraries
17-
${ICU_UC_LIBRARY} ${ICU_I18N_LIBRARY})
17+
${ICU_UC_LIBRARY} ${ICU_I18N_LIBRARY} ${ICU_DATA_LIBRARY})
1818
include_directories(
19-
${ICU_UC_INCLUDE_DIR} ${ICU_I18N_INCLUDE_DIR})
19+
${ICU_UC_INCLUDE_DIR} ${ICU_I18N_INCLUDE_DIR} ${ICU_DATA_INCLUDE_DIR})
2020
endif()
2121

2222
add_swift_library(swiftStdlibStubs IS_STDLIB IS_STDLIB_CORE

utils/build-script

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,14 @@ the number of parallel build jobs to use""",
624624
"--android-icu-i18n-include",
625625
help="Path to a directory containing headers libicui18n",
626626
metavar="PATH")
627+
android_group.add_argument(
628+
"--android-icu-data",
629+
help="Path to a directory containing libicudata.so",
630+
metavar="PATH")
631+
android_group.add_argument(
632+
"--android-icu-data-include",
633+
help="Path to a directory containing headers libicudata",
634+
metavar="PATH")
627635
android_group.add_argument(
628636
"--android-deploy-device-path",
629637
help="Path on an Android device to which built Swift stdlib products "
@@ -669,14 +677,17 @@ the number of parallel build jobs to use""",
669677
if args.android:
670678
if args.android_ndk is None or \
671679
args.android_ndk_version is None or \
680+
args.android_icu_data is None or \
681+
args.android_icu_data_include is None or \
672682
args.android_icu_uc is None or \
673683
args.android_icu_uc_include is None or \
674684
args.android_icu_i18n is None or \
675685
args.android_icu_i18n_include is None:
676686
print_with_argv0("When building for Android, --android-ndk, "
677687
"--android-ndk-version, --android-icu-uc, "
678688
"--android-icu-uc-include, --android-icu-i18n, "
679-
"and --android-icu-i18n-include must be "
689+
"--android-icu-i18n-include, --android-icu-data, "
690+
"and --android-icu-data-include must be "
680691
"specified.")
681692
return 1
682693
# FIXME: lib/Driver/ToolChains.cpp depends on this environment variable
@@ -999,6 +1010,8 @@ the number of parallel build jobs to use""",
9991010
"--android-icu-uc-include", args.android_icu_uc_include,
10001011
"--android-icu-i18n", args.android_icu_i18n,
10011012
"--android-icu-i18n-include", args.android_icu_i18n_include,
1013+
"--android-icu-data", args.android_icu_data,
1014+
"--android-icu-data-include", args.android_icu_data_include,
10021015
]
10031016
if args.android_deploy_device_path:
10041017
build_script_impl_args += [
@@ -1025,6 +1038,8 @@ the number of parallel build jobs to use""",
10251038
"--android-icu-uc-include", args.android_icu_uc_include,
10261039
"--android-icu-i18n", args.android_icu_i18n,
10271040
"--android-icu-i18n-include", args.android_icu_i18n_include,
1041+
"--android-icu-data", args.android_icu_data,
1042+
"--android-icu-data-include", args.android_icu_data_include,
10281043
]
10291044
build_script_impl_args += build_script_impl_inferred_args
10301045

utils/build-script-impl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ KNOWN_SETTINGS=(
212212
android-icu-uc-include "" "Path to a directory containing headers for libicuuc"
213213
android-icu-i18n "" "Path to a directory containing libicui18n.so"
214214
android-icu-i18n-include "" "Path to a directory containing headers libicui18n"
215+
android-icu-data "" "Path to a directory containing libicudata.so"
216+
android-icu-data-include "" "Path to a directory containing headers libicudata"
215217
android-deploy-device-path "" "Path on an Android device to which built Swift stdlib products will be deployed"
216218
export-compile-commands "" "set to generate JSON compilation databases for each build product"
217219
)
@@ -1566,6 +1568,8 @@ for deployment_target in "${HOST_TARGET}" "${CROSS_TOOLS_DEPLOYMENT_TARGETS[@]}"
15661568
-DSWIFT_ANDROID_ICU_UC_INCLUDE="${ANDROID_ICU_UC_INCLUDE}"
15671569
-DSWIFT_ANDROID_ICU_I18N="${ANDROID_ICU_I18N}"
15681570
-DSWIFT_ANDROID_ICU_I18N_INCLUDE="${ANDROID_ICU_I18N_INCLUDE}"
1571+
-DSWIFT_ANDROID_ICU_DATA="${ANDROID_ICU_DATA}"
1572+
-DSWIFT_ANDROID_ICU_DATA_INCLUDE="${ANDROID_ICU_DATA_INCLUDE}"
15691573
)
15701574
fi
15711575

0 commit comments

Comments
 (0)