However, if you have ever written OS X or iOS-specific code, you already discovered that Apple uses a non-standard header organisation. This organisation stems from the concept of "framework" that, according to Apple official documentation, is:
[...] a hierarchical directory that encapsulates shared resources, such as a dynamic shared library, nib files, image files, localized strings, header files, and reference documentation in a single package.
Needless to say, C/C++ compilers and linkers for OS X have been updated to correctly use framework resources. From a C/C++ programmer perspective, nowadays the only practical difference with any other UNIX-flavour is the requirement of using specific linker flags (-framework).
The Problem with Framework Headers
There's an important catch, though. A Framework is organised in a directory hierarchy containing the header files into the Headers subdirectory, and such hierarchy can be nested: that is, a framework directory may contain a set of sub-framework subdirectories, each one organised as a self-contained framework with the same directory structure. The CoreServices framework, one of the basic OS X frameworks, contains a set of sub-frameworks including OSServices and LaunchServices, just to name a few.Two of the most popular cross-platform C/C++ IDEs, Eclipse CDT and NetBeans, do not feature proper support for OS X frameworks yet. Eclipse CDT is making some progresses, but there still are open bugs which make Eclipse CDT practically unusable for serious OS X programming, at least as far as Eclipse CDT Kepler is concerned. You can see the status and the description of the most important Eclipse bugs in the following links:
What these bugs say, to make the long story short, is that frameworks are still unusable, especially so when they are nested.
Solutions
There is no easy solution yet, and I'm only aware of the following workarounds:
- Using XCode.
- Provide a "flattened" include directory containing symlinks to the header directories of each framework (at least the ones you need).
Using XCode
Using Apple's official C/C++ IDE is a solution to overcome the framework headers problem but it's hardly a "workaround"; instead, it's a switch to another IDE. Apple XCode is a good IDE for C/C++ project and it's being constantly improved, but there are important reasons not to use XCode, at least in certain situations. The most important reasons why I do not use XCode are the following:
- I want to use the same IDE on different operating systems, and XCode is available only on OS X.
- I want to use an extensible IDE with a good plugin ecosystem: XCode, in my opinion, is not a good choice here.
- I want to use an IDE that supports the GNU Build System (AKA GNU Autotools). I've tried many times to maintain such projects with XCode but I couldn't find an efficient workflow.
Provide a Flattened Include Directory
A real solution to the headers problem is providing an alternate include directory that your IDE may correctly scan and parse. Let's clarify the reasons why the problem originates in order to understand the rationale behind this solution. The framework directory organisation is tricky because of the following reasons (none of which is taken into account by the above-mentioned IDEs):- A framework root directory is named after the framework to whose name the suffix .framework is added.
- Headers are found into the Headers subdirectory of the framework root.
- As explained in the previous section, frameworks can be nested.
Nested frameworks are particularly tricky and I'll illustrate it with an example. This statement:
#include <CoreServices/CoreServices.h>
is supposed to do include the CoreServices.h file, located into the Headers subdirectory of the CoreServices framework root directory (CoreServices.framework), which is located in one of the operating system framework search path:
- /System/Library/Frameworks
- /Library/Frameworks
Recent Eclipse CDT releases, for example, are able to do this correctly. Nevertheless, they do not work correctly when a framework header uses a nested framework header. The problem with nested frameworks is that this statement (locate into the CoreServices.h file)
#include <OSServices/OSServices.h>
is supposed to include the OSService.h file, located into the Headers subdirectory of the OSServices framework root directory. But this framework is not located into the framework search path, but it's nested (as a sub-framework) into the CoreServices framework.
Both Eclipse CDT and NetBeans fail to interpret this kind of statements correctly, and as a consequence header files are not properly found nor scanned.
The easiest way to trick the IDE into including the files "correctly" is the following:
- Providing a new include directory.
- The include directory will contain symbolic links to the header directories of each required framework (and all its sub-frameworks).
- The name of the symbolic links will be the framework name, stripping the .framework suffix.
This way, when the IDE sees the #include statement of a nested header such as
#include <OSServices/OSServices.h>
it will be able to find the OSServices.h header file by following the OSServices symbolic link, which takes to the sub-framework include directory.
Automating the Creation of the Symbolic Links to the Header Directories
To automate this task I've written a shell script you can find in this GitHub repository. What it does is creating all the relevant links in the current directory. For example, the following command:
$ link-osx-framework-headers.zsh CoreServices
creates the following symbolic links (ls output stripped):
$ ls
AE
CarbonCore
CoreServices
DictionaryServices
LaunchServices
Metadata
OSServices
SearchKit
each one linking to the corresponding include directory, including for nested sub-frameworks:
$ ls -l OSServices
OSServices -> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Headers
Listing the Available Frameworks
If you do not remember the name of a framework you need you can have the script list all the available frameworks by simply invoking it with no parameters:
% link-osx-framework-headers.zsh
/System/Library/Frameworks/AGL.framework
/System/Library/Frameworks/AVFoundation.framework
[...snip...]
/Library/Frameworks/iLifeSlideshow.framework
/Library/Frameworks/iTunesLibrary.framework
Configuring the IDE
To have your IDE scan the newly created include directory, you have to configure it appropriately. Typically every C/C++ IDE will let you add include paths, where header files are searched. In the case of Eclipse CDT, for example, you can:
- Right-click a project and choose Properties (⌘I on OS X).
- Navigate to the C/C++ General/Paths and Symbols pane.
- Choose the Includes tab (the first one).
- Add the include directory for your language (C, C++ or both).
- Workspace.
- File system.
If the directory containing the symbolic links is into the project root, you can include it as a Workspace path, otherwise specify its path as a File System directory. In the following screenshot you can see a workspace include path:
Eclipse CDT - C/C++ Include Path |
Making a System-Wide Include Directory
You could be tempted to make a single include directory with symbolic links for all the framework. That's easy to do:
- Have link-osx-framework-headers.zsh list all the frameworks.
- Pipe it through a while read loop.
- Have link-osx-framework-headers.zsh create each links for each framework.
In shell parlance (using Z Shell):
$ exec zsh
$ link-osx-framework-headers.zsh | \
while read fmk ; do \
link-osx-framework-headers.zsh ${${fmk:t}:r} ; \
done
The workspace include directory still is my favourite option. On the one hand you have to make one include directory for each project and configure each project individually, but on the other hand you are just including the frameworks you really use, thus reducing the quantity of files scanned by the IDE. If you use an external build system, such as the GNU Build System, I find it easier to setup the include directory alongside the Autotools configuration files, so that I keep the files seen by the IDE "in sync" with the package configuration.
Thanks for this post, I wish I had found it a few days earlier as I ended up doing the same thing you did, but after many hours of frustration.
ReplyDeleteYou're welcome!
ReplyDelete