How to build ICU so I can use it in an iPhone app?

egrunin picture egrunin · Nov 14, 2011 · Viewed 9.6k times · Source

How do I configure and build ICU so I can link it to my iPhone app?

I'm maintaining an iPhone app that uses a SQLite database. Now I have to compile with ICU support enabled (SQLITE_ENABLE_ICU). I've got the latest ICU source.

The configure flags I'm using:

./configure --target=arm-apple-darwin --enable-static --disable-shared

After that, running gnumake runs without errors.

Then I add the libraries to my Xcode project. But when I build, I get 50 lines of this:

Undefined symbols:
  "_uregex_close_48", referenced from:
      _icuRegexpDelete in libsqlite3-cerod.a(sqlite3_cerod.o)
  "_ubrk_current_48", referenced from:
      _icuNext in libsqlite3-cerod.a(sqlite3_cerod.o)
  "_ucol_strcoll_48", referenced from:
      _icuCollationColl in libsqlite3-cerod.a(sqlite3_cerod.o)
  "_u_isspace_48", referenced from:
      _icuRegexpFunc in libsqlite3-cerod.a(sqlite3_cerod.o)
  "_utf8_countTrailBytes_48", referenced from:
      _utf8_countTrailBytes_48$non_lazy_ptr in libsqlite3-cerod.a(sqlite3_cerod.o)
     (maybe you meant: _utf8_countTrailBytes_48$non_lazy_ptr)
  "_ubrk_next_48", referenced from:
      _icuNext in libsqlite3-cerod.a(sqlite3_cerod.o)

Any idea what I'm doing wrong?

Edited to add:

When I add the libraries to the project (right-click on the project name, then Add Existing...), I get this:

ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libicudata.a, file was built for unsupported file format which is not the architecture being linked (i386)
ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libicui18n.a, file was built for unsupported file format which is not the architecture being linked (i386)
ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libicuio.a, file was built for unsupported file format which is not the architecture being linked (i386)
ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libicule.a, file was built for unsupported file format which is not the architecture being linked (i386)
ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libiculx.a, file was built for unsupported file format which is not the architecture being linked (i386)
ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libicutu.a, file was built for unsupported file format which is not the architecture being linked (i386)
ld: warning: in /Users/eric.grunin/dev/iOS/icu/source/lib/libicuuc.a, file was built for unsupported file format which is not the architecture being linked (i386)

That's why I think I'm building the library incorrectly. It's as if it's saying:

  • It can't tell what architecture the .a files are built for
  • libsqlite3-cerod.a is built for i386

I don't understand either possibility, but I'm new to iPhone development.

Edited to add

I tried @Sergio Moura's solution, and got the error mentioned in my comment.

I tried @sergio's solution, which built. But I'm still getting the equivalent errors, starting with:

ld: warning: in /Users/eric.grunin/dev/iOS/icu/iosbuild/lib/libicudata.a, file was built for unsupported file format which is not the architecture being linked (i386)

Might I be telling Xcode the wrong thing? I'm right-clicking on the project name, then selecting "Add->Existing File", and choosing the six or seven .a files from /icu/iosbuild/lib. Is that the correct process?

Note:

@sergio is recommending configure --host=arm-apple-darwin, @Sergio Moura is using configure --target=arm-apple-darwin. Neither made a difference, alas.

Edit #2

Targeting the device (instead of the emulator) solved all but one of the link errors! Here's what's left:

Undefined symbols for architecture armv6:
  "___sync_synchronize", referenced from:
      _ucol_initUCA_48 in libicui18n.a(ucol_res.ao)
      udata_getHashTable()      in libicuuc.a(udata.ao)
      _umtx_init_48 in libicuuc.a(umutex.ao)
      _initCache in libicuuc.a(uresbund.ao)
      icu_48::hasService()       in libicui18n.a(coll.ao)
      _ucol_initInverseUCA_48 in libicui18n.a(ucol_bld.ao)
      icu_48::locale_set_default_internal(char const*)in libicuuc.a(locid.ao)
      ...
ld: symbol(s) not found for architecture armv6

This was preceded by a cascade of these warnings:

ld: warning: CPU_SUBTYPE_ARM_ALL subtype is deprecated: /Users/eric.grunin/dev/iOS/icu/iosbuild/lib/libicuuc.a(resbund.ao)
ld: warning: CPU_SUBTYPE_ARM_ALL subtype is deprecated: /Users/eric.grunin/dev/iOS/icu/iosbuild/lib/libicuuc.a(ustrfmt.ao)

Edit #3

@Stephen R. Loomis's suggestion that I change #define U_HAVE_GCC_ATOMICS from 1 to 0 (in platform.h) made no difference, alas. I also realized that the last line of the error (not found for architecture arm6) didn't mean it would work for arm7, it was only an fyi that this was a cross-compile. When I specified an arm7 build, it failed with the same messages. Alas.

Edit #4

Success!

