Powered by Mac OS X Server

Step into Xcode:
Mac OS XDevelopment

Fritz Anderson

Part II
Xcode Tasks

Chapter 20 – Xcode for Make Veterans

This chapter is for experienced Unix programmers who are accustomed to controlling application builds with a dependency manager such as GNU make. As an integrated development environment, Xcode is at first glance far removed from the tools you are used to. But Xcode is not a tightly integrated tool set like Metrowerks’s CodeWarrior. The editor, the build system, and some convenience services run as part of Xcode’s process, but for preprocessing, compilation, assembly, and linkage, Xcode is a front for gcc and other command-line tools. You may feel your builds have been sealed away from you in a black box; in this chapter, I hope to open the box for you a little.

A makefile is organized around a hierarchy of goals. Some goals are abstract (like the frequently used clean or install targets), but most are files. Associated with each goal is a list of other goals that are antecedents—dependencies—of that goal, and a script for turning the antecedents into something that satisfies the goal. Most commonly, the antecedents are input files for the programs that the script runs to produce a target file. Make’s genius comes from the rule that if any target is more recently modified than all of its antecedents, it is presumed to embody their current state, and it is not necessary to run the script to produce it again. The combination of a tree of dependencies and this pruning rule make make a powerful and efficient tool for automating tasks like building software products.

The organizing unit of a makefile is the target-dependency-action group. But often, in the case of application development, this group is stereotyped to the extent that you don’t even have to specify it—make provides a default rule that looks like

    %.o     :   %.c
    $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<

...so all the programmer need do is list all the constituent .o files in the project, and the built-in rule will produce the .o files as needed. Often, the task of maintaining a makefile becomes less one of maintaining dependencies than of keeping lists.

In the same way, Xcode makes dependency-analysis a matter of list-keeping by taking advantage of the fact that almost all projects are targeted at specific kinds of executable products, such as applications, libraries, tools, or plugins. Knowing how the build process ends, Xcode can do the right thing with the files that go into the project.

A file in the Groups & Files list of Xcode is a member of three distinct lists.

When you create a target, either in the process of creating an Xcode project, or by adding a target to an existing project, you specify what type of product you want to produce, and you can’t change that type except by making another target. The target type forms one anchor—the endpoint—in the Xcode build system’s dependency analysis: It tells the build system what the desired structure (single-file or package) of the product is, and how to link the executable.



Figure 20.1: Build phases in a modest project. You gain access to build phases by opening the disclosure triangle next to the Targets group in the Groups & Files list, and then opening the triangle for the target of interest. Each phase, in turn, contains the files that belong to it. Files can be added to a phase by dragging, or removed by selecting them and pressing delete.

The other anchor of the build system is the set of build-phase members for the target. The Compile Sources build phase, plus the sources you add to it, yield object files, which are the inputs to the executable-linkage phase implicit in your choice of target type. The various file-copying phases, plus the files you supply for them, yield the copy commands needed to populate the auxiliary structure of an application.

20.1 Xcode Build Variables

The action for the default make rule for .c files parameterizes almost the entire action. The command for the C compiler and the set of flags to pass are left to the makefile variables CC, CPPFLAGS, and CFLAGS. You set these at the head of the file to suitable values, and all the compilations in your build comply.

Xcode relies similarly on variables to organize build options, but at a much finer granularity. There is one variable for each of the most common settings. For instance, the variable GCC_ENABLE_CPP_RTTI controls whether gcc’s -fno-rtti will be added, to suppress generation of runtime type information. This variable is set by a check box in the Build tab of the Get Info window for the target.

In the Groups & Files list of any Xcode project, find the Targets group, and click on the disclosure triangle next to it to open the group and reveal the contents. Double-click on one of the targets inside (or the only target). This should reveal the Get Info window for the target as shown in Figure 20.2. Click on the Build tab if it isn’t already selected.



Figure 20.2: Finding a specific gcc option in a target’s settings. Open the Targets group in the Groups & Files list, and double-click on a target to open the Info window. The Build tab lists most of the settings for which Xcode maintains build variables.

