script

You are currently browsing articles tagged script.

There are a few annoying bugs when using sub-projects in Xcode that build dependencies for the “master” project, particularly for frameworks or shared libraries that have header files and that need to be embedded in the final product (built by the “master” project). This is the case for MHKKit and Riven X.

My solution to those problems has been for a while to set a common build directory for my entire user. This preference is set in the Xcode preferences. Having a single build directory has quite a number of advantages (one-shot delete of temporary build files, Spotlight ignore for faster build times, one directory to go to for Terminal sessions, one directory to add to dyld environment variables, etc). It also happens to fix all sub-project related issues, because every project suddenly has the same build directory.

However, a project should not assume that this will be the case. Most external contributors will not have set that Xcode preferences, or they may have a good reason for not doing so. In any case, a project should always build successfully no matter the specific environment, given a sane Xcode tools installation.

So let’s look at some of the more annoying issues, and how we can resolve them in a manner that works in both situations (common build directory or not).

Framework header files

Despite the sub-project properly added to the master project, and even though linking phases will properly resolve the path of any shared library or framework reference from an sub-project, including framework header files will fail. This comes as a bit of surprise, because adding a framework as a regular project item, no matter where it is on disk, will allow framework includes to work for that particular framework.

The solution to this problem is of course to specify the sub-project’s build directory in the framework search path. An additional twist is to use Xcode build setting variables to handle all configurations at once. One does need to assume certain things about the sub-project’s location and build settings, but for the common case of having the sub-project in a sub-directory below the “master” project, my solution will do the job.

The generic pattern is something like this:

FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/SUB-PROJECT-DIRECTORY-NAME/build/$(CONFIGURATION)";

where SUB-PROJECT-DIRECTORY-NAME is the name of the sub-directory containing the sub-project. As an example, Riven X’s Base.xcconfig contains the following:

FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/mhk/build/$(CONFIGURATION)";

Although I have no experience with shared library include files, I also strongly suspect including them will fail. Their case is a bit different, considering the header files will not be bundled, but the gist of the problem remains the same. Adapting my solution for frameworks to shared library is a (trivial) exercise for the reader.

Copying sub-project build products

For some unknown reason (and most likely not a very good one), using a sub-project build product reference in a copy phase in the “master” project will fail. The Xcode build system construct an incorrect path, assuming the referenced build product is in the “master” project’s build directory.

The solution is to use a shell script to copy that build product. As an additional requirement, I wanted to use Xcode’s copy tool (pbxcp) in exactly the same way as the build system does. The script makes the same assumption as the solution for framework header files.

First, the code. I’m not going to make it generic, so this is for copying MHKKit to the Riven X bundle. You can of course copy more than one item per script invocation with proper modifications. On with the code:

# Find the pbxcp tool
COPY_TOOL="${DEVELOPER_LIBRARY_DIR}/PrivateFrameworks/DevToolsCore.framework/Resources/pbxcp"
if [ ! -f "${COPY_TOOL}" ]; then
	COPY_TOOL="/System/Library/PrivateFrameworks/DevToolsCore.framework/Resources/pbxcp"
fi

# Find the build product
MHKKIT_PATH="${BUILT_PRODUCTS_DIR}/MHKKit.framework"
if [ ! -d "${MHKKIT_PATH}" ]; then
	# assume default Xcode setup where MHKKit was built in its own build directory
	MHKKIT_PATH="${SRCROOT}/mhk/build/${CONFIGURATION}/MHKKit.framework"
fi

# Copy the build product
$COPY_TOOL -exclude .DS_Store -exclude CVS -exclude .svn -resolve-src-symlinks "${MHKKIT_PATH}" "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"

The script first finds pbxcp (in a way that will work on both 10.4 with Xcode 2.4 and above, and 10.5 with Xcode 2.5 and 3.0). It then assumes the build product (in this case, “MHKKit.framework”) is in the “master” project build directory. If that turns out not to be the case, it assumes it is in the default build directory of the sub-project (in this case, the MHKKit project) for the active configuration. It finally invokes pbxcp to copy the build product to the appropriate location (in this case, the “Frameworks” directory of the Riven X application bundle).

For a list of build setting variables for common bundle locations, see Xcode Build Setting Reference, specifically the “Product Layout Build Settings” category.

Tags: ,