Easy Xcode Static Library Subprojects and Submodules

In which a method is presented for reliably building static libraries with subprojects in Xcode, and it is suggested that this method, combined with Git submodules or other similar mechanisms, provides the best way to share libraries, frameworks, or other code between projects.

I’m sure I don’t need to tell you that it’s a useful thing to be able to share code between projects. On the small end of the scale, you might have created some nice views, or text processing classes, and want to be able to include them in multiple apps. On the larger end, perhaps you produce games written with a shared framework, or you’re maintaining an open source library.

One way to do this is to copy files - or even just snippets of code - between your projects. At a very small scale this works, but as things get larger, inevitably, over time, it leads to a codebase that’s hard to maintain, and probably buggy, because it’s nigh on impossible to keep all the copypasta in sync.

It would be better to keep the shared source together in one place. Doing this, you ensure that bug fixes easily propagate to all the apps using the code, and you can keep tests etc. in a central location.

For iOS (and I would argue, Mac, although I’m focusing on iOS in this post) the ideal way to do this is to create an Xcode project that builds a static library, then include that as a subproject in your app projects. Using features like Git’s submodules, or Subversion’s externals, you can ensure that the files for the same subproject in every app that uses it are kept in sync, with one master copy of the subproject kept centrally. This also enables easier collaboration - whether it’s with colleagues in your company, or the app-developing-public at large via GitHub.

I use this method with libEucalyptus to allow it to be included in multiple apps, some of which I don’t control, but keep it in a central location, and it works well.

“Someone on StackOverflow told me this could never work work.
Or, wait…, maybe they said it always worked automatically?…”

There’s a lot of confusion - and misinformation - out there on the web about how to do this. Most people seem to think that creating a static library project in Xcode, and using this as a subproject in other projects, is fraught with peril - a path filled with custom project configurations, header path manipulation, and strange project-editor voodoo to get things to work. Xcode 4 actually provides fairly good support for doing this though. Set things up correctly once, and you’ll be good to go.

By the end of this blog post, you’ll know how to create an Xcode project that compiles a static library, exposing public headers to other projects in a way that makes them easy to #import, and how to use that project as a subproject in other projects.

You’ll also be able to include this project in superprojects as a submodule using Git, or, if you’re not a Git user, it’ll hopefully be fairly obvious how you could use your favourite version control system to accomplish something similar (for example, using SVN externals).

Create an Xcode project for the static library

Let’s get started making our static library. First, we’ll fire up Xcode and create a new project:

Welcome To Xcode dialog with Create New Project button highlighted

We’ll use the Cocoa Touch Static Library template:

Choose A Template For Your New Project sheet with Cocoa Touch Static Library selected and highlighted

Next, we get to choose a name for our library. Let’s go with the imaginative name of SampleSubproject:

Choose Options For Your New Project sheet with the Product Name input box highlighted

After this, you’ll get a file dialog allowing you to choose where to put the project. I also let Xcode create a Git repository for me - there’s a checkbox in the dialog for that - we’ll use that later.

Hurray, a static library project!

Xcode showing the new static library subproject

Configure the static library target’s header location

Now, there’s basically only one thing to do to get this ready for other projects to use. By default, Xcode will place your public library headers in place where superprojects can include them like this: #import <HeaderFromTheSubproject.h>. This strikes me as a little messy. I like to change things so that I can include the headers like this: #import <SampleSubproject/HeaderFromTheSubproject.h> - just as you would when using a header from a framework (or, indeed, from many system-provided dynamic libraries). Xcode’s default configuration will also treat the library headers as ‘part of’ your finished product, and when you do an Archive build they’ll be included in the Archive. This is obviously wrong for an iOS app, and besides being messy will also cause your app to fail verification - not desirable.