The list you see is a front end for most of the build variables Xcode maintains for this target. Click on an item; the text area at the bottom of the Info window fills with text describing what that item does. In brackets, and usually at the end of the description, are the name of the build variable the item controls, and what gcc option (if any) it affects. Both the label and the description are searchable: The list in Figure 20.2 was narrowed down to four entries by typing “runtime” into the search field at the top of the window.

20.2 Custom Build Rules

Xcode’s build system can be extended to new file types and processing tools. The default rules in the build system match file extensions to product types, and process any source files that are newer than the products. You can add a custom rule that instructs the build system to look for files whose names match a pattern, and apply a shell command to such files.



Figure 20.3: A custom build rule. This rule captures all files in the target that have the suffix .lemon, passes them to the lemon parser generator, and if that step succeeds, moves the product files to the project’s Derived Sources subdirectory. The rule specifies the form of the output file’s path so the build system knows whether, and when, to trigger the rule.

Create a rule by double-clicking a target icon in the Groups & Files list, selecting the Rules tab, and pressing the plus button. A “slot” will be added to the top of the list that has two popup menus completing the sentence “Process...using....” For a custom rule, you select Source files with names matching: and Custom script:. This will open a text field for the matching pattern, into which you will type the matching pattern (a glob pattern like *.lemon), and a second field for the single shell-script line to process the matched file. Don’t worry about the size of this field; it will grow vertically as you type. Remember also that you can chain shell commands with the && operator.

You may use any build variable you like in the shell command. Additionally, there are some variables that are specific to custom rule invocations:

INPUT_FILE_PATH
The full path to the source file (/Users/xcodeuser/MyProject/grammar.lemon).
INPUT_FILE_DIR
The directory containing the source file (/Users/xcodeuser/MyProject).
INPUT_FILE_NAME
The name of the source file (grammar.lemon).
INPUT_FILE_BASE
The base (un-suffixed) name of the source file (grammar).

Apple recommends that intermediate files—like the source files output by parser generators—be put into the directory named in the DERIVED_FILE_DIR variable. The build system is supposed to detect the type of such files and process them accordingly; however, as I write this, I find that the .m file I generated with the *.lemon rule shown in Figure 20.3 was copied to my application’s resources directory, instead of being compiled. If you’re not satisfied with how your version of Xcode treats your intermediate files, make sure they are sent to SRCROOT (the same directory as your project, which should be the working directory for the rule’s command), and include them as regular source in your target. This will mean you’ll have to generate one pass of the intermediate files by hand.

20.3 Run Script Build Phase

You can add arbitrary script actions to a build by adding a Run Script build phase. Select ProjectNew Build PhaseNew Shell Script Build Phase, and you will be presented with an editor into which you can type commands in the scripting language of your choice (Figure 20.4).



Figure 20.4: The Run Script build phase window. You can specify any language you could use in an executable script file. By adding files to the input and output files lists, you can have the Xcode build system run the script only when its products are out-of-date.

A Run Script phase can have specific files as inputs and outputs. If these are present, they will be taken into account by Xcode’s build system—if all the outputs are newer than all the inputs, the phase is skipped. If outputs are unspecified, the phase is always executed. The sequence in which a Run Script phase is executed is determined by the availability of its inputs and the need for its outputs.

When a Run Script build phase is executed, the script is copied to a temporary directory, and a series of setenv commands copy build variables into the environment. The script is then run, with your permissions, with the project directory current, with the input files passed in as parameters.

There are two caveats about build variables and Run Script phases. First, you can’t change them: They are passed to your script by doing a setenv for each variable before the script is invoked. When the script finishes, those environment variables, along with any changes you might have made to them, go out of scope.

Second, you don’t get all of the build variables. Apple’s documentation suggests that variables that set options specific to a single tool (such as the variables that begin with GCC_) are not exported to the Run Script environment. At present, such variables are exported, if they have been changed from their default values by the current build configuration. So if you have a script that sets up for code-coverage tests when the code-coverage flag (GCC_GENERATE_TEST_COVERAGE_FILES) is set, you will have to test whether that symbol is present in the environment (and know what its absence means) before using its value.

A cautious user might take Apple’s documentation to mean that the visibility of GCC_, and similar, variables is not guaranteed in future releases.

20.4 Under the Hood: A Simple Build

