UPDATE: Apple changed everything (again) without telling developers (again) and broke everything (again).
Current versions of Xcode (the minimum that Apple allows you to use) will *not* work with the architecture-link part of this blog post.
Instead, see this StackOverflow question I asked (and answered) with an updated technique: http://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4
This is NOT a perfect way of doing static libs on iPhone; however, it does work – I’ve used it in iPhone apps that are on the App Store right now – and I’ve been sitting on this post for over a month, improving it, tweaking it. It’s not perfect, but I think it’s better to put it out onto Google than to keep it quiet any longer.
NB: I’m not a Mac developer; the iPhone is relatively new to me. I apologize in advance for any stupid mistakes I make here where I should have known better!
PS: If you don’t read this blog normally, you might be dismayed at the amount of whinging about Apple’s less-than-perfect code and documentation in this post. Sorry. Let me just say that I am *trying* to be even-handed – and that that requires not only praising Apple, but also calling out their bad acts where relevant. I and many of my friends and ex-colleagues have suffered hours and days of wasted time – ON THIS TOPIC ALONE – because of tiny things that Apple staff “forgot to mention” or “didn’t bother testing”. It’s understandable, but it’s also painful.
A recap of what we need
You might want to read How to make an iPhone static library – part 1 if you haven’t already – it goes into much more detail on what we need, and why.
Recapping the key points:
- One (or more…) “libSOMETHING.a” files that we can include in other projects
- Must work both in Simulator and on Device
- Must be distributable with NO SOURCE CODE (even if we ourselves have the source code – this makes version control much simpler!)
- Must be implemented ENTIRELY inside Xcode – no external tools or dependencies
- Must work on iPhone OS 2.x and iPhone OS 3.x
On OS 2.x, I didn’t need to do this, but with 3.x, Apple hard-coded some new bugs into Xcode, making Xcode even harder to use.
Here’s the bug:
In the Build Settings for a Target, Xcode for iPhone OS 3.x and above will IGNORE the architecture instructions you give it
This means that “Compile these architectures” (a list of names) and “Only build the selected architecture” (a checkbox) are both ignored. You MUST treat these settings as “FYI only”, and be careful not to set them manually – your settings will be silently overridden. I’ve tested this by editing the Xcode .xcodeproj files (XML format) manually, and finding that they are ignored no matter what.
Instead, you MUST make all your architecture-configuration inside the “Base SDK” setting, and then NEVER EVER use anything EXCEPT the “Base SDK” setting in the main Build drop-down (the one you can see at all times when in the main Xcode view, up at the top left, just underneath the red, yellow, and green buttons (close window/minimize window/maximize window).
Unfortunately, this makes that drop-down ENTIRELY USELESS. Crap.
But … there’s a workaround!
You can create MULTIPLE values for Base SDK, and use the master drop-down to choose between them.
By (manually) creating new Target’s.
Ultimately, we create a unique Target for every set of Build Settings we need. In the simple version, all we have to do is create one Target for the Device, and a separate Target for the Simulator. However, ultimately, you want (MUST!) create a separate Target for each combination of:
- Device / Simulator
- Debug / Release
- Main app / Lite app
…because if you don’t, then it’s highly likely you’ll accidentally change that drop-down one day, your project will screw up completely, and you’ll have no idea what’s going on. I have helped lots of people who broke their projects by using the master drop-down in OS 3.x. So many that I’m sure Apple *never* bothered to test this drop-down with basic Unit Tests, sadly, or they would have noticed quickly that they’d broken the settings that populate it.
Creating your Targets
First, understand that there are two “sets” of targets in Xcode (and there is NO way of telling the difference between them – this is a classic Xcode design bug).
There’s the Targets you can see – in the Targets twisty – and then there’s the “root” or “default” Target, which you access by scrolling to the top of the “Groups & Files” pane, and clicking on the first line (should be the same name as your Xcode Project). ALL values in the default target are copied into the real Targets, and then any “local overrides” in the real Targets are applied.
Usually, you only have 3-10 settings in the real Targets – all the rest is inherited from the default target.
Create new Targets by right-clicking on the Targets twisty and selecting “Add… -> New Target…” and selecting “Static Library” from the GUI. Make sure to give the new static lib a unique name!
MAKE SURE you do not re-use the same product name / target name / executable name that the original project’s target had (again, just trust me: this exposes so many horrible bugs in Xcode that you simply DO NOT want to go there…). Standard practice is to make TWO new Targets, include “Device” in the name of one of them, and “Simulator” in the name of the other one, and delete the original one (but only after you’ve tested the new projects are fully working!)
NB: Xcode has hard-coded a “magic” relationship between the Target name and the Project name and the Application Binary name. Just don’t go there. I’ve tried; it’s a horrible hacky mess, and if you screw it up you have to manually edit Xcode’s behind-the-scenes text files to fix it – Xcode is hard-coded to prevent you from editing it yourself, so just avoid it all and keep all your Target names “unique”.
One Target for device, one Target for simulator
On iPhone OS 2.x, you could create a single file, e.g. “libMY-LIBRARY.a” (NB: other standards, not important here, dictate that the filename must begin “lib” and must end “.a”), which contained both the Device binary and the Simulator binary; this is no longer possible on OS 3.x.
Instead, you MUST create two separate binaries. If you look at the mainstream 3rd-party libraries (e.g. AdMob), you’ll see that they give you TWO .a files, one “something-something-device.a” and the other “something-something-simulator.a”. This is because they’re using separate Xcode Target’s for Simulator and Device.
In each of those Target’s, simply:
- Click on the Target name in the Groups & Files pane
- Press command-I
- Go to the “Build” tab
- Change the Base SDK (It’s right at the top of the list) to whatever is appropriate – Simulator 2.2.1, or Device 3.0, or whatever
Now you have some “unconfigured” new Targets, but you need to add all your source files to them, or they will do nothing.
This is a bit trickier, because you have to: Close your eyes, cross your fingers (because there’s no way you can know if this will work or not – this being Apple (useless at UI design), and this being Xcode (highly random on what it does for drag/drop)). However, *trust me*: it just so happens that Xcode will 100% of the time in this situation do what you actually wanted: duplicate all the links to the files and move the duplicates to the place you wanted them.
- Select everything in the original Target’s sub-groups, one sub-group at a time, and drag them to the equivalent sub-group of each of the new targets.
Making life simpler: screw Apple’s (broken) Configurations
Configurations are a pain in the ass – now that OS 3.x has broken the Base SDK and Architecture settings, they don’t really work; it’s far, FAR easier just to delete them.
- select the root Target (first item in the Groups & Files pane)
- hit command-I
- Go to the “Configurations” tab
- select each of them and press “Delete” button at bottom left of the screen, until there’s only one left
Of course, now you’ve lost the ability to dynamically change between “normal” and “highly optimized” compiles.
Or .. have you?
No, of course you haven’t – you just need to create some more Targets!
Assuming you deleted the “Release” Configurations, create 2 new Targets: “Release Simulator” and “Release Device”. Set them as per the original targets (you can in fact create them FROM the original Targets – although Apple has removed the keyboard shortcut, you can select each existing Target, and from the Edit menu select “Duplicate”; this is a good idea, as it spares you from manually setting the Base SDK and possibly getting it wrong).
Unfortunately, you will need to manually re-create the “Release” custom settings.
Create a new, virgin, Xcode project, set it to Release mode, and then open the Build Settings. From the second drop-down at top-left, titled “Show:”, select “Settings Defined at This Level”, to see ONLY the things that are “unique” to Release builds – and copy the changed settings across to your manually-created “Release” Targets.
How do I use this to Build?
The drop down at top-left of Xcode’s main window should now:
- NOT have multiple things in the “Active Configurations” section – instead it should have only one, either “Debug” (or “Release” – which ever one you didn’t delete). NB: this DOES NOT MEAN ANYTHING
- SHOULD have multiple things in the “Active Target” section – one for each different thing you think you want to build
- WILL have lots of things in the “Active SDK” section
This is VERY IMPORTANT:
ALWAYS leave the “Active SDK” set to “Base SDK”; this is to workaround the bug(s) in the version of Xcode that came with iPhone OS 3.x; you may be tempted to change it BUT DO NOT! Instead, only ever change the Active Target
This way, all you have to do is glance at the Active Target, and you will always – ALWAYS – know exactly what Xcode is going to build right now.
Eventually, Apple will (hopefully) fix the new bugs they introduced for OS 3.x, and we can go back to using the drop-down to select Configurations, SDK’s, etc – but, actually, now that I’m used to using Targets, I see it’s much, much easier – and a lot less error-prone. Basically, Apple’s GUI is too poorly designed, and it’s just easier to have ONE thing to click on, rather than lots of them. So, I think I’ll stick to it from now on.
How do I use this in a separate project?
Still a lot of work to do, I’m afraid…
But first of all, you need to right-click EACH of the items in the Products twisty (there will be exactly one for each Target you have), and select “Reveal in Finder”.
Then, you need to drag/drop the revealed files – one by one – into the APP project.
NB: a lot of people have their Xcode setup such that these binaries will be located in DIFFERENT directories – that’s why you should use “Reveal in Finder” to make sure that you are *guaranteed* to be drag/dropping the correct file. I’ve confidently dragged the wrong file before (an old build from a previous setup), and wasted hours trying to figure out why none of my source changes seemed to show up in the app. Oh, how I laughed when I worked out the cause (actually, I nearly cried).
Libraries: the problem(s)
First, you need to copy all the Header files. Frameworks do this for you – but, remember: Apple has banned us from using Frameworks on iPhone :(.
Second, you need to implement some minor manual fixes to make Objective C work correctly. Objective C is very, very hacky, and with external libraries you need to set an extra linker flag to make it link the libs correctly.
Copying the header files
More bugs. Xcode’s “Add Files” dialog box has multiple bugs that have been around for multiple versions of Xcode. I’ve found only one way of reliably adding header files for external libs – and it’s not perfect :(.
- create a folder on your hard disk, inside the LIBRARY proejct, called “[library] headers” where [library] is the name of your library
- inside Xcode, manually delete all headers from your LIBRARY project – but do NOT click “also move to trash”
- manually move all header files (using Finder) into the “library headers” sub-folder
- inside Xcode, select “add existing files” and re-add all the headers
- …now go to your APP project…
- drag/drop the “library headers” folder from Finder into your APP project inside Xcode
- you MUST select “Recursively create groups for any added folders” – there are bugs in the other option that cause it to break your Xcode project
- you SHOULD NOT select “Copy items into destination group’s folder”; any changes to your header files inside LIBRARY project will automatically be picked up by APP project; however, if you delete the source for LIBRARY project (e.g. if you do a CVS checkin and then delete the working directory), then the APP project will refuse to build any more… it’s up to you.
What’s wrong with this?
Only one thing: when you create new Header files in LIBRARY, they do not automatically appear inside APP. You have to manually drag/drop them from finder into Xcode when it has APP project open.
However, at least Xcode does automatically create a Group named “[library] headers” and place all the files inside that group, which is nice…
Fixing the Objective C linker
If you don’t do this, then several things go wrong:
- Interface Builder appears to work, but any time your NIB files reference a LIBRARY class, they will cause your APP to crash with “impossible” line numbers and “impossible” error messages”
- Funky code in your library that uses custom Categories on Apple classes will fail to run at all
Apple (and sites you’ll find on Google) warn you about the second item above, but I only discovered the first item by accident.
UPDATE: To fix the first item:
UPDATE: …well, the approach for the second used to work for me, but now it dosn’t any more. Maybe a bug in the latest SDK update from apple, may be something else enitrely.
To fix the second item:
- open up your APP project
- go to the root Target
- in the Build Settings page type: “Other Linker Flags” into the search box. You should get one result
- double-click on the RHS, and click the “+” button on the dialog that pops up
- type “-ObjC” into the second dialog, and OK out of there
I have heard rumours that you might also need “-all_load” for some libraries – but I’ve not investigated that myself, and I’ve not (yet) encountered situations where it’s needed, sorry!
UPDATE: With iPhone OS 3.0 and above, you often DO need the -all_load flag – it’s due to bugs in Apple’s shipped SDK for 3.0. Oops.
UPDATE: Other problems with Interface Builder
If you do NOT manually copy all your header files across to the application project, IB will stop working.
That’s a great feature of IB. That’s terrible GUI design from Apple, though: the only way you’d ever find out about it is if someone else told you first. I’ve not yet heard of this technique anywhere except on that one linked page above. YMMV…
Hopefully I haven’t missed anything out. If I have, I’ll re-write the post as necessary when people point out what’s missing.