To fix these problems, we’ll need to change one setting in the project editor. Select the project in the Navigator sidebar, then select the SampleSubproject library target, and make sure that All, not Basic, is selected at the top of the list of settings. Then, in the Packaging section, find the setting entitled Public Header Folders Path. Change it, in the target’s column, to read include/$(TARGET_NAME). This will make Xcode place the headers in a subfolder named after the target, and the lack of a ‘/’ at the start of the path means they’ll be placed next to the library, rather than in what Xcode sees as being a location on the target system, so they won’t be included in the products of Archive builds.

Xcode window showing the Public Header Folders Path of SampleSubproject being edited

That’s it. Before we go on to setting up an app project to use this subproject though, let’s make the library do something so we can see it working later.

Write the static library code

First, let’s make a simple class to use - we’ll call it “SSHelloer”. It will, in fine sample code tradition, return strings with a “hello” message. I won’t walk you through adding a class to an Xcode project, since I’m sure you know how to do it already, but here’s the code:

#import <Foundation/Foundation.h>

@interface SSHelloer : NSObject

- (NSString *)hello;

@end
#import <SSHelloer.h>

@implementation SSHelloer

- (NSString *)hello
{
    return @"Hello from SSHelloer in SampleSubproject!";
}

@end

Create a global header for the static library

Next, it’s a bit odd, really, that the template sets us up a class called SampleSubproject. Usually we’d expect a statement like #import <SampleSubproject/SampleSubproject.h> to import all the headers from the SampleSubproject library, not a SampleSubproject class. Let’s rectify this. Delete the SampleSubproject.m file entirely, and change the interface in SampleSubproject.h file to just #import <SampleSubproject/SSHelloer.h>. It’s not very helpful in our simple single-class library, but in a big project with lots of headers you could list all the headers in a list here, and then clients could just #import <SampleSubproject/SampleSubproject.h> to get everything from the library.

Xcode window showing the global library header

Set the visibility for the library headers

The last thing to do is to set the visibility of the headers in the project. For each header, there are three options. Public means that clients of the library will be able to see the header. This is what we want for headers that we’re intending to be visible to the apps including our project as a subproject. Project means that only files in this project will be able to see the headers. It’s useful for classes that are intended to be available to the library you’re creating, but not exposed to clients of the library. Private is a little misleading. Intuitively, you might think its a synonym for Project, but it’s not. It’s intended for headers that are intended for some clients of the library, but are not public. It’s useful, for example, for Apple, which can have headers for private APIs that are available at build-time for ‘internal’ clients to use, but will not be shipped out to us in Mac OS X SDKs. It doesn’t really make any sense in our situation, so it’s best to just ignore that it’s there, and choose from Public or Package.

From that explanation, you can hopefully see that both our headers, SampleSubproject.h and SSHelloer.h need to be Public. You can set this in the “Target Membership” section of the right-hand “Utilities” panel:

Xcode window showing the global library header

Library subproject ready!

Lastly, just as a sanity check, build the library, and all should be well.

That’s it, our subproject is ready to go. At this point, I committed everything to Git, and pushed it to GitHub at http://github.com/th-in-gs/SampleSubproject. I’ll use the repo from GitHub later to clone it into the app that’s using it. If you want to do something similar, even if you’re using Git, it’s not necessary to use GitHub. You can use submodules from any Git host - or even the local filesystem. You could also use another version control system that supports submodules or externals, or even just filesystem symlinks if you like to live dangerously.

Using the subproject

Well, that’s us with a nice static library set up, we’re home and dry, right? Well, not quite - we’re still not using it. As with setting it up, it’s fairly simple in Xcode 4, but it does take a few steps.

Create an app superproject

First, we create a new single-view app, called, imaginatively again, SampleSuperproject. We’ll set this app’s project up to refer to our SampleSubproject, building and linking the library from it into the SampleSuperproject app when the app is built. When it’s run, it’ll use some of the SampleSubproject library’s wonderful helloing functionality.

Choose A Template For Your New Project sheet with Single View Application selected and highlighted

Clone the static library into the app superproject’s directory tree

After creating the SampleSuperproject, we need to get the SampleSubproject ‘into’ it somehow. I use Git submodules to do this, but as I mentioned, you could use e.g. Subversion externals or whatever your version control system’s similar alternative is. If you’re not using Git, skip on to the next section.