The Xcode build system—to repeat—is just a front for gcc and other command-line tools. Xcode’s dependency analysis identifies what tasks need doing, and issues high-level commands to accomplish those tasks. The high-level commands, in turn, map to shell commands, the execution of which Xcode monitors for warnings and errors.

The Build Results window, accessible through the BuildBuild Results menu command, shows the progress of a build, and a table of the results. By clicking on the third icon to the bottom-left of the results list, you can open an additional panel in the Build Results window, containing a transcript of the build commands, and the literal console output from the tools Xcode invokes.

In this section, we’ll sample the build transcript of a Core Data-using program as it is built from a clean start for both PowerPC and Intel architectures. The transcript will be heavily edited for space and readability; the line breaks (\) and ellipses (...) don’t appear in the original.

Native Build of Target "PktReader" 
   using Build Configuration "Release"

This build will use the Release configuration, rather than the Debug configuration. The Debug configuration for this target does not take the extra time to build for both architectures, because ZeroLink would make the debug version runnable only on the build machine, and the build machine can run only the native-architecture version.

Copy Structural Files

The first thing that happens in a clean build is that Info.plist and PkgInfo, two files you may not have directly edited, but which must be present in any application package, are copied from intermediate storage to their places in the application package. This step doesn’t correspond to any build phase in the target, but is performed when needed.

Here we see the operation for Info.plist. Internally, Xcode denotes this step as a PBXCp high-level operation; this expands to the actual shell commands that create the destination directory, make the project directory current, and perform the copy. pbxcp (for Project Builder-X cp) is a private variant of the cp tool. It offers options the regular command does not, such as the ability to exclude SCM directories from copy operations.

PBXCp build/Release/PktReader.app/Contents/Info.plist \
 build/PktReader.build/Release/PktReader.build/Info.plist
 
    mkdir build/Release/PktReader.app/Contents
    cd /Users/fritza/Projects/PktReader
    /.../pbxcp -exclude .DS_Store -exclude CVS -exclude .svn \
       -strip-debug-symbols -resolve-src-symlinks \
       build/PktReader.build/Release/PktReader.build/Info.plist \
       build/Release/PktReader.app/Contents

The build transcript shows the high-level commands flush-left, with the actual commands that implement them indented in the lines that follow.

Copy Bundle Resources

The next series of high-level commands, CpResource, are issued in response to your explicit request, once for each file or directory in the Copy Bundle Resources phase. In this example, the file WeatherStation.plist gets copied into the application’s Resources directory:

CpResource 
 PktReader.app/Contents/Resources/WeatherStation.plist \
 WeatherStation.plist
 
    mkdir build/Release/PktReader.app/Contents/Resources
    cd /Users/fritza/Projects/PktReader
    /.../pbxcp -exclude .DS_Store -exclude CVS -exclude .svn \
       -strip-debug-symbols -resolve-src-symlinks \
       WeatherStation.plist \
       build/Release/PktReader.app/Contents/Resources

Compile Sources (C/C++/Objective-C)

The build system then turns to producing executable binaries. The first task is to produce a precompiled header for each architecture the binary is to be runnable on. The process of building a universal binary is mostly the same as building stand-alone binaries for each architecture separately, and then combining them into a “fat” binary file. Because we’ll be compiling once for each of two architectures, we need two precompiled headers.

Precompiled headers are kept in a subdirectory of /Library/Caches. The high-level command ProcessPCH specifies what precompiled header file to create, from what prefix (.pch) file, using what dialect and which parser. It maps to commands that ensure the cache directory exists, an environment-variable setting, and an invocation of gcc.

ProcessPCH /Library/Caches/.../PktReader_Prefix.pch.gch \
 PktReader_Prefix.pch normal ppc objective-c \
 com.apple.compilers.gcc.4_0
 
    mkdir /Library/Caches/...
    cd /Users/fritza/Projects/PktReader
    setenv MACOSX_DEPLOYMENT_TARGET 10.4
    /usr/bin/gcc-4.0 -x objective-c-header -arch ppc -pipe \
       -Wno-trigraphs -fobjc-exceptions -fpascal-strings \
       -fasm-blocks -Os -Wreturn-type -Wunused-variable \
       -fmessage-length=0 -mtune=G5 -fvisibility=hidden \
       -Ibuild/PktReader.build/Release/.../PktReader.hmap \
       -mdynamic-no-pic -Fbuild/Release -Ibuild/Release/include \
       -Ibuild/PktReader.build/Release/.../DerivedSources \
       -isysroot /Developer/SDKs/MacOSX10.4u.sdk \
       -c PktReader_Prefix.pch \
       -o /Library/Caches/.../PktReader_Prefix.pch.gch

