Preview:
I figured out how to define modules for static libraries, so you can import them into Swift or into other module-defining frameworks. This solves a common build error: “include of non-modular header inside framework module
“, as it allows you to turn that non-modular header into a modular one.
You just have to define a custom module.modulemap
file and copy it into the right place. Read more for details, or skip to the end if you just want to know what to do.
A brief and incomplete rationale for modules
In 2013, Xcode 5 was released with support for modules, a new way of using libraries and frameworks. Xcode 5 only supported modules in system libraries, but Xcode 6 extended support to third-party libraries as well. Before the advent of modules, developers gained access to libraries via #import
statements, which literally copy the requested header files into the importing file. This made the library APIs visible within that file, but required repeatedly parsing system headers again and again as multiple files imported the same headers. This is an unnecessary duplication of work.
We could greatly improve build times if the compiler could parse all the headers of a library just once. However, this is difficult in a #import
-based system, because the compiler does not know what files constitute the canonical headers for any given library. Some libraries provide an “umbrella” header that imports all of its API, but the compiler can’t always know what header that would be, and even these aren’t always complete. (Example: UIKit.h
does not import UIGestureRecognizerSubclass.h
.) We needed a better way of defining the canonical interface for a given library.
Modules are that better way. A module is a binary representation of a library’s interface—once built, the module can be reused throughout the entire compilation process. It can also be used to provide access to the library from other languages, such as Swift. In fact, Swift code can only make use of frameworks and libraries that have a module defined, so this becomes a critical piece of infrastructure going forward.
Module maps
So how do we define a module from a bunch of headers? How do we define the canonical API for a library? Module maps. A module map is a simple file that defines the canonical headers for a library. They’re easiest to understand by example, so let’s jump right in.
The following is a valid module map for an imaginary library. It is place in a file named module.modulemap
in the same directory as the library headers.
module AirplaneKit {
header "Airplane.h"
header "AirplaneView.h"
header "Cockpit.h"
header "Fuselage.h"
header "Wheels.h"
header "Wings.h"
}
Simple enough; the module map is just a list of headers that define the representation of the library. It can get a bit tedious to write out every header, though, and it’s easy to forget to update the modulemap when you add a new header, so module maps support a convenient syntax:
module AirplaneKit {
umbrella header "AirplaneKit.h"
export *
}
That export *
line means “Also consider any file imported by one of the listed headers to be part of the module.” This is generally what you’ll want to do. (The umbrella
part is not strictly necessary, but indicates that the header is expected to import every header file in its directory.)
In general, Xcode has fairly good support for defining modules based on your own frameworks: just set the Defines Module build setting to YES and maintain your framework’s umbrella header. Xcode will build the appropriate module map file and embed it within your framework. Support for static libraries, though, is somewhat lacking.
Before we move on, one important note: In Xcode 6.3.1, you must clean your project after any change to a module.modulemap file. In my experience, the compiled version of the module is not rebuilt when the module map file changes. Also, when tweaking the module map file, make sure you’re editing your original file, not the one in the build folder. Xcode sometimes likes to jump to that one instead.
Defining modules for static libraries
If you’re using a static library and you want to make it available to Swift or to another module, there are 2 things you need to do to “modularize” it.
1. Add a file named exactly module.modulemap
as described above.
2b. If you’re building your own static library, make sure your library’s headers and the module map are being copied to [build directory]/include/[LibraryName]/
. If your static library target is using a Copy Headers build step (as is the default in Xcode 6.3.1), remove it. Instead add a Copy Files build phase, configured as follows:
If you have any problems, please let me know on Twitter (@bjhomer). I’d like to make sure these steps work for everyone.
Leave a Reply