To include the submodule, make sure your SampleSuperproject is a Git repository (mine already is, since I let Xcode create one for me). We’re going to have to drop down to the terminal to get the submodule into place. Commands I typed are in bold:

lappy8086:SampleSuperproject jamie$ cd SampleSuperproject lappy8086:SampleSuperproject jamie$ git submodule add git://github.com/th-in-gs/SampleSubproject.git Cloning into 'SampleSubproject'... remote: Counting objects: 21, done. remote: Compressing objects: 100% (14/14), done. remote: Total 21 (delta 6), reused 21 (delta 6) Receiving objects: 100% (21/21), 4.15 KiB, done. Resolving deltas: 100% (6/6), done.

And with that the subproject is set up in the filesystem. Git submodules are slightly strange beasts - the submodule is treated like a single file in the superproject by Git; when you commit it, you commit a reference to a specific version of the subproject. In the filesystem though, the submodule is fully checked out for you to use. If you’re going to be using a subproject from a Git submodule like this, I highly recommend you read up on Git submodules before you do it in earnest, because, like everything with Git, they’re not entirely intuitive. Pro Git has a good chapter on them, as does the official Git Community Book - although I do think that the official examples are rather complex for an introduction. Anyway, for the purposes of this article, let’s assume we now have a copy of the subproject in a directory inside the superproject’s directory.

Add the static library’s .xcodeproj to the app’s project

Getting the subproject into the superproject’s Xcode project is easy. Just find the SampleSubproject.xcodeproj from the subproject folder in Finder, and drag it into Xcode’s Navigator tree. Alternatively, add it with Xcode’s Add Files File menu item (make sure to add the SampleSubproject.xcodeproj file only, not the entire directory).

After you’ve added the subproject, it’ll appear below the main project in Xcode’s Navigator tree:

Xcode window showing SampleSupreproect project with the SampleSubproject subproject highlighted in the Navigator

Configure the app target to build the static library target

Now, we need to get the SampleSuperproject to build and link to the SampleSubproject library.

First, in the SampleSuperproject app’s target settings, find the Build Phases section. This is where we’ll configure the SampleSuperproject target to automatically build and link to the SampleSubproject library.

Xcode window showing SampleSuperbroject's build phases

Once you’ve found that, open the Target Dependencies block and click the ‘+’ button. In the hierarchy presented to you, the SampleSubproject target from the SampleSubproject project should be listed. Select it and click Add.

Adding a target dependency to the SampleSuperproject target

Next, we need to set the app to link to the library when it’s built - just like you would a system framework you wanted to use. Open the Link Binary With Libraries section a bit below the Target Dependencies section, and hit ‘+’ in there too. At the top of the list should be the libSampleSubproject.a static library that the SampleSubproject target produces. Choose it and hit add.

Adding libSampleSubproject.a to the SampleSuperproject target

Lastly, because we’re using Objective-C, we’ll have to add a couple of linker flags to the SampleSuperproject app’s target to ensure that ObjC static libraries like ours are linked correctly. In the SampleSuperproject target’s Build Settings, find the Other Linker Flags line, and add -ObjC and -all_load. I won’t go in to what makes these extra flags necessary technically here, I’ll just say that if you don’t want at the very least strange runtime errors when you try to use categories defined in static libraries, you’ll need them.

Setting the linker flags on the SampleSuperproject target

We’re almost done! If you hit build now, you’ll see that the SampleSubproject library is built before SampleSuperproject app, and they’re linked together. We haven’t actually used any functionality yet though. In order to do this, there’s one more setting to change - we need to be able to find our library’s public headers. Now, there are some sources on the web that will tell you that Xcode 4 will find these automatically, but I’ve never found that to be true.

Configure the app target to use the static library’s headers when building