ProcessPCH /Library/Caches/.../PktReader_Prefix.pch.gch \
 PktReader_Prefix.pch normal i386 objective-c \
 com.apple.compilers.gcc.4_0
 
    mkdir /Library/Caches/...
    cd /Users/fritza/Projects/PktReader
    setenv MACOSX_DEPLOYMENT_TARGET 10.4
    /usr/bin/gcc-4.0 -x objective-c-header -arch i386 -pipe \
       -Wno-trigraphs -fobjc-exceptions -fpascal-strings \
       -fasm-blocks -Os -Wreturn-type -Wunused-variable \
       -fmessage-length=0 -fvisibility=hidden \
       -Ibuild/PktReader.build/Release/.../PktReader.hmap \
       -mdynamic-no-pic -Fbuild/Release -Ibuild/Release/include \
       -Ibuild/PktReader.build/Release/.../DerivedSources \
       -isysroot /Developer/SDKs/MacOSX10.4u.sdk \
       -c PktReader_Prefix.pch \
       -o /Library/Caches/.../PktReader_Prefix.pch.gch

Once the precompilation is done, Xcode’s build system runs through every C-family source file in the Compile Sources build phase, and compiles them for the first architecture:

CompileC build/.../ppc/PacketInterpreter.o PacketInterpreter.m \
 normal ppc objective-c com.apple.compilers.gcc.4_0
 
    mkdir build/.../ppc
    cd /Users/fritza/Projects/PktReader
    setenv MACOSX_DEPLOYMENT_TARGET 10.4
    /usr/bin/gcc-4.0 -x objective-c -arch ppc -pipe \
       -Wno-trigraphs -fobjc-exceptions -fpascal-strings \
       -fasm-blocks -Os -Wreturn-type -Wunused-variable \
       -fmessage-length=0 -mtune=G5 -fvisibility=hidden \
       -Ibuild/PktReader.build/Release/.../PktReader.hmap \
       -mdynamic-no-pic -Fbuild/Release -Ibuild/Release/include \
       -Ibuild/PktReader.build/Release/.../DerivedSources \
       -isysroot /Developer/SDKs/MacOSX10.4u.sdk \
       -include /Library/Caches/.../PktReader_Prefix.pch \
       -c PacketInterpreter.m \
       -o build/.../ppc/PacketInterpreter.o
 PacketInterpreter.m: In function '-[PacketInterpreter
                                              fillWeather]':
PacketInterpreter.m:59: warning: local declaration of 'origin'
                                         hides instance variable

The high-level CompileC command breaks down into a mkdir for the intermediate-product directory for the current architecture, a cd to make sure the project directory is current, a setenv to designate the target version of Mac OS X, and finally the invocation of gcc.

I chose as a representative sample a file that produced a warning. Xcode reads error and warning text directly from gcc’s standard-error stream, and interprets it. In the build transcript, the standard error text is shown in italics; the text of warnings and errors is collected and put into the list in the Build Results window, into the tooltips for the error and warning badges in the gutter next to the offending lines, and into the status bar when a badge is clicked.

Xcode is generally good at parsing gcc’s error messages, but not every message passes intelligibly through to the IDE. Undefined and multiply-defined symbols detected at link time, in particular, are reported in the IDE without the part of the message that says what the offending symbols are. If you’re ever in doubt as to what an Xcode error message means, looking at the build transcript for gcc’s exact output may clear things up.

Linkage

Ld build/.../ppc/PktReader normal ppc

    mkdir build/.../ppc
    cd /Users/fritza/Projects/PktReader
    setenv MACOSX_DEPLOYMENT_TARGET 10.4
    /usr/bin/gcc-4.0 -o build/.../ppc/PktReader -Lbuild/Release \
       -Fbuild/Release \
       -filelist build/.../ppc/PktReader.LinkFileList \
       -framework Cocoa -framework IOKit -arch ppc \
       -isysroot /Developer/SDKs/MacOSX10.4u.sdk -Wl,\
       -syslibroot,/Developer/SDKs/MacOSX10.4u.sdk

