DEV Community


Posted on

How to build VCX iOS library

VCX is part of Hyperledger’s Indy and Aries projects. It’s a library for exchange of verifiable credentials written in Rust. It has also c-callable API with wrappers for iOS, Java, Node and other languages.
Our team needed to build the iOS static library from scratch and I decided to write down some notes from the process. The description of the build process is not only a good help for those who need the same, but it’s also good for understanding the build process itself which can be useful for any other library.

I won’t go into details describing all commands, I just want to provide a high-level description. If you want to see specifics, go to this repo jakubkoci/indy-vcx-build with the final Bash script or look at these two links which were my inspiration (thank you to all authors for their contribution):


You need to run a build script on Mac OS with Rust and some other libraries installed. You will find everything described in the script.


At the end of the process, we want to have a vcx.framework folder containing a binary file compiled for all required architectures together. This binary file will be combined from libvcx library and all its dependencies libindy, libssl, libcrypto, libsodium and libzmq.


Whenever you want to build a native library, you need to select the architecture a.k.a type of processor of a device where your app, and therefore library, will be running. There are usually following options for iOS:

  • arm64 (aarch64-apple-ios): 64-bit ARM processor in iPhone 5s, iPad Air and newer.
  • armv7s (armv7s-apple-ios): 32-bit ARM processor in iPhone 5.
  • armv7 (armv7-apple-ios): 32-bit ARM processor in iPhone 3GS, 4, 4S.
  • x86_64 (x86_64-apple-ios): 64-bit simulator.
  • i386 (i386-apple-ios): 32-bit simulator.

In my case, OpenSSL library supports only two architectures, arm64 for devices and x86_64 for a simulator, so I need to build libraries only for these two.

You can find more about architectures here:

Build Native Libraries

For every 3rd party library I checkout particular repo and run a build script within it:





I take similar steps with libindy, but on top of cloning the repo, I also checkout the required version by tag:

$ git clone git clone
$ cd indy-sdk/libindy
$ git checkout $INDY_VERSION
$ cargo lipo —release —targets="aarch64-apple-ios,x86_64-apple-ios”

You can spot that instead of architectures arm64 or x86_64 I use aarch64-apple-ios, x86_64-apple-ios. I call them triplets. Some libraries use the first architecture format, some use triplets, but you can see the mapping between architectures and triplets in the Architectures section above.

You can find more about architectures here:


Lipo is a command-line tool that allows us to create universal binary with more architectures in one file (called Universal Static Library, or just “fat file”). We can also use it for extracting one universal file into more files according to given architecture (“non-fat file”).

If you check the output library with lipo you will see whether it’s a fat-file or not, and what architectures it contains.

For example, in our case:

$ lipo -info output/libsodium-ios/dist/ios/lib/libsodium.a
Architectures in the fat file: output/libsodium-ios/dist/ios/lib/libsodium.a are: armv7 armv7s i386 x86_64 arm64 

or, for libindy:

$ lipo -info output/indy-sdk/libindy/target/aarch64-apple-ios/release/libindy.a
Non-fat file: output/indy-sdk/libindy/target/aarch64-apple-ios/release/libindy.a is architecture: arm64

If you run this command for every library you’ll see that all 3rd party libraries are fat files with all architectures, but libindy has been built as non-fat files. We need to extract architectures from fat-files to non-fat files:

$ lipo -extract ${arch} ../$FILE_PATH -o ${arch}/$FILE_NAME-fat.a
$ lipo ${arch}/$FILE_NAME-fat.a -thin $arch -output ${arch}/$FILE_NAME.a

You may be confused why there are two steps (extracting — keep output as a fat file with given architecture only, and thinning — create a non-fat file with given architecture). That’s because you need to extract architecture before you thin the file. If you try to thin the file directly, you will get the following error:

fatal error: /Applications/ input file (arm64/libzmq.a) must be a fat file when the -thin option is specified


I also copied libindy from indy-sdk to a different directory. Extracting and copying helps us set environment variables which are required to build libvcx in the following way:

export OPENSSL_LIB_DIR=$WORK_DIR/libs/openssl/${ARCH}
export IOS_SODIUM_LIB=$WORK_DIR/libs/sodium/${ARCH}
export IOS_ZMQ_LIB=$WORK_DIR/libs/zmq/${ARCH}
export LIBINDY_DIR=$WORK_DIR/libs/indy/${ARCH}

cargo build —target “${TRIPLET}--release --no-default-features —features “ci”

Combine It All

Now, we have all libraries libssl, libcrypto, libsodium, libzmq, libindy and libvcx separated by architecture. What we want is one file with all libraries for all architectures. This part is too code heavy to describe it here. In general, we first combine all libraries for given platform and then create a fat file with all architectures with lipo. I prepared a simple drawing describing it:

Alt Process of extracting and combining native libraries

This still not over. To call output libvcxall.a library from our code we need to wrap it with Objective-C and c-callable headers. First, we copy the libvcxall.a to indy-sdk/vcx/wrappers/ios/vcx which contains these headers and then we use xcodebuild and lipo utility to create our final vcx.framework.

Yes, we’re finally done. I hope you find it helpful even if you don’t need to build the library by yourself, but were curious about the process.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.