Again in the SampleSuperproject target’s Build Settings, find the Header Search Paths line this time, and add two settings: "$(TARGET_BUILD_DIR)/usr/local/lib/include" and "$(OBJROOT)/UninstalledProducts/include". Make sure to include the quotes here - they’re necessary if you have any directories with names containing spaces in your file hierarchy (and make sure they’re regular ‘straight’ quotes, not the fancy things Habari will surely turn them into in this post). Why two paths? The first is the ‘normal’ path, where Xcode puts the headers during a normal build. The second is necessary to make builds specifically triggered by the Archive item in the Project menu work. I’m not sure why Xcode’s internal build procedure seems to vary based on whether you’re doing an Archive build or not - to be honest, it feels like a build system bug to me. If anyone knows, I’d love to hear more about it in the comments.

Setting the Header Search Paths on the SampleSuperproject target

Use the static library in the app

Thats it for the settings! Now, in files compiled by the app target, you can just #import <SampleSubproject/SampleSubproject.h> and use functionality from the library, just as you would from a system framework. In our contrived example, we can call [[[SSHelloer alloc] init] hello]; and get our string back:

Calling [[[SSHelloer alloc] init] hello];

Rather than walk through changing the project to do this, I’ll assume that you already know how do do things like make labels in Interface Builder and set up a simple UI. If you want to run the sample app, it’s also on GitHub. Remember, it uses Git submodules to include the static library subproject - you need to remember that when cloning it and make sure to also initialise and update the submodules (you did read those Git submodule references I mentioned earlier, right?), like this:

lappy8086:tmp jamie$ git clone git://github.com/th-in-gs/SampleSuperproject.git Cloning into 'SampleSuperproject'... remote: Counting objects: 36, done. remote: Compressing objects: 100% (21/21), done. remote: Total 36 (delta 13), reused 36 (delta 13) Receiving objects: 100% (36/36), 9.91 KiB, done. Resolving deltas: 100% (13/13), done. lappy8086:tmp jamie$ cd SampleSuperproject/ lappy8086:SampleSuperproject jamie$ git submodule init Submodule 'SampleSubproject' (git://github.com/th-in-gs/SampleSubproject.git) registered for path 'SampleSubproject' lappy8086:SampleSuperproject jamie$ git submodule update Cloning into 'SampleSubproject'... remote: Counting objects: 25, done. remote: Compressing objects: 100% (17/17), done. remote: Total 25 (delta 7), reused 25 (delta 7) Receiving objects: 100% (25/25), 4.84 KiB, done. Resolving deltas: 100% (7/7), done. Submodule path 'SampleSubproject': checked out '8a6ac6af716213b91a3714ce7d6039e121b4b610'

And here’s how it looks:

Calling [[[SSHelloer alloc] init] hello];

Recapping

The steps again, in order:

  1. Create an Xcode project for the static library
  2. Configure the static library target’s header location
  3. Write the static library code
  4. Create a global header for the static library
  5. Set the visibility for the library headers
  6. Create an app superproject
  7. Clone the static library into the app superproject’s directory tree
  8. Add the static library’s .xcodeproj to the app’s project
  9. Configure the app target to build the static library target
  10. Configure the app target to link to the static library target
  11. Configure the app target to use the static library’s headers when building
  12. Use the static library in the app

Of course, in reality, you wouldn’t perform these steps in this order. Perhaps you already have app projects, and want to create the subproject to factor out some common code. Or perhaps there’s a library you want to use already that you just need to reconfigure the project for so that you can include it in this way. Hopefully though, now that you’ve seen that it’s not all that hard to get working, you’ll be able to start using library submodules and reap the code-sharing benefits.

I’d love to see more open source projects having static library targets that are set up in the way described above. That way, they’d be ready for app projects to just use directly as submodules without having to keep special, slightly forked, versions of the projects - or worse, but sadly common, including the code directly.

In case you missed the links, here’s the sample subproject on GitHub, along with the sample superproject app, referencing the subproject as a submodule.


27 Comments

…ocess? Xcode custom build phases and custom build scripts are nearly ideal for this type of work… Easy Xcode Static Library Subprojects and Submodules In which a method is presented for reliably building static libraries with subprojects in Xcode, an…


