ios - Importing CommonCrypto in a Swift framework

ID : 20393

viewed : 7

Tags : iosswiftcommoncryptoios

Top 5 Answer for ios - Importing CommonCrypto in a Swift framework

vote vote

98

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 
vote vote

83

You can actually build a solution that "just works" (no need to copy a module.modulemap and SWIFT_INCLUDE_PATHS settings over to your project, as required by other solutions here), but it does require you to create a dummy framework/module that you'll import into your framework proper. We can also ensure it works regardless of platform (iphoneos, iphonesimulator, or macosx).

  1. Add a new framework target to your project and name it after the system library, e.g., "CommonCrypto". (You can delete the umbrella header, CommonCrypto.h.)

  2. Add a new Configuration Settings File and name it, e.g., "CommonCrypto.xcconfig". (Don't check any of your targets for inclusion.) Populate it with the following:

    MODULEMAP_FILE[sdk=iphoneos*]        = \     $(SRCROOT)/CommonCrypto/iphoneos.modulemap MODULEMAP_FILE[sdk=iphonesimulator*] = \     $(SRCROOT)/CommonCrypto/iphonesimulator.modulemap MODULEMAP_FILE[sdk=macosx*]          = \     $(SRCROOT)/CommonCrypto/macosx.modulemap 
  3. Create the three referenced module map files, above, and populate them with the following:

    • iphoneos.modulemap

      module CommonCrypto [system] {     header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonCrypto.h"     export * } 
    • iphonesimulator.modulemap

      module CommonCrypto [system] {     header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"     export * } 
    • macosx.modulemap

      module CommonCrypto [system] {     header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/CommonCrypto/CommonCrypto.h"     export * } 

    (Replace "Xcode.app" with "Xcode-beta.app" if you're running a beta version. Replace 10.11 with your current OS SDK if not running El Capitan.)

  4. On the Info tab of your project settings, under Configurations, set the Debug and Release configurations of CommonCrypto to CommonCrypto (referencing CommonCrypto.xcconfig).

  5. On your framework target's Build Phases tab, add the CommonCrypto framework to Target Dependencies. Additionally add libcommonCrypto.dylib to the Link Binary With Libraries build phase.

  6. Select CommonCrypto.framework in Products and make sure its Target Membership for your wrapper is set to Optional.

You should now be able to build, run and import CommonCrypto in your wrapper framework.

For an example, see how SQLite.swift uses a dummy sqlite3.framework.

vote vote

76

I found a GitHub project that successfully uses CommonCrypto in a Swift framework: SHA256-Swift. Also, this article about the same problem with sqlite3 was useful.

Based on the above, the steps are:

1) Create a CommonCrypto directory inside the project directory. Within, create a module.map file. The module map will allow us to use the CommonCrypto library as a module within Swift. Its contents are:

module CommonCrypto [system] {     header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.0.sdk/usr/include/CommonCrypto/CommonCrypto.h"     link "CommonCrypto"     export * } 

2) In Build Settings, within Swift Compiler - Search Paths, add the CommonCrypto directory to Import Paths (SWIFT_INCLUDE_PATHS).

Build Settings

3) Finally, import CommonCrypto inside your Swift files as any other modules. For example:

import CommonCrypto  extension String {      func hnk_MD5String() -> String {         if let data = self.dataUsingEncoding(NSUTF8StringEncoding)         {             let result = NSMutableData(length: Int(CC_MD5_DIGEST_LENGTH))             let resultBytes = UnsafeMutablePointer<CUnsignedChar>(result.mutableBytes)             CC_MD5(data.bytes, CC_LONG(data.length), resultBytes)             let resultEnumerator = UnsafeBufferPointer<CUnsignedChar>(start: resultBytes, length: result.length)             let MD5 = NSMutableString()             for c in resultEnumerator {                 MD5.appendFormat("%02x", c)             }             return MD5         }         return ""     } } 

Limitations

Using the custom framework in another project fails at compile time with the error missing required module 'CommonCrypto'. This is because the CommonCrypto module does not appear to be included with the custom framework. A workaround is to repeat step 2 (setting Import Paths) in the project that uses the framework.

The module map is not platform independent (it currently points to a specific platform, the iOS 8 Simulator). I don't know how to make the header path relative to the current platform.

Updates for iOS 8 <= We should remove the line link "CommonCrypto", to get the successful compilation.

UPDATE / EDIT

I kept getting the following build error:

ld: library not found for -lCommonCrypto for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)

Unless I removed the line link "CommonCrypto" from the module.map file I created. Once I removed this line it built ok.

vote vote

64

This answer discusses how to make it work inside a framework, and with Cocoapods and Carthage

🐟 modulemap approach

I use modulemap in my wrapper around CommonCrypto https://github.com/onmyway133/arcane, https://github.com/onmyway133/Reindeer

For those getting header not found, please take a look https://github.com/onmyway133/Arcane/issues/4 or run xcode-select --install

  • Make a folder CCommonCrypto containing module.modulemap

      module CCommonCrypto {     header "/usr/include/CommonCrypto/CommonCrypto.h"     export *   } 
  • Go to Built Settings -> Import Paths

      ${SRCROOT}/Sources/CCommonCrypto 

🌳 Cocoapods with modulemap approach

🐘 public header approach

🐏 Cocoapods with public header approach

🐝 Interesting related posts

vote vote

58

Good news! Swift 4.2 (Xcode 10) finally provides CommonCrypto!

Just add import CommonCrypto in your swift file.

Top 3 video Explaining ios - Importing CommonCrypto in a Swift framework

Related QUESTION?