Tools • 1:05:15
Beneath the surface of Xcode lies a powerful, highly configurable build system. Understand how to configure Xcode build settings at the project, target, and file level. Learn best practices for structuring complex projects to achieve blazing fast build times on your multi-core Mac. See how to select the right compiler for your project. Get the most out of the tools you use every day.
Speaker: Chris Espinosa
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it may have transcription errors.
To understanding the Xcode project management and build system session 919, if this is not the flight you plan to take, we recommend you deplane now. I'm Chris Espinosa. I'm the manager of the Xcode Core Technologies Group. We have a fairly short agenda for you today, long on content, short on bullet items. I'm going to start with a brief review of the Xcode build process and the Xcode project architecture. Those of you who are old hands at Xcode or have been using Xcode 3.1 for a while may wish to just, you know, Twitter your disrespect for a little while and sit it out until the interesting part comes. Those of you who are new to Xcode, this is a really important part of the presentation because it'll get us all on some common terminological grounds. People coming from different traditions and different environments may mean completely different things when I say target or project or build setting or environment variable. I'm going to try to get us on some common ground so when we get into the deep water, you'll understand what I'm talking about. Second is we'll do some what's new in Xcode 3.1. Those of you who have the Xcode 3.1 seed, there are a lot of these that are covered in the release notes. but there's nothing like seeing it done live on stage in front of you to show you what the power of the new features really is. And then we'll go into some advanced techniques. The main technique I'm going to be demonstrating and describing in some detail is how to use cross-project references, how to cause one project to invoke a build in another project, and to use its build products in order to create a larger one, how to chain multiple projects together. We'll give a couple of demos, and then we'll take questions and have a lot of time for questions, okay?
Let's start with the brief review. When I say project, I mean the Xcode project, the.xcode proj file that you double-click to open an Xcode project. Inside the project are two things, targets and content. And the targets are an organizing principle for the rest of the content. When you build, you don't build a project. You build a target. That's an important distinction.
People say, I want this project to build this other project. And I say, wait. If you're saying that, you don't have it straight in your head. A target is what is built. A project is simply a container that holds targets. The target is a collection of inputs, source files, resource files, and instructions on how to build those inputs to produce a build product. I'm going to use that term a lot, build product. That's what it's all about. Every target builds a build product, and it builds it from source files, it builds it using rules, it builds it using build settings.
The targets, the content is organized in a target in a set of build phases, and each build phase handles a particular type of input. There's a compile sources build phase that builds all your source files. There's a copy bundle resources build phase that copies all of the resources in your target into the bundle in the build product. You can create your own build phases to run an arbitrary script in the shell or to perform an explicit copy of other things to other places actions. The build phases are done in a stepwise order depending on both the order you place them in and their dependency requirements.
Things may be done in different order depending upon what depends on what other things. Each target has its own set of build phases. And the contents of one, the build product of one target can be included in a build phase of another target. That's the key to using multiple projects in Xcode.
Like I said, targets have sources, inputs, they have rules, and then they have settings, build settings that tell the compiler, the linker, the resource compiler how to do what it's going to do. And those build settings are really what you spend most of the time adjusting when you're configuring your Xcode project and your Xcode targets. Each target has its own set of build settings and that applies to building that target. At the top level, the project also has a set of build settings that applies generally across all of the targets. And it's a hierarchy, just like object-oriented programming. The project build settings are superior, but the target build settings can override the project build settings. So if you've got something that you want to apply across everything you're going to build, like I want this to build 32-bit or 64-bit. I want this to build debug or not debug. I want this to build my English language version versus some other version. You put that at your project level and all targets inherit it. If you have something that you want to differ from target to target, this target is building a static library so it has this set of settings. This target is building an application, it has different settings. This target is building a shared library, it has different settings. The specialization goes at the target level. Project level general, target level specialization. The number one user error with Xcode is I set this build setting and it didn't take effect.
And that's because, well, you set it at the project level, and you already had an override set at the target level. So if you set a build setting and it's not taking effect, go to your target level and make sure that it's set at the target level. If not, either set it explicitly at the target level, or take that build setting and delete it from the target to let the project level setting shine through. Number one rule.
Now, this is a common mistake as well. People use these terms loosely, project, target, executable, and build product. There's often a one-to-one-to-one-to-one correspondence between these. In fact, when you create a project from the templates, the stationery we provide, you get a project. And in that project is one target. And that one target creates one build product. And that one build product corresponds to an executable that can launch and run and debug that build product. And they all have the same name. So people think they're all the same thing.
But they're not. They're four separate things. The project, the target, the build product that the target builds, and the executable that launches and debugs that are four separate things. And you adjust them differently according to what you want to do. And I think to start with, we'll bring up Michael Rodden and we'll show you the basics of going through building a simple Xcode project to show you what the build is like and what the parts are. So Michael's going to go to the Open Recents menu.
and open up our favorite demo, which is the TextEdit application. This is the code that builds the TextEdit application that's standard on every Macintosh Mac OS X machine. So first we're going to disclose the project folder and take a look at what's inside the project folder. Michael, if you could move it a little bit, the window a little bit to the right, 'cause it's being clipped on the screen. There we go. So you see inside the project, the top level project object, are all of the files within the project. And if you collapse that-- actually, if you scroll down, down at the bottom, there's a folder called Products. You open that up. And that is a proxy, a representative of what our final build product is going to be, TextEdit.app.
That's what we intend to build. Now I want to show you how we're going to build that. The way we build it is that in this project, there is a target, one target called TextEdit. At TextEdit is represented with, you know, an a kind of A with a green check mark. The green check mark means it's the active target.
It's what's going to be built when I click the build button. If you disclose that target and open it up, you see it's build phases. It compiles sources, it copies bundle resources, It links the binaries with the libraries, and it even runs a little script at the end. Each one of those is a step with certain inputs.
You can see the inputs by disclosing, for example, the compile sources. It says it compiles 12 sources. Those are the 12 source files it compiles. Okay? So this is the structure of a project in the build system. Now, to adjust the build settings in an Xcode project, you double-click the target. And this is the table of build settings for that. It's a rather long table. If you scroll it down, you'll see that there are several hundred options that you can set for all kinds of things going on in that target. And that's why it's useful to have a little filter bubble at the top where you type something and it'll narrow it down to just the ones that you care about.
One of the things that we're going to talk about later is, for example, the deployment target. It's which version of the operating system. So rather than search through it, why don't you just type DEPL? Oh, that narrows it down a little bit. Deployment target, there we go, compiler default. It's in bold because it's defined at this level, and by compiler default, that means it's going to be whatever it is for this operating system. We're running on 10.5 here, so it means we're going to build for 10.5.
Now, let's close that and go look at the executable. When we build the target, it creates the build product, and then it'll create, and then if we want to launch or debug it, we control that with this executable. You double-click that, and you get a panel with some tabs for arguments and debugging. And you can see in arguments, for example, if you want to add arguments to be passed at the command line, if you have argc and argv in your main, that's where you pass those. If you're looking at environment variables, and you want to set them, you set them there. So these are how you control how the application is launched after it's built. But you can see that the project, the target, the build product, and the executable that runs it are four separate things. Okay, now we're going to start building it. So you open up the build results window. Okay. from that menu which is way up there in the ceiling. And this is the build results window, and it's empty because, well, we haven't built anything. So first thing we're going to do is just click build.
And you can see that each of our build steps-- oh, you didn't have that checked. OK. Want to clean it? Oh, there we go. You can see each of the build steps Each build phase has a collection of steps and each step is listed there. And some of them have warnings. That's fine. It's not our code.
Each one of those lines has behind it a whole lot of what went on to apply for that input to that target, the settings in that target's build settings inspector to that operation. And we're going to show you how to do it. Open up a text edit window. There we go. We happen to have one handy. And just drag and drop one of those compiling lines into it.
So that's the text where if you were in a make file or if you were executing this from the command line, this is the command that you'd issue to the GCC compiler to compile that line. It's a whole lot of stuff going on and it's mainly providing the header and library search paths, the framework link paths, the build settings, the warnings, the optimizations, all of the architectures, all the controls you want. If you've got something wrong with your build, If you don't know what's going on, go look at this, because all of the information literally is in this line. You may not be able to understand it, but if you've got a build that's working and a build that's not working, compare those two.
The difference will be there, I guarantee you. And if you're filing something on Xcode users at list.apple.com or filing a bug reporter report, this is what we need to tell you what's going on. Now, this is line by line. It could be very tedious to copy everything out. So we have a build transcript hidden under a little button, the third one in. And it shows you the complete build transcript of the entire build. This can be several hundred megabyte of text.
for a long build. But for every step, for every build phase, in every target that you build, the complete and detailed instructions of how it built are here. And if you do the command line Xcode build, this is what you see spewing out at the console, except in Xcode 3.1, we give you a nice summary of what the errors were at the end. That's a nice touch. Okay. So, now, If you look at one of the compile lines, you can see, for example, the, find it in there, Oh, we're using compiler default, so it wasn't set, right? Yeah. So when we set the-- when we showed you the build setting for the deployment target, the value there is passed in-- if we had set it to something, it would be passed in a -mmac-- in a dash, mmechosx version min directive in this command line. So that's how you can make sure that the value that you see in the build setting is being transcribed into an instruction for the compiler by checking this right here, okay? These are some basic things. We'll come back to these later on in the demo. Can I have the slides again, please?
Okay, so we'll come back to the build process after we talk a little bit about what's new in Xcode 3.1 because I want to demonstrate those to you. There's six new things in Xcode 3.1 I wanna talk about. They are all about, they're mainly around the addition of SDKs to support iPhone development, okay? And everything that we're going to talk about and show applies both to Mac OS X development and to iPhone development.
The important difference between Xcode 3.1 and previous versions of Xcode is that the tool chain, the compilers, the linkers, all of the things that are actually used to build code are moving into the SDKs from the native system. The iPhone uses a different chip, and so all of the tools to target the iPhone's chip are actually in the iPhone's SDK, not in Xcode itself. we're making Xcode a much more generalized, portable, pluggable IDE, so that if we come out with a new architecture, with a new platform, with a new chip, and we have a whole new set of compilers, that's just another SDK you drop into Xcode. And we'll show you how we do that.
With the SDKs and the ability to build for multiple SDKs, people want to build an application that works on different platforms and uses different SDKs, so we put the control to switch the SDK right in the user interface for you instead of making you go in and change your project settings by going in there. We've even put it in one giant combo pop-up so that you can change a bunch of settings at once. We have given you much more control about building for a specific architecture right there in the IDE. That does two things.
One is it helps you if you're debugging and you want to debug a particular architecture. Two is it really helps you set up a project that is shared among multiple people. And I have a PowerPC 64-bit machine and you've got a Core 2 Duo machine. And we all want to debug our own thin versions, but we don't want to have to go change the project settings every time in order to optimize that. We've added the ability so that you can make most build settings that matter conditional on both the architecture and the SDK in use. So if you've got a piece of code that builds for both the Mac and the iPhone, you can have different values for a build setting for your iPhone build and for your Mac build without having to change anything. You just set it at once and then it takes effect when it needs to. And finally, because 10.5 and 10.6 in the iPhone SDKs have different selections of frameworks available to you, we've added the ability to declare weak linking against a framework right in the user interface. So you don't have to do some command line mumbo jumbo like you did in the past. Let me go through those.
Those of you who've been with us for a while know that the biggest change with Xcode 2.5 and Xcode 3.0 was to make the developer directory self-contained. Before Xcode 2.5, when you installed the Xcode tools in your system, they went into the root level of the system, and they were there, and they were immutable, and they replaced everything that went there before, and you couldn't use a previous or a later version of Xcode. Starting with Xcode 2.5 on Tiger and Xcode 3.0 on Leopard, We made the developer folders completely self-contained and independent of each other. So you could have Xcode 2.5 and 3.0, for example, side by side on the Leopard machine. And we've even extended that to 3.1.
So you can have on one machine 2.5, 3.0, and 3.1 all together next to each other. And their tool chains and their SDKs and their IDEs and everything is distinct and self-contained. So you launch one Xcode, you get its tools, You launch a different Xcode, you use its tools.
But we've gone a step further with Xcode 3.1. With 3.0, the tools, the compilers and everything, are right there in the top level of the Xcode developer folder. But in Xcode 3.1 and moving on in the future, like I said, we're moving the tools into the SDKs. So there are several SDKs in each developer folder for 10.4 and 10.5 and 10.6. And those nominally contain the headers and the libraries that you use to build for that version of the operating system. But now, they also contain specific tools for the platform that you're targeting. So, for example, the iPhone SDK doesn't just have a library folder with its frameworks in it. It also has user bin and GCC in its own GCC compiler that targets the chip that's in the iPhone. So this is really important because this means as we go further, you're going to be able to add more tools and customize tool chains just by building them into an SDK and dropping it into Xcode rather than waiting for us to do a turn of the Xcode architecture. That's going to be very important for scalability in the future.
SDKs and deployment targets are a really favorite topic and one that's easily confused in Xcode. There are a lot of SDKs now and a lot of different target platforms that you can develop for. And what you want to do when you set up a target in your Xcode project is you want to decide two things. First, what's the earliest version of the OS I want to run on?
to maximize my customer set. We recommend periodically that you just leave 10.1, 10.2, getting on to leaving 10.3 users behind, but you certainly want to, if you're building, for example, for Snow Leopard, you want to consider supporting Leopard and perhaps even Tiger users. That is called your deployment target. You set that in a pop-up in your build setting inspector, you say, if my deployment target is 10.4, my code will work on all 10.4 machines and later. But you have a second decision to make because you don't want to bind yourself to only the features available in 10.4, especially if you're running on 10.5 and 10.6. You may want to say, well, yeah, I will support the Tiger users, but I want to take advantage of these new APIs that are available in Leopard. So then you build against the Leopard SDK.
If you pick an SDK, the deployment target by default is that SDK's version. If you build against the 10.4 SDK by default, you're deploying for 10.4. If you build against the 10.5 SDK by default, you're deploying to 10.5. And of course, an application built for a specific version of the operating system is going to continue to run on later versions of the operating system so far as the operating system supports the chipset and the language and everything that that was written for. But it won't take advantage of the new features. In order to take advantage of the new features of an SDK, you have to build against the SDK for that platform. And then what happens? Well, if I build against the 10.5 SDK to take advantage of 10.5 features, what do I do about the 10.4 users? Well, what happens is that all of the APIs that are not available in 10.4 are not available in 10.4.
When your program loads, they're replaced with null function pointers. And your job at runtime is to determine whether or not the function you want to call is available. And skip around it if it's a null function pointer or not available, and use it if it is. That's the way to write an application that launches on 10.4 and shows 10.4 functionality, but also launches on 10.5 and takes advantage of new 10.5 features, okay?
This is going to be true of the iPhone platforms as well. There is the deployment target setting, which used to be called Mac OS X deployment target, but has been cleverly renamed deployment target, now controls either Mac OS X or iPhone deployment. We just swizzle it to the right value to pass to whichever compiler we happen to be invoking. So if you build with a deployment target for iPhone 2.0 against either the iPhone 2.0 or simulator 2.0 SDK, you will run on 2.0 and whatever later versions. Since we don't really have backwards compatibility to the 1.0 because we don't have a development model for 1.0, we don't have iPhone deployment versions earlier than 2.0 right now. But the same mechanism will take effect. As we add more iPhone SDKs, you'll use that in the same way you're using the Mac OS X deployment target and SDKs.
Now, if you've got multiple SDKs, or if you've got an application that you're building against the 10.4U or 10.5 SDK, and you want to see what it's like on 10.6 Snow Leopard, or if you've got a core piece of functionality that you're building for Mac OS, and you want to say, see, you know, it's just links against, you know, foundation and core foundation and maybe a couple of other frameworks. Can this build for the iPhone? Used to be that you'd have to go into your target settings and change the base SDK and save that and run it and rebuild it. And if you have multiple targets, you'd have to do that for every target. And then you don't want to commit that to your repository 'cause you don't want all of your coworkers to get that.
Right now we have an active SDK pop-up in the toolbar. You can add it to your toolbar, or it's part of the overview toolbar that you can add to your default toolbar. And what this does is this sets a temporal override. This is important. It's a temporal override to the SDK. It does not change your project. Your project settings will still be unchanged. If you close your project, if you commit it to the repository, that won't be changed. It's only a user interface feature that lies to the build system and says, you think you're building with 10.4u, but the user said build with 10.5 just to see. But the effect is that everything will build with 10.5. There's also a value in that pop-up for project settings, which is I'm going to build this project. I want to build it with what it says it wants to be built with. That's a useful thing to do.
The Active SDK is really useful in a try before you buy if you want to see what your project is going to behave like in Snow Leopard or see what your Mac project is like in the iPhone world. It's also the crucial control you use to switch back and forth between building for the iPhone SDK, the iPhone device, and the iPhone simulator. Those are different SDKs. They have different tool chains. They have different platforms. They have different code bases. They actually build for different architectures. So it's important to switch back and forth with that popup in the SDK, rather than have to drill down all the way in the inspector to see what's going on. Okay?
Last topic is weak frameworks. I told you a little bit about what happens if your deployment target is set earlier than your SDK. For any given framework that's on both platforms, any new APIs in the new framework will be replaced with null pointers when you're launching on the older system. That's how you can write one piece of code that launches on both and it takes advantage of new functionality of present and skips around null pointers or crashes, if you don't, on the old system. Well, what about whole new frameworks? What if you want to write something that takes advantage of OpenCL on Snow Leopard, but OpenCL is not available on Leopard? What you do is you bring that framework into your application, but you mark it weak. That means when Xcode goes to link it, it says, I'm gonna link against this, and I'm going to desire it at runtime, but not expect it at runtime. Normally, you build against frameworks, you compile against your headers, you link against them, and when your application goes to load, it has to find those frameworks in order to fix up all of the references. And if a framework is missing, your application will just not launch at all. But if you mark that framework weak when you build it, and you compile against a weak framework, when you launch, if it's missing, your application will launch, but all of the function pointers to it will be null. It's even better than that, because the compiler doesn't expect a framework not to be present, so it's going to assume that those function pointers could never be null, even though there are, so your checks against null function pointers are going to be optimized out of your code. So what you have to do when you're using weak frameworks is you have to do a secondary check.
You have to use a DYLD call, or you have to look at a version number to figure out what system you're running on, whether that framework is present, and jump around large bodies of code, or even conditionally, to even go to a different module or plugin according to what OS you're running on. It gives you the flexibility, but there is a little more work you have to do in your code in order to build against a weak framework.
The way you control weak frameworks is pretty easy. When you add the framework, there's a way to check to say whether it's weak or not. You can go into the link binaries with libraries build phase, select that, you see the detail view. There's a column in the detail view called role. And in that role, you can change any framework linkages to be required or weak according to whether you want it required or weak. Finally, we've made some changes in the way we deal with architectures.
The main issue with architectures is that when you ship products to users, you want them to run on as broad a number of architectures as possible. At least PowerPC and Intel. And if you've got a 64-bit application, you want PowerPC, Intel, and 64-bit PowerPC and 64-bit Intel as well for the Leopard platform.
But building for four architectures means building four times. And it means taking four times as long every time through the debug cycle. And when you're debugging, there are only a very, very, very small number of issues that are going to be architecture dependent. Most of your development time is going to be done on the native architecture of your build machine. So the way we've recommended and the way we've been set up historically is that in your release configuration, the set of build settings for your target for when you're building production, you explicitly say, "These are my four architectures, PPC, PPC64, i386, x86, 64." You hard code that. And that's a problem because if we ever change the architecture set, you have to go change that hard coded list. And then to make your project portable among machines so your development team can work conveniently together, in the debug configuration, the set of build settings that you use when you're in your compile link debug iteration, you set it to a predefined build setting called native arch. And native arch means whatever this machine happens to be. So if you're developing on a PowerPC, you're debugging PowerPC code. If you're working on a MacBook, you're debugging Intel code.
A couple of problems with that is that that doesn't really give you flexibility to say, well, I just want to build thin for this architecture or build thin for that architecture. I want to see what it's like. And also, NativeArch has been historically defined to be the 32-bit equivalent. So if you're writing a 64-bit application, well, this will build your 32-bit version, and that's not very useful. In Xcode 3.1, we have changed the architecture definition and the architecture pop-up. We have defined some nice, readable, instead of that, set of checkboxes that I went through in great details on five slides last year in this session. Do you remember that? We don't have to do that anymore, it's gone. We have a pop-up that says standard 3264 bit or standard 32-bit architectures or native architecture for this machine. And you just pick the one that matters. And if you wanna set your own, you choose other, and then you type in your own, which is really nice, really clean. Standard 3264 bit means, in release configuration, It means PowerPC, PowerPC 64, i386, x86 64. That's great. That's really helpful. It also means the same thing in debug configuration. Your debug configuration has the same architectures as your release configuration. So when you need to change your set of architectures, you just change it in one place across all your configurations, set it and forget it. You don't have to maintain separate architecture lists for separate ones. But does that mean you're going to build four-way FAT for your debug cycle every time? No. Because we have a new build setting that you should set in your debug configuration that says build active architecture only. It's a check box. In any configuration where this check box is checked, when you're set up for that target in that configuration and you click build, it is only going to build the active architecture as set in the active architecture pop-up in the UI. And so if you're working on four-way code, if you want to build everything for Intel 32-bit, you just pick I386 from Active Architecture and build your debug configuration. If then you see a bug that says, oh, this, you know, I want to see how this builds in 64-bit, you just pick x86-64 from Active Architecture and click build, and it builds that for the debug configuration. So you've got both things. You've got easy, thin building. You've got switching architectures without going back to your build settings and changing your project file. And you've got a try before you buy for whatever you want to do with your architectures. It's a really nice mechanism.
Along with the ability to change SDKs on the fly and change architectures on the fly, or to build a target for multiple architectures or build a target for multiple SDKs, we have a way to conditionalize other build settings based on what those architectures and SDKs are. You may have seen in Xcode 3.0 the ability to have per architecture variance of build settings. Well, we've generalized that and basically said, okay, for most build settings, you've got this ability to add a conditional build setting. You just select it.
You choose add conditional build setting from the gear menu at the bottom of the inspector, and you get another line with some pop-ups in it. And the pop-ups give you control over, you know, it's almost an NS predicate. It's not exactly an NS predicate, but it's an expression for what SDK, what architecture do I want, and then you supply a value. So, for example, I can set deployment target conditional on what SDK I've chosen. So if my deployment target normally is the compiler default, but whenever I'm building for PowerPC, I want to set the deployment target to 10.3, I can just set that. I can add as many conditions as I want and I can even define these conditions in a bracket and wildcard style language in my Xe config files if I'm using configuration files. This is going to give you a lot of control over projects that build multiple ways for multiple SDKs and multiple platforms. Once again, let's bring up Michael Rodden and show you how that all works.
For this demo, we're going to use a project that we called Ytap Support. We took the Ytap demo program from the iPhone SDK, and we factored out all of the code in it that would compile against Mac OS X frameworks, and we're going to build it into a static library, and we've configured this project so it can build either for the iPhone SDK, either the device or the simulator, or for Mac OS X. So this is going to be a cross-platform static library that can be built and included in multiple applications. So first let's go look at the Ytap support target. It's a target just like we saw before.
We go to its build settings. Now look at its architectures. Pop the architectures value. It says standard 32-bit universal, okay? It doesn't say exactly what it is. I can go in and find out if I want, but that's going to be a value that's going to persist over time if we change what the definition of standard architectures are. If we, for example, start building on Spark machines, we'll just add Spark to the list of standard architectures, and you'll inherit that for free. Very valuable thing. Now go look at the base SDK. Right now, this project is configured for Mac OS X 10.5 SDK. If you change that to the iPhone simulator, Well, notice that standard architectures has now changed to iPhone simulator standard architecture, which is i386. A Mac OS X application, by default, is two-way FAT, universal, but a simulator application, by default, is only x86, i386, because that's the only platform the simulator runs on. Okay? Now, if we look at the debug configuration, that's what we're looking at now.
Notice the build active architecture only checkbox is checked. If you switch to the release configuration, You'll see that it's unchecked. That means that if our architecture is multiple values, if we're building universal, in our release configuration, it will always build multiple architectures, but in our debug configuration, it's only going to build one for quick turnaround. Okay? Now we want to see some of the linker specifications, so just let's type in "linker."
And notice other linker flags. See the lines below it? It's got a disclosure arrow. We've got different frameworks being configured for different sides of this static library. If we're building for any Mac OS X, well, we want to link against Cocoa. But if we're building for any iPhone device or simulator, we want to link against UIKit. And so we just have per SDK variants on the linker flags in order to bring in the different frameworks for the different configurations. Correspondingly in the source code, there's a pound if iPhone that pound imports UI kit or pound imports Cocoa, depending upon which it's being built for. Okay? So if we build this, if we open up the build results window and build it, And we're building for the 10.5 SDK? Yeah.
So we'll build this and if you can open up the transcript, or just actually, just hover, this is a new feature, just hover and wait, and it shows us a tool tip saying what that step was built for and it says, Active Architecture i386, Active SDK 10.5 SDK. So we know that we just built all those files for the SDK. But if you go to the pop-up up there, the overview pop-up, change that to simulator iPhone OS 2.0, and then rebuild. Everything gets rebuilt. You hover over it.
and you see that it's building for the active SDK, iPhone OS Simulator SDK. Now, here's the important part. If you open up the transcript, here you can probably use your fancy control zoom in. Oh, there we go. So show the, yeah, this is the fun part. Notice the path to GCC here.
It's getting GCC 4.0, not from user bin, which is where a make file would get it, and not from /developer/user bin, which is where Xcode 3.0 would get it, but it's getting it from the iPhone simulator SDK. The simulator SDK carries its own copy of GCC with it. That goes all the way back to the early slides that I showed where the simulators, where the SDKs are self-contained development environments, and when you switch in the UI, you're actually switching your entire tool chain out from under you just like that. Okay? Thank you, Michael.
So that's what's new in Xcode 3.1. It's mainly a lot of features to ease development against multiple SDKs and to build projects that can build against multiple SDKs. And when you build a project against multiple SDKs, or even when you build a moderately complex project just for the Mac OS, you're probably not going to have that mental model of one project, one target, one build product, one executable to launch it. You're going to have multiple projects. The projects may have multiple targets. The targets will have many build phases and steps. Each of them may create multiple build products, and then they'll want to use each other's build products. They'll want to bring them in, copy them into their folders, link against them, do all sorts of complex things. Most of this historically has been left as an exercise to the reader. We're going to walk you through it now and show you the general techniques you need to use to make this work in Xcode.
First of all, we're going to talk about subprojects. There are no subprojects. If you are thinking of subprojects, you are thinking in a way that will get you into trouble down the road because Xcode projects have a complex and networked relationships. They are not strict subordinates. Like in anything, you can reduce -- any tree is a subset of a graph, of course, so you can set up any tree of projects you want, and if you want projects to act like subprojects of other projects, you can do that. But Xcode allows you to create multiple branching dependencies among projects so that you can share code in really powerful ways. So if you're thinking that you're going to make this project a subproject of another project, try to erase that thought from your mind and saying, I'm going to refer to this project from another project, and that will open up a lot more possibilities for what you can do in the future.
When you use a cross project reference, it doesn't make any assumptions of what you're making that cross project reference for. Those of you who come from a code warrior tradition are probably used to, oh, I build a shared library in one project. I am going to make this project a sub project of the other project and code warrior just mostly automatically figured out, oh, this is an app, that was a shared library. I'm going to include it, create a dependency, link against it, bundle it in, everything's done, one step. Xcode makes none of those assumptions for you. So, when you use a cross project reference, it is up to you to explicitly specify all of the things that you want to do with that cross project reference. If you want to use the output of one project as the input to another, you have to establish the cross project reference, you have to create a dependency between the two targets in those projects so that one builds the other. You have to, if you're building a library, you have to explicitly create the linkage between one and another. If you're creating a self-contained application, you may have to copy the framework in and then finally, due to the nature of DYLD, you may have to fix up the path so that the build product, when created and moved into your application, is properly constituted to run in its new location. I'm going to take you through all of those steps, show you a demo, and then talk about ways you can generalize these steps.
First, the way to establish a cross-project reference. It's pretty simple. You have project A, you have project B, you drag project B into project A. That's pretty simple. That does nothing. It just lets Project A know that Project B exists. One of the things it does do is it shows you the build products of Project B inside the Groups and Files tree of Project A, so that you can use them in other places, but it's up to you to say where.
So the second step is how to use the build projects. The most common thing If project A builds a small part, if project B builds a small part and project A brings that in as a static library or a shared library or something, you need to move that into a build phase. So normally what you do is you just take that build product icon in the groups and files tree that just appeared when you dragged in the project and you drag it down into your products build phase and add it. That's the basic technique for making an application link with a framework that you've built.
But you're not done yet. If the framework is in a well-known common place, your application will work. But if you want to embed that framework within your application, well, actually, before we get to that, you need to make sure that whenever you build the application, the framework is up to date. Whenever you build the framework, it reminds the application that it needs to relink with it. That means you need to create a dependency. You go to the target. The target has a list of dependencies. It has a list of other targets that need to be up to date when that target is built. You go to the target pane. You go to the direct dependencies panel. You click the plus button. And the other targets in that project, including the ones from project references, will show up. You select the one that you care about, and you add it to the list. When that framework target, for example, is added to the direct dependency list of the application, that means when you build the application, it will automatically make sure that that framework is up to date and build it if necessary. If you update and rebuild the framework and it's newer than your application, when you rebuild your application, it will relink with that framework. You have to create this dependency manually. Otherwise, it'll just take the latest version of that framework no matter whether it's up to date or not. That is a great way to get build errors, which is to forget to create the dependency, to make a change in your framework, to make a corresponding change in your application, and then to rebuild your application. Because without that, without having had the framework rebuilt, the application won't be picking up the latest version of the framework. Create the dependency, and it will.
And then finally, depending upon how you want to use the build product, you may want to copy or embed it. You may have one project that creates a bunch of resources. You want to copy them in. You may have a project that creates static or shared libraries. You may want to copy them in. You can put a copy files build phase that goes into your framework directory in your application, and then you just drop the build product in there. And that will ensure that it gets copied in. Now, a special note about frameworks or dynamic libraries.
With DYLD, the library manager inside Mac OS X, frameworks generally want to have some idea of where they're going to be installed and who's going to use them. They can be installed in a common location by an absolute path or they can be invoked relative to whatever's loading them. And so you have to use some build settings called dynamic library install name on the framework to tell it whether it expects to be at an absolute path or whether it expects to be invoked relative to the executable. The default is absolute path. When you build a framework from a framework template by default, it's going to expect to be put in the libraries folder in your user folder, for example. If you're building a framework for embedding in an application, you want to change the dynamic library install name of that framework to be relative to the executable path of the application in which you're embedding it. Just @executable_path/.../framework/ and then the name of the framework, which is the executable path build setting. Commit this to memory, come look this up, look for it on the web. This is the formula for building a framework that is embedded in an application.
You can do better than that, though. You can build one framework that is shared among multiple applications. As long as all of the applications have the same relative path to refer to that framework, you can deliver, say, a folder called applications, which is parallel to a folder called libraries, and all those applications can use all the libraries in the library folder.
If you look at the Xcode developer folder, that is exactly how we structure it. developer library has a lot of shared frameworks that are shared among all of the various pieces of the Execo development environment. The way we do that is with the dynamic library install name on the frameworks using the RPath methodology. RPath means whoever is invoking me, give me a relative path to them. So on each shared framework target, you define its dynamic library install name as relative to the RPath and then on every application that wants to invoke those frameworks you set its run path search paths, this is where it defines its RPath, to its framework directory, either within itself or relative to a common location. You can come up after and ask us details on this. There are a couple of good resources on the web to tell you how to do this. We went through this in excruciating detail last year at WWDC. So you can go look at my presentation with Rick Ballard's demo from last year on ADC on iTunes and see this in excruciating detail. This is the primary thing you have to do if you have multiple applications sharing multiple frameworks in a common location.
Now, the techniques I've showed you, making a cross project reference, moving, creating a target dependency in a cross project reference, putting the build product into a link phase or something like that, putting the build product into a copy bundle resources phase, these can be used for all sorts of architectures, all sorts of layouts of your project. If you have a static library that you're using in multiple projects, you can build it once and then just include it as if it were a prebuilt binary in multiple projects using this technique.
If you have an aggregate target, you can create an aggregate target which is just a target that consists of other targets being built in a specific order. You can use this technique to make the aggregate target dependent upon all of the other targets built in a particular order if you really need to build something like that. You can create a project that has an application and and a bunch of plug-ins and part of the app needs to be built and then the plug-ins need to be linked against it and then the plug-ins need to be copied into the application. You use this technique to do that.
But once again, the best way to get the hang of how this works is to actually see it. So we're going to take that static library we built, and we're going to build it into an iPhone application, and then we're going to build it into a Mac OS X application as well.
So we're going to get the Ytap application. This is the iPhone sample code that you get, except we've extracted all of the common code out of it. And what we're going to do, the first thing is, we're going to take that Ytap support project that we built, we're going to drag the project icon from it and go drop it.
and just say yes to the sheet, all the defaults are correct. And now you see that the Y-Tap support project is a cross-project reference in the Y-Tap application project. You turn it down, and you see its build product right there. Yeah, can you drag? There we go. Okay, so you see Y-Tap is the application project, Y-Tap support is the cross-project reference, and the libytapsupport.a is the build product that we're building. Now, If you go down to the target, open that up, this is our one target, you see the link binary with libraries build phase. Let's, yeah, you just drag that down, drop it in, And that has made it so that we're going to link the application with the static library pre-built by the other project. Now, that's great, but that implies that that app -- that static library has already been built. If we want to force it to be built, we've got to create the target dependency. So we double-click our target, go to the general pane. Now, first, I want to take a little digression here. I want to show you a new cool thing.
How many people hate how you have to add frameworks to an application in Xcode? Not as many as I'd expected, but there is an all-new way. In the link, there's a linked libraries pane here that shows you where, what all the libraries you're linked with now, and you see the required, that's the pop-up for required and weak.
with the magical third column that NS TableView gives us. If you press the plus button, This is a list of all of the frameworks in your current SDK or all of your SDKs. And you just pick the one you want and it adds it to the list. No more groping around the file system to find your frameworks. They're all listed right there in one little window. And you've got some filtering and sorting if the pop up at the top shows you whether you want frameworks, just dilibs or object files.
It also goes into /usrlib and gives you all the dynamic libraries there. So it's more for just frameworks. You're gonna use that a lot. I love it. So anyway, to create a target dependency, you press that plus button, and there's my Ytap support target in my cross project reference. Click add target. And this direct dependency means that whenever I build my application, the static library will be built if necessary. If I ever change my static library, the application will be relinked if necessary. Okay? So now we should be able to just go back, build panel, and build. And I'd say build and go now. What do you think? Feeling lucky? Sure, he says. Now we're using the, did you build the library for the, okay that's simulator. Looks like it's not finding the headers in the common build directory.
Luckily, we have our build system expert here to debug this. Make sure that the common build directory is set up 'cause that's my next slide. I prefer that we save questions for the end. We're gonna have a lot of time for questions. So actually, if we can go back to the slides, I'll just finish up with the slides. He did it. He did it, never mind. Yay. Okay, back to the demo machine.
So this is the Ytap application. Run that one in client. Okay, run that one in server, that's fine. Okay, this is an application that does nothing by itself. So now what we're going to do is open up another project. the Mac Ytap, this is constituted the same way. This is a new Mac OS X application that's written to be like the Ytap application on the iPhone, but it's a Mac OS X app that uses the same static library. And so we're going to go through the same steps. We're going to add the cross project reference.
We're going to disclose it, find that static library, add that static library, Now we double click the target, create the cross project reference. You do this enough, it really gets to be second nature. And then build and go. Now this is the one where it had the stale reference. I think that it's getting the file from the wrong location. Notice how he's doing his forensics. We should have made this a debugging the build system session.
Now this worked last time, I'll say, let's go back to the slides and he'll say, I've got it working. So two project reference tips. One is that if you have multiple projects that are building build products that each other need to refer to, the best and perhaps the only successful way to do it is if you have a shared build directory. If you saw when he went back to Xcode preferences to the build tab and made sure that we were using a shared build directory, that is the way the projects communicate within each other. All of the build products are in a flat build directory. That's fairly important to maintain. The second thing is that if you have projects that are referring to each other, they need to have a consistent set of configuration names because when you build the debug configuration of one project and it has a dependency on another target, it's going to go build the debug configuration of that target. If you have one project where your configurations are development and deployment and another project where your configurations are debug and release, they're not going to know how to line up with each other. So it's really important to keep your configuration names in a coherent space.
So while he's wrapping up on the great demo that I put together and subjected him to running through when it was broken, I want to recap what we've done. We did a brief review, projects, targets, build phases, and executables and build products are separate from products and targets, common terminology. Lot new in Xcode 3.1. Support for weak frameworks, support for active architecture, support for choosing your SDK in the popup, choosing your active architecture in the popup, the new overview SDK, some nice little things like being able to see the SDK and the architecture in and a tool tip on every build step. And of course the whole structure of SDKs, which now have the build tools embedded within them for long-term flexibility. Important things like how to set the deployment target and the SDK in relation to each other so that you can deploy on one version, but take advantage of features in new versions. And then advanced techniques of creating cross-project references of using the build products in different build phases of of creating project dependencies and then how to set up the dynamic library references so if you embed shared libraries or frameworks in applications, the applications can reach in and use those frameworks. Okay? Oh, he's got it now. Now we can go back. This is the big finish.
Build succeeded. And that one is client. So now we've got two applications, and they're both connected, one running on the Mac, one running in the simulator, and if you click on one, it sends a signal over Wi-Fi inside the Mac to the other. So this is the same code built once for the static library that is being deployed both to the Mac side side with a Cocoa application and the iPhone simulator side with an iPhone Cocoa Touch application, common code using common frameworks with only slight differences between the two being built twice with cross project references and target references. Okay? And with that, Tavares Ford is going to come up or Michael Jurowicz is going to come up and lead some Q&A for us.