Nice write up; thank you for providing such a thorough treatment on this.


Hi, nice article. Just to let you know that I’ve found that -all_load is no longer needed to get categories working with the latest SDKs.


…’t had a chance to play with it, so I can’t say much about it, other than it looks interesting. Easy Xcode Static Library Subprojects and Submodules How to share code in Xcode.

Lots and lots of tabs.

Proprietary and commercial Ruby tool chain for…


Hi there, thank you for such a rich and intuitive post! Really does give some useful insight. Had to use subprojects today at work without really knowing what they were, this really cleared things up :) One issue for me along the way. I get an error in my main static library .h file, it cant find the header its including. I think this is because of the scope of the file, as Xcode won’t let me set the Target Membership for any files, see screenshot here: http://cl.ly/image/2o2E0t0R050Z If you can tell by the screenshot, the tick is grayed out and I cant select it! Any insight would be great :)

Thanks


Hi Alex.

That looks mysterious to me, not sure what could be going on there. Are the other headers from your library allowing themselves to be included in targets?


Hello,

in Xcode 4.4.X the scope for header files can’t be set in ‘Target Membership’ to public. Where can this be set in the latest Xcode versions?

Thanks in advance. dominik


Hi! thank you for this post, but I had same problem, that had Alex. If skip this step, I cant use classes from added library in main project, because i have not permission. If anybody know how to fix it, please help) Thanks.


I had the same issue AlexB is seeing: in your explanation, you can select in "Target Membership" to include a header or not.

In XCode 4.4.1, this doesn’t work anymore. (At least, I think so, I didn’t try on earlier versions.) You can only select if a .c or .m file is a target membership. For a .m, this automatically will then also include the same name .h.

But if you want to include a completely separate .h, that doesn’t work.

After hours of looking around, I figure out that you need to select this as follows: Build Phases -> Copy Files Now you can select which files you want to copy to where.

Tom


The quotes around ”$(TARGET_BUILD_DIR)/usr/local/lib/include” are all screwed up - if you copy-paste them with Chrome they will not come out like real quotes, and XCode will not find the path. Also, the folder itself is incorrect, as of XCode 4.3 the "lib/" part must be removed. Both of these problems can be debugged by looking at the build logs.

Jamie, Alex does not include all library header files into his super-project, only the header files he marked as "public", the ones with the exported classes/functions etc.


Alex, Domnik, Roman, Tom, you are correct - there seems to be a bug in the latest Xcode templates. It can be fixed though.

After setting up the subproject, before doing anything else, go to the "Build Phases" of the the static library target, delete the "Copy Files" phase that’s at the end, and add a "Copy Headers" phase in its place. You should then be able to add the headers to the target and set their visibility as the post discusses.


Denis, first, I specifically say in the article to check the quotes are plain quotes if you paste them.

Second, on the paths, I don’t believe you’re correct. I rebuilt the entire project in Xcode 4.5 when trying to reproduce Alex et al.’s issue, and the paths had not changed. I think you must have also changed the "Installation Directory" setting for the library?


With Xcode 4.4.1, adding "$(TARGET_BUILD_DIR)/usr/local/lib/include" to the Header Search Paths for the superproject doesn’t seem to be required. I was able to build for the simulator and device without issues. I still had to add "$(OBJROOT)/UninstalledProducts/include" to make the archive build happy, however.

Thanks for writing this up. It was very helpful.


… Great Article on Setting Up Xcode Projects WIth SubmodulesBy on August 25, 2012 in , , Overview This article explains how to set up projects with in a way that projects can build as they should while keepi…


Thank you, Jamie. Copy Headers work like a charm %)


Thanks! You just saved my day today!


It works!


In order to make the library work when archiving you need to add the following to User Header Search Paths "$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts"

The fix in this guide doesent solve the Archive-problem.

I have XCode 4.3.3 and I’m building for iOS 5.0 target. - Dunno if this has something to do with anything.