The Ld high-level command links the compiled objects with the libraries and frameworks designated in the Link Binary With Libraries build phase. In this case, the -framework option is used to link the Cocoa and IOKit frameworks. The object files aren’t listed to the gccinvocation, but are drawn via a -filelist option from a file Xcode generates in the intermediate-products directory.

Compile Sources (Second Architecture)

Because this target is destined for both PowerPC and Intel architectures, the compilation and linkage phases have to be done all over again, the only differences being the -arch i386 option passed to gcc, and the use of an i386 intermediate-products directory:

CompileC build/.../i386/PacketInterpreter.o PacketInterpreter.m \
 normal i386 objective-c com.apple.compilers.gcc.4_0
 
    mkdir build/.../i386
    cd /Users/fritza/Projects/PktReader
    setenv MACOSX_DEPLOYMENT_TARGET 10.4
    /usr/bin/gcc-4.0 -x objective-c -arch i386 -pipe \
       -Wno-trigraphs -fobjc-exceptions -fpascal-strings \
       -fasm-blocks -Os -Wreturn-type -Wunused-variable \
       -fmessage-length=0 -fvisibility=hidden \
       -Ibuild/PktReader.build/Release/.../PktReader.hmap \
       -mdynamic-no-pic -Fbuild/Release -Ibuild/Release/include \
       -Ibuild/PktReader.build/Release/.../DerivedSources \
       -isysroot /Developer/SDKs/MacOSX10.4u.sdk \
       -include /Library/Caches/.../PktReader_Prefix.pch \
       -c PacketInterpreter.m \
       -o build/.../i386/PacketInterpreter.o
 PacketInterpreter.m: In function '-[PacketInterpreter
                                              fillWeather]':
PacketInterpreter.m:59: warning: local declaration of 'origin'
                                         hides instance variable

Linkage (Second Architecture)

Ld build/.../i386/PktReader normal i386

    mkdir build/.../i386
    cd /Users/fritza/Projects/PktReader
    setenv MACOSX_DEPLOYMENT_TARGET 10.4
    /usr/bin/gcc-4.0 -o build/.../i386/PktReader \
       -Lbuild/Release -Fbuild/Release \
       -filelist build/.../i386/PktReader.LinkFileList \
       -framework Cocoa -framework IOKit -arch i386 \
       -isysroot /Developer/SDKs/MacOSX10.4u.sdk -Wl,\
       -syslibroot,/Developer/SDKs/MacOSX10.4u.sdk

Create Universal Binary

The two linkage phases produced separate binary files, in the respective intermediate-products directories, for Intel and PowerPC. The delivery format for binaries compatible with both architectures is a single file containing both versions of the binary. This is known to Apple marketing as a “universal binary,” and for historical purposes as a “fat binary.” The high-level command CreateUniversalBinary uses the lipo tool to assemble the binary in its final form and position in the application bundle:

CreateUniversalBinary 
 build/Release/PktReader.app/Contents/MacOS/PktReader \
 normal "ppc i386"
 
    mkdir build/Release/PktReader.app/Contents/MacOS
    cd /Users/fritza/Projects/PktReader
    /usr/bin/lipo -create build/.../ppc/PktReader \
       build/.../i386/PktReader \
       -output \
       build/Release/PktReader.app/Contents/MacOS/PktReader

Compile Data Models

This application makes use of a Core Data managed object model drawn from a .xcdatamodel data model created in Xcode. Xcode data models are not directly usable by Core Data, but must be compiled into .mom managed-object model files. This is done by the momc compiler embedded in Xcode’s data-modeling plugin.

Surprisingly, this is done twice, once for each architecture. In each case the resulting .mom file, with the same name, is copied to the same destination in the application bundle.

The Xcode IDE lists .xcdatamodel files in the Compile Sources build phase.

