controlling which project header file Xcode will include
My Xcode project builds to variations of the same product using two targets. The difference between the two is only on which version of an included library is used. For the .c source files it's easy to assign the correct version to the correct target using the target check box. However, including the header file always includes the same one. This is correct for one target, but wrong for the other one.
Is there a way to control which header file is included by each target?
Here is my project file hierarchy (which is replicated in Xcode):
MyProject
TheirOldLib
theirLib.h
theirLib.cpp
TheirNewLib
theirLib.h
theirLib.cpp
myCode.cpp
and myCode.cpp does thing such as:
#include "theirLib.h"
…
somecode()
{
#if OLDVERSION
theirOldLibCall(…);
#else
theirNewLibCall(…);
#endif
}
And of course, I define OLDVERSION
for one target and not for the other.
Note the #include
must be as shown. Both of the following fail with a file not found error:
#include "TheirOldLib/theirLib.h"
#include "TheirNewLib/theirLib.h"
So is there a way to tell Xcode which theirLib.h
to include per target?
Constraints:
- the two header files have the same name. As a last resort, I could rename one of them, but I'd rather avoid that as this will lead to major hair pulling on the other platforms.
- having to change the #include
to add a reference to the enclosing folder is also something I'd rather avoid, because I would need to do it twice with a conditional compile directive.
- I'm free to tweak my project as I otherwise see fit
Thanks for any help.
The key part of the answer is to use USE_HEADERMAP = NO as suggested by Chris in a comment. Here are the details.
Short recipe (checked in Xcode 3.2.2):
add a custom build setting of USE_HEADERMAP = NO for each concerned target. Here is how:
1.1. Open the target's info panel on the "Build" pane.
1.2. Pull down the action pop-up menu at the bottom left of the window, select "Add User-Defined Setting".
1.3. In the newly added line, set the first column ("Setting") toUSE_HEADERMAP
, and the second column ("Value") toNO
.add the correct include path to each target (target Build settings "Header Search Paths"). In my example that would be:
2.1. addTheirOldLib
for "old" target
2.2. addTheirNewLib
for "new" target
Step 1 disables the automatic header map feature of Xcode, through which any header file included in the project is directly accessible through its name, whatever its actual path. When two headers have the same name, this feature leads to an unresolvable ambiguity.
Step 2 allows for the #include "theirLib.h"
to work without qualifying the header file actual path name.
These two steps together fulfill my two constraints.
Finally, USE_HEADERMAP
is not documented by Apple, as far as I can tell. I'll fill a bug report for that, as this setting is crucial in a number of cases, as googling for it reveals. Reported as rdar://7840694. Also on open Radar as http://openradar.appspot.com/radar?id=253401
USE_HEADERMAP=NO
is overkill for some projects. It might be enough to just use HEADERMAP_INCLUDES_FLAT_ENTRIES_FOR_TARGET_BEING_BUILT=NO
.
Documentation here:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html
Xcode speeds up building by creating header map files.
Instead of providing the compiler with a list of directories where to search for headers, you can also provide it with header map files. A header map file is like a hashtable, the lookup keys are the include
arguments, the values are the paths to the headers.
Here is an example for such a map file:
(Note: This is not the actual syntax of a header map file, it's just a human readable representation)
Foo.h -> /usr/include/Foo.h
Bar.h -> /home/user/Documents/ProjectA/src/include/Bar.h
foo/bar/foobar.h -> /home/user/Documents/ProjectB/inc/foo/bar/foobar.h
These three entries match
#include "Foo.h"
#include "Bar.h"
#include "foo/bar/foobar.h"
Now Xcode has three settings that control the generation of header map files.
-
HEADERMAP_INCLUDES_FLAT_ENTRIES_FOR_TARGET_BEING_BUILT
IfYES
(default), all header files that belong to the target being built are added to the header map file and can be included usinginclude "header.h"
. Note that headers can only belong to framework/library/bundle targets, not to application/program targets. -
HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_ALL_PRODUCT_TYPES
IfYES
(default), headers of all other targets are added to the header map file and can be included usinginclude <TargetName/header.h>
. Note that this is also true for non-framework targets. -
HEADERMAP_INCLUDES_PROJECT_HEADERS
IfYES
(default), all other headers present in the project file the built target belongs to are also added to the header map file and can be included usinginclude "header.h"
.
Additionally there is the general setting USE_HEADERMAP
that controls if a header map file shall be generated at all. Only if YES
(default), Xcode will generate a header map file and pass it as argument to the compiler.
In case a header is not listed in a header map file or header maps aren't used, the compiler will search for the headers using one of two search strategies:
In case the header is imported with <...>
, it will search in all directories specified with the -I
option (HEADER_SEARCH_PATHS
), all directories specified with the -isystem
option (SYSTEM_HEADER_SEARCH_PATHS
in Xcode), and in the stanadrd system header directories (/usr/include
of the selected SDK and other directories belonging to the installed developer tools); in exactly that order and within each category in the order given.
In case the header is imported with "..."
, it will search in the same directory as the .c/.m file being built, in all directories specified with the -iquote
option (USER_HEADER_SEARCH_PATHS
in Xcode), and in the same directories it searches for <...>
; in exactly that order and within each category in the order given.