Sorry, guys. Forgot to add the url for the source on the Archive-thingie: http://stackoverflow.com/a/9509786/406055


Esben, I believe that this ("$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts") is exactly equivalent to the "$(OBJROOT)/UninstalledProducts/include" I suggested.


just wanted to point out that your instructions about ‘copy files’ vs ‘copy headers’ contradict the recent tutorial published by apple about the subject where it states:

Your library will have one or more header files that clients of that library need to import. To configure which headers are exported to clients, select your library project to open the project editor, select the library target to open the target editor, and select the build phases tab. If your library target has a “Copy Headers” build phase, you should delete it; copy headers build phases do not work correctly with static library targets when performing the “Archive” action in Xcode.

http://developer.apple.com/library/ios/technotes/iOSStaticLibraries/iOSStaticLibraries.pdf


following your tutorial worked on a sample small project i used.. but then when i integrated it to my real project.. i got this error: ld: symbol(s) not found for architecture i386

the offending line was adding the -all_load flag to build settings-> other linker flags.. removing it made my app work just fine.

for more details please see my post on stack over flow: http://stackoverflow.com/questions/6984368/undefined-symbols-for-architecture-i386-objc-class-skpsmtpmessage-refere/14197638#14197638


Thanks for a great post. This helped me a lot. I’ve been searching the web for a good tutorial about static libraries and non of them get very close to the detail level and the flow of this article.

Great post !

Thank you.


Thanks for the comments, Abdullah.

On the i386 missing symbols vs -all_load issue, I suspect that rather than that being causal, you changing the flags just managed to kick Xcode into regenerating some caches that fixed the issue. You’re correct, though, that the -all_load flag is not actually required - it was a bug in the linker that caused it to be necessary in the past (pre Xcode 4.4, I believe). I really don’t think it should be the cause of link issues like the one you describe though.

For the "Copy Headers" issue, without more evidence, I still consider this to be a bug in Xcode that Apple’s tutorial is supplying a different workaround to (and implicitly admitting to here - if it’s the wrong thing to do, why couch the recommendation in language saying it "[does] not work correctly"?). In any case, I like the way I’m doing it, and it works well wherever I’ve used it, so I’m sticking to it :-).


Is there a way to export the library and then import it into Superproject? Say I wanted to give this library to someone else. How would I give them a single .a file or .framework or something that they could import (kind of like an external .jar in java) into their project without cloning from a repository and adding my .xcodeproject file? If this is too far away from the goal of the tutorial, I apologize. Either way — thank you for the tutorial; it’s been the most clear one I’ve found so far!


Hello Jamie, Thanks for the tutorial. I am running into the same issue. After doing all the above mentioned steps, when I try to do MyLibrary/Myheader. h (The file names are changed here) in the actual source code where I am trying to use the library, I get the error MyLibrary/Myheader. h not found. I am having a hard time. Can you please provide me your thoughts?


BTW, I am using the latest Xcode (Xcode 4.6)


Thank you for taking the time to write this out so clearly. Your attention to formatting and the highlighted screenshots made your article easy to scan so I was able to find the information I needed quickly. Very much appreciated.


Jamie,

I just realized that I wrote an article that builds on yours, but never let you know. I gave you mad props! I wrote this back in March, and have gotten some good feedback. But I really wouldn’t have been able to do what I did without your in-depth analysis of static libraries. You did a great job.

Here you go: http://blog.appliedis.com/2013/03/29/static-libraries-in-xcode-4/

I’d love to get some feedback from you. I want to write a follow-up and explore some of the nuances, but it has been back-burnered. I’ll keep you posted if I get to it.


Hello iOSCoder,

you need to add ‘Copy headers’ in your build phases first. Do it by clicking ‘Add build phase’ and then selecting ‘Copy headers’.

Build Phases -> Add Build Phase -> Copy Bundle Resources (in case you don’t have it) Then drag and drop your file there… Good luck ;)

(http://stackoverflow.com/a/20253365)