Importing CommonCrypto in a Swift framework

hpique picture hpique · Aug 11, 2014 · Viewed 102.8k times · Source

How do you import CommonCrypto in a Swift framework for iOS?

I understand how to use CommonCrypto in a Swift app: You add #import <CommonCrypto/CommonCrypto.h> to the bridging header. However, Swift frameworks don't support bridging headers. The documentation says:

You can import external frameworks that have a pure Objective-C codebase, a pure Swift codebase, or a mixed-language codebase. The process for importing an external framework is the same whether the framework is written in a single language or contains files from both languages. When you import an external framework, make sure the Defines Module build setting for the framework you’re importing is set to Yes.

You can import a framework into any Swift file within a different target using the following syntax:

import FrameworkName

Unfortunately, import CommonCrypto doesn't work. Neither does adding #import <CommonCrypto/CommonCrypto.h> to the umbrella header.

Answer

Mike Weller picture Mike Weller · Mar 17, 2017

Something a little simpler and more robust is to create an Aggregate target called "CommonCryptoModuleMap" with a Run Script phase to generate the module map automatically and with the correct Xcode/SDK path:

enter image description here enter image description here

The Run Script phase should contain this bash:

# This if-statement means we'll only run the main script if the CommonCryptoModuleMap directory doesn't exist
# Because otherwise the rest of the script causes a full recompile for anything where CommonCrypto is a dependency
# Do a "Clean Build Folder" to remove this directory and trigger the rest of the script to run
if [ -d "${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap" ]; then
    echo "${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap directory already exists, so skipping the rest of the script."
    exit 0
fi

mkdir -p "${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap"
cat <<EOF > "${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap/module.modulemap"
module CommonCrypto [system] {
    header "${SDKROOT}/usr/include/CommonCrypto/CommonCrypto.h"
    export *
}
EOF

Using shell code and ${SDKROOT} means you don't have to hard code the Xcode.app path which can vary system-to-system, especially if you use xcode-select to switch to a beta version, or are building on a CI server where multiple versions are installed in non-standard locations. You also don't need to hard code the SDK so this should work for iOS, macOS, etc. You also don't need to have anything sitting in your project's source directory.

After creating this target, make your library/framework depend on it with a Target Dependencies item:

enter image description here

This will ensure the module map is generated before your framework is built.

macOS note: If you're supporting macOS as well, you'll need to add macosx to the Supported Platforms build setting on the new aggregate target you just created, otherwise it won't put the module map in the correct Debug derived data folder with the rest of the framework products.

enter image description here

Next, add the module map's parent directory, ${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap, to the "Import Paths" build setting under the Swift section (SWIFT_INCLUDE_PATHS):

enter image description here

Remember to add a $(inherited) line if you have search paths defined at the project or xcconfig level.

That's it, you should now be able to import CommonCrypto

Update for Xcode 10

Xcode 10 now ships with a CommonCrypto module map making this workaround unnecessary. If you would like to support both Xcode 9 and 10 you can do a check in the Run Script phase to see if the module map exists or not, e.g.

COMMON_CRYPTO_DIR="${SDKROOT}/usr/include/CommonCrypto"
if [ -f "${COMMON_CRYPTO_DIR}/module.modulemap" ]
then
   echo "CommonCrypto already exists, skipping"
else
    # generate the module map, using the original code above
fi