Summary: @sergio's build flags were essentially correct. I added -DU_HAVE_GCC_ATOMICS=0 to the ios build's CFLAGS. The one thing I had been doing wrong was not realizing I needed to cross-compile the library to create a device build.

I haven't tried to repeat this for the simulator, but that's outside the scope of my question.

Special thanks to Steven R. Loomis for pitching in, and to Sergio Moura for getting things rolling.

Answer

sergio picture sergio · Nov 17, 2011

EDIT:

I can confirm that if you do, as Steven R. Loomis suggests:

  1. set U_HAVE_GCC_ATOMICS to 0 in icu/source/common/unicode/platform.h

  2. make distclean

  3. sh cross_configure.sh (using my script, i.e., if you are using it)

the problem should be solved. Indeed, without doing this, the built libraries contain the offending undefined symbol:

sergio@sfogliatella$ nm -a ./lib/libicuuc.a | grep __sync_
     U ___sync_synchronize
     U ___sync_val_compare_and_swap_4
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize
     U ___sync_synchronize

After following the above suggestion, this is the result for the same command:

sergio@sfogliatella$ nm -a ./lib/libicuuc.a | grep __sync_
nm: no name list
nm: no name list

So, definitely, the offending symbol is not present in the binaries.

END EDIT.

Cross-compiling libicu for iOS requires two separate steps:

  1. compiling libicu for your host (MacOS) in a build directory;

  2. cross-compiling libicu for iOS by also specifying the cross-compile directory.

The reason why step 1 is necessary is that libicu will bootstrap itself a bit, i.e., it will compile some intermediate tools, which will be then used in the rest of the build process; those tools need to be ran on the host platform, so they are to be available.

Well, all in all, you can follow the steps (1. compile for the host):

$ cd $icu
$ mkdir hostbuild
$ cd hostbuild
$ ../icu/source/configure <configure settings you need>
$ gnumake

Once this is done, it's time to cross-compile (2. compile for iOS):

$ cd $icu  (or cd ../ from the previous directory)
$ mkdir iosbuild
$ cd iosbuild
$ sh ../cross_configure_icu.sh
$ gnumake

Where cross_configure_icu.sh is a shell script similar to those proposed by Sergio Moura above, but customized for libicu and using the more advanced llvm compiler:

DEVROOT=/Developer/Platforms/iPhoneOS.platform/Developer
SDKROOT=$DEVROOT/SDKs/iPhoneOS4.3.sdk
SYSROOT=$SDKROOT

ICU_PATH=<ABSOLUTE_PATH_TO_YOUR_ICU_DIR>
ICU_FLAGS="-I$ICU_PATH/source/common/ -I$ICU_MYSRC/source/tools/tzcode/ "

export CXXPP=
export CXXPPFLAGS=
export CPPFLAGS="-I$SDKROOT/usr/lib/gcc/arm-apple-darwin10/4.2.1/include/ -I$SDKROOT/usr/llvm-gcc-4.2/lib/gcc/arm-apple-darwin10/4.2.1/include/ -I$SDKROOT/usr/include/ -I$SDKROOT/usr/include/c++/4.2.1/armv7-apple-darwin10/ -I./include/ -miphoneos-version-min=2.2 $ICU_FLAGS"

export CFLAGS="$CPPFLAGS -pipe -no-cpp-precomp -isysroot $SDKROOT"
export CPP="$DEVROOT/usr/bin/cpp $CPPFLAGS"
export CXXFLAGS="$CFLAGS" 
export CC="$DEVROOT/usr/llvm-gcc-4.2/bin/arm-apple-darwin10-llvm-gcc-4.2"
export CXX="$DEVROOT/usr/llvm-gcc-4.2/bin/arm-apple-darwin10-llvm-g++-4.2"
export LDFLAGS="-L$SDKROOT/usr/lib/ -isysroot $SDKROOT -Wl,-dead_strip -miphoneos-version-min=2.0"

sh $ICU_PATH/source/configure --host=arm-apple-darwin --enable-static --disable-shared -with-cross-build=$ICU_PATH/hostbuild

In the above script (source), ICU_PATH is an absolute path because libicu configure so requires for the with-cross-build option. Again, check your values for the SDK and compilers, but this should be ok for 4.3.

Finally, you should take into account that Apple has (half) rejected at least one app that was linked against libicu, because it uses reserved APIs. Have a look at this S.O. topic.

EDIT:

happy the hear that you could compile!

now, to the linking problem.

first of all, please check that the libicu libraries are in the correct format:

sergio@sfogliatella$ lipo -info ./lib/libicuuc.a 

output should be (for any of the libs):

input file ./lib/libicuuc.a is not a fat file
Non-fat file: ./lib/libicuuc.a is architecture: arm

If this is fine, then next question: are you building for the simulator or for the device? simulator needs i386 libraries, device arm libraries... from the error message you show:

ld: warning: ... file was built for unsupported file format which is not the architecture being linked (i386)

it seems to me that you are building against the simulator... for that you will need "normal" macos x libs...