DataModelCompile build/.../ppc/PktReader_DataModel.mom 
 PktReader_DataModel.xcdatamodel
 
    mkdir build/.../ppc
    cd /Users/fritza/Projects/PktReader
    /.../XDCoreDataModel.xdplugin/Contents/Resources/momc \
       PktReader_DataModel.xcdatamodel \
       build/.../ppc/PktReader_DataModel.mom

DataModelCompile build/.../i386/PktReader_DataModel.mom 
 PktReader_DataModel.xcdatamodel
 
    mkdir build/.../i386
    cd /Users/fritza/Projects/PktReader
    /.../XDCoreDataModel.xdplugin/Contents/Resources/momc \
       PktReader_DataModel.xcdatamodel \
       build/.../i386/PktReader_DataModel.mom

PBXCp PktReader.app/Contents/Resources/PktReader_DataModel.mom \
 build/.../ppc/PktReader_DataModel.mom
 
    mkdir build/Release/PktReader.app/Contents/Resources
    cd /Users/fritza/Projects/PktReader
    /.../pbxcp -exclude .DS_Store -exclude CVS \
       -strip-debug-symbols -resolve-src-symlinks \
       build/.../ppc/PktReader_DataModel.mom \
       build/Release/PktReader.app/Contents/Resources

PBXCp PktReader.app/Contents/Resources/PktReader_DataModel.mom \
 build/.../i386/PktReader_DataModel.mom
 
    mkdir build/Release/PktReader.app/Contents/Resources
    cd /Users/fritza/Projects/PktReader
    /.../pbxcp -exclude .DS_Store -exclude CVS \
       -strip-debug-symbols -resolve-src-symlinks \
       build/.../i386/PktReader_DataModel.mom \
       build/Release/PktReader.app/Contents/Resources

Finishing Touch

Finally, the application package directory is touched to make its modification date match the end of the build process, which is what it intuitively ought to be.

Touch build/Release/PktReader.app

    mkdir build/Release
    cd /Users/fritza/Projects/PktReader
    /usr/bin/touch build/Release/PktReader.app

20.5 The xcodebuild Tool

There are times when there is no substitute for a command-line tool. The Unix command line presents a well-understood interface for scripting and controlling complex tools. Apple has provided a command-line interface to the Xcode build system through the xcodebuild tool. Using xcodebuild is simple: set the working directory to the directory containing an .xcodeproj project package, and invoke xcodebuild, specifying the project, target, configuration, and any build settings you wish to set. If there is only one .xcodeproj package in the directory, all these options can be defaulted, and simply entering

$ xcodebuild

will build the project’s current target in its default configuration. Apple’s intention is that xcodebuild should have the same role in a nightly-build or routine-release script that make would have.

Building a target one of five actions you can specify for xcodebuild:

build
(Default) Builds the specified target, out of SRCROOT into SYMROOT/CONFIGURATION. This is the same as the Build command in the Xcode application.
clean
Removes from SYMROOT the product and any intermediate files. This is the same as the Clean command in the Xcode application.
install
Builds the specified target, and installs it at INSTALL_DIR (usually DSTROOT/INSTALL_PATH).
installsrc
Copy project source to SRCROOT.
installhdrs
Copy headers to their installed location.

If there is more than one .xcodeproj package in the current directory, you must specify which one you are interested in, with the option -project, followed by the name of the project. Not specifying a target is the same as supplying the -activetarget option; you can also specify -alltargets or -target followed by the name of the target you want to build. It’s a little different with configurations: The absence of a specification selects the default configuration; -activeconfiguration uses, as you would imagine, the active configuration for the selected project; and -configuration followed by a configuration name selects that configuration.

20.6 Settings Hierarchy

Build settings in Xcode can be set at any of four (or in the case of xcodebuild, five) layers in a hierarchy. A setting may be made

Settings in each layer override the settings from the ones below it, as shown in Figure 20.5.



Figure 20.5: The hierarchy of build settings in Xcode and xcodebuild. A setting may be made at one or more of these layers, but it is the topmost setting in the hierarchy that controls. Settings in higher layers may refer to settings from lower layers by the variable reference $(VALUE). The top layer, command-line settings, is present only in an xcodebuild invocation.

For example, consider the build variables ZERO_LINK and GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS. By default, ZERO_LINK is 0, and GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS is empty. In Figure 20.6, ZERO_LINK defaults to NO in Xcode, and the Release configuration doesn’t change that at either the project or the target level; but the project setting for the Debug configuration sets ZERO_LINK to YES, and that is the value used when the Debug configuration is active. GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS is empty so far as Xcode is concerned, and at the project level, the policy is to set DEBUG_LEVEL to 2 for Debug builds and to 0 for Release builds. But for this target only, the developer decides to set DEBUG_LEVEL to 1 for Release builds.



Figure 20.6: The Build-settings hierarchy in action.

The result is, when the Debug configuration is selected, ZERO_LINK will be set to YES, and GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS will be DEBUG_LEVEL=2; when Release is selected, ZERO_LINK will be NO, and GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS will be DEBUG_LEVEL=1.

20.7 Build Configurations

It’s usual in a software development project that you don’t want to use the same build settings for your day-to-day development as you do for releases of the product. In a development build, you’ll want symbols #defined that unmask debugging code, and compiler switches that suppress optimization and generate debugging symbols, but these settings would hurt performance and product size in a release build.

In a makefile, you’d take care of this need by drawing your compiler and linker flags from variables, and setting the variables according to the intended use of the build—CFLAGS, for instance, might include -g -O0 for a working build, and -Os for release.

In Xcode, you can organize groups of build variable settings by purpose, using the build-configuration feature. When a new Xcode project is created, it contains three configurations, Debug, for quick turnaround and easy debugging; Release, for optimal size and performance; and Default, which makes no changes (other than settings that name the product and identify its type) to the default values Xcode sets. The Default settings will yield an even more release-oriented product than the Release configuration, in that function symbols will be stripped, and the build product will be placed in an installation directory instead of the project’s build subdirectory.

You can review the configurations in a project by double-clicking the project icon at the top of the Groups & Files list (or selecting ProjectEdit Project Settings) and selecting the Configurations tab. See Figure 20.7.



Figure 20.7: The project configuration list, showing the three standard configurations, Debug, Release, and Default, in an Xcode project. You can add configurations of your own by selecting a configuration you want to start from, and pressing the Duplicate button. You can also delete or rename the selected configuration. The default configuration, selected with the popup menu at the bottom of the window, is the configuration xcodebuild will use for builds for which you do not specify a configuration.

When you edit the settings for a configuration using the Build tab of the project Info window, you are setting a policy for all the targets in your project. For instance, you might want Fix & Continue to be for the Debug configuration, and off for Release. You want this to be the case for everything you build with this project, so you double-click the project icon at the top of the Groups & Files list to open the project Info window and select the Build tab. With the Configuration: popup at the top of the window showing Debug, you check Fix & Continue; you switch the popup to the Release configuration, and make sure Fix & Continue is unchecked.

Notice that the Fix & Continue item label turns to boldface when you set it. When a window contributes a setting to a configuration, the setting’s label is shown in bold. This is an important point: An item may be blank, or unchecked, but its label will be in bold. This does not mean “no setting!” It means that this window affirmatively makes the setting blank or NO. To change a setting to “no setting,” select the setting’s line, and press the delete key. The line will lose its boldfacing, and the value shown will be the default, or inherited, value.

Not every build setting can be left to project-wide policy; some must be set per-target. The obvious cases are things like the names of the targets’ products, but it may be that you intend to use -O3 optimization, for instance, instead of -Os, for a library target. Open the Targets group in the Groups & Files list and locate the target of interest. Double-click on it, and an Info window will appear. Like the project Info window, it, too, will have a Build tab for you to alter build settings.

The Build panel of the target Info window is the place where you can see what settings wlll actually apply to that target in the selected configuration. Settings you make in the target window override the values inherited from the project level and below, but what you see in the target window is the final word.

At both the project and the target levels, you can select from the Configuration: popup menu which configuration your settings apply to. You can apply your settings to all configurations by selecting All Configurations in the popup.

20.8 Summary

This chapter covered Xcode as a build system like make that fronts for command-line tools that consume source files to replace out-of-date product files. We saw how to customize the build process by adding build rules and inserting shell-script build phases. We also discussed how build configurations can apply packages of build-time environment variables to a project.


This document was translated from LATEX by HEVEA.