Development • 1:01:41
In this, the second in a series of advanced Xcode sessions, you'll learn how to organize your configuration for large project development, use distributed builds, and configure for workgroups. Topics will also include the use of scripting to drive the Xcode environment through repetitive tasks, as well as maintaining projects that require sharing source code with Project Builder and make-based development.
Speaker: Anders Bertelrud
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Xcode Lead Engineer, Anders Bertelrud. I'm Anders Bertelrud, Xcode Lead Engineer, as you heard. In the previous session, we saw a couple of ways of increasing the productivity of Xcode. We saw how to increase productivity when navigating your project, in particular with the detail lists, finding the right files, the files by name and by content, that kind of thing. We saw editing, how to get productive with that.
We saw some source code management, how to use documentation, workflow, that kind of thing. So in this session, what I'd like to do is to dive down a little bit on the build system, and we can take a look at some of the ways specifically of increasing productivity when building your projects in Xcode. So let's take the hood off.
So what we're going to cover is we're going to start with talking about the Xcode project model. Just a brief summary, we've talked about a lot of concepts in all these sessions, like targets and files and build phases, that kind of stuff. But sometimes it can be a little bit tricky to keep it all straight. So I just want to get real basic and talk about some of the concepts real quickly.
And then we're going to talk about optimizing the build performance. Some of the things that you can do to speed up the things that your project's already doing. So the kinds of builds that you're already doing on your projects. Then we're going to talk a little bit about how to automate some of the common tasks.
There may be things you are doing using Finder or the command line that we can actually set up to be done automatically. Maybe even at night using automatic script to run so you don't even have to worry about it. So we'll talk about this. We'll start with the project model.
So...
[Transcript missing]
"File references that are not actually part of any target at all. And well, what are these useful for? Well, you know, maybe you want to have a to-do list or you have Read Me or something like that. Some people actually use Xcode to manage their recipe collection. And admittedly, most of you actually have projects that build products.
So for you, this would probably be a to-do list or something like that." So another thing that we have in a project is dependencies between targets. And this is fairly logical, right? If you have a library in an application, you want to make sure the library is up to date before you try to build the app. Fairly simple. We also have build styles, and we'll get to those a little bit more later, but they're actually separate entities in a project that get applied to a target dynamically when you build. We'll talk about that in more detail.
So here's another case, which related to a question that we had just recently from the previous session. You can actually have one project that refers to another. So in this case, we still have the first project as the file references, targets as usual. Files belong to the targets, nothing special there. The second one is files, targets, files belong to targets. And there's the dependencies between the targets within a project.
But there's something more we can do. We can actually add a project reference to the first project, in which case we have a link to the other project. So now the first project, the one on the left, actually knows about the project on the right. And therefore, we can set up dependencies between the targets between the different projects. So this can be very useful.
We still, of course, have the build styles, and they can actually apply to builds of the targets in the secondary projects. We'll go into that a little bit more. So we talked about targets. Well, what are these targets then? We talked about application and library and those kinds of things. Those are really product types, the types of things you want to build. The target types more refer to build systems. So we have this dependency layer that goes between the targets. Those were the red dashed arrows you saw in the previous diagram.
So you can have your application, your library, et cetera. But each target can have its own build system. And so we'll see how this can be useful. The first kind are native targets. This is what Xcode really shines with. This is where the dependency graph is maintained continuously, up-to-date, internally, so we can show nice up-to-date information about it. Native targets currently support applications, frameworks, libraries, plugins. And there is project-level support for multiple OS SDKs. So you can be on Panther and you can build for Tiger, for example.
They provide the best integration with the user interface. But sometimes that's not feasible. So sometimes you have other kinds of targets that you want to use. So one other kind are what we call external targets. And the icon here is a shell command or a command line tool icon because it actually lets you invoke any command line tool at all.
And you can typically invoke things like new make or ant or those kinds of things. Typically, they're used if you have maybe an open source project or a project that is cross-platform. But you can actually invoke any script or command, as we'll see a little bit later on in a demo.
Xcode also supports Project Builder Jam-style targets. So for those of you who had projects in Project Builder, there was a separate command line tool called Jam, stands for Just Another Make. And that's what, it was an external tool, the pipeline for the information between the IDE and the build tool wasn't that great, so that's why we have native targets.
But the Jam targets still support everything that they did in Project Builder. We did this specifically so that you'd be able to transition your projects from Project Builder to Xcode very easily. Um... It's a good idea to convert to native targets. There's a menu item for that. The only thing not supported as native targets in Xcode 1.5 are Java targets. We're working on that.
And the architecture of Xcode is actually fairly flexible. So in the future, we plan to allow you to plug in customized support for other kinds of build systems if you want to. We don't have that yet. We're hard at work on that when we're not doing presentations for WWDC.
So that's a little bit about target types. So these things determine what gets built, but how do we determine how to build it? Well, these are the build settings, right? So they control the inner workings of a build. They're like the properties, it's like new make variables, analogous to that. So they're individual properties. One setting's value can actually refer to another one. This is somewhat powerful. You can define a setting that has the path of a common directory and then base everything off of that.
For example, the install path can actually refer to $home. Turns out that home is one of the build settings that gets inherited from the environment, so that can be kind of nice. Build settings are passed as environment variables. I mentioned external targets. We pass that in the environment.
So if you have an external shell script or a new make target or whatever, they will actually have access to that. Of course, for native targets, it doesn't really matter. There is no external command to be called. Build settings can be set at various levels. We'll see that shortly.
They're usually set on a particular target, because that's the most natural place to set a preference or a build default is on the product that gets built. And there's an ordering for build settings, so that your customized values override the defaults, and the build styles override the target values, et cetera. The higher levels override the lower levels.
So what are the levels? Well, here's the precedence. At the bottom, we have the inherited environment variables. These are just the basic things like the home variables. They come from a terminal. If you're purely an ID user, you probably don't care so much about this. After that come the built-in default settings. There's a lot of these, right? These are all the default settings for all the compilers or linkers or whatever you have installed. So whether or not optimization is on or off by default, that kind of thing.
Above that are the target settings. The build style settings come above that. We'll talk a little bit more in detail about that. And above that are the command line settings. Well, what's this? Well, for the IDE itself, this is always empty. This is always a moot point. But it turns out you can actually build a project from the command line, and that's where this gets useful. You can overwrite any setting at all from the command line when you invoke what's called Xcode build to build your project from the command line.
The environment variables we talked about, these get inherited. The built-in defaults are the linker settings, that kind of thing, the default values. Target settings are real interesting, so we're going to drill it down and talk more about those. You set the target settings in the Build tab of the Target Inspector or the Target Info window.
So we see the little red circle there, how you get to that. And that opens up this panel. And here we see the settings. It's just a list. You saw this in the previous session. It shows the inherited default values as well as anything you've customized. Anything that you've customized is shown in bold.
And the inherited ones are not in bold. Also, I mentioned the build settings. Anything set in a build setting trumps the values at lower levels. So we show in strikethrough, we show the ones that are set in build settings. So this indicates that the dead code stripping setting is actually overridden in the current build style.
Something else here is we have a pop-up menu that lets you focus on a category of build settings. I think this was shown in the introduction to Xcode, but it's worth pointing out again that you can actually click on that table heading there and narrow down your focus.
And of course, we have the search field, which lets you find any setting that you want quickly. And this matches not only the name of the setting, but also we search in the help string for that setting. So if you type in, if you're not sure or if you can't find the setting you're looking for, try typing a phrase related to that into the field here.
OK, so much for target settings. That's pretty simple. So build style settings, let's talk about that. These guys are set in a similar manner. They allow for variations on targets, so debug versus release, the pro version of the app versus a demo version, let's say. As I mentioned, the build styles override anything at the lower level.
Some examples of how to use build styles. You can, for example, build without debugging symbols. This is fairly obvious. You can build with zero link, enable or disable. Zero link is a feature that we use at development time, but we really don't want to use while we ship the product.
Different optimization levels, pretty easy. Alternative algorithms. Suppose you're trying out some new hash table algorithm. You want to build one variation of your app with the old algorithm, one variation with the new one, and do some performance tests, let's say. You can enable or disable features. If you do a demo version of the app, you want to disable save.
And with dead code stripping, the code's going to get removed as well. That's a feature that's new to 1.5. You may want to set different build output paths so that you have your debug build object files going to one place and your deployment build going to the other place.
So why not just have multiple targets, right? I mean, we already have separate targets. Well, you could, but let's consider an example, right? So what if I have an application, and let's say that I have a demo version of that application, right? So all we do there is we set a special pound to find that turns off saving, okay? Let's say there's a support library that they all rely on.
But actually, so I've been doing development for a month, and so let's see, okay, well, what I've actually been developing is my debug version of these things, right? So now it comes time to deploy. Well, I want to optimize better. I want to maybe turn off some debug logging, that kind of stuff. So I really have another version of each of these targets.
And then suppose I want to have yet a third style where I say, well, let me try that new hash algorithm. I still want to have optimization on so I can really do the performance testing. So now I have really yet a third set of each of the targets. So all told, I have nine targets. And really what I have, though, if you look at them, is a combination of three targets times three variations on each.
We coalesce these into build styles. We get rid of these. So what we really have is M plus N as opposed to M times N. So build styles are multiple targets, right? You can use multiple targets and just ignore build styles. That's fine. But they're there and especially helpful for large projects.
So let's talk about how to set those, the settings there. We go to, actually, the project, if you notice, what's selected here is the project icon up at the top. Build styles are attached to the project, not to each target. So we inspect the project, and that's where we see a list of the build styles. So this is in the styles tab.
If you notice at the top, the four tabs, I've selected the styles tab. And there I see, in the pop-up, I see all of the styles that are available. window looks very familiar, right? It's the same thing that we see in the target inspector, it's just now we're editing a different level of the precedence.
One important thing or one important way to use the build styles is to only define the things that are unique about that style. So if you have some setting that really is common to all the styles, has the same value for all the styles, odds are that it's not really part of the variation.
So that's one thing to keep in mind. One interesting thing is that you can actually refer to the overridden value. You don't actually have to obliterate whatever is defined below. You can just append to it. So if you have a bunch of preprocessor macros, you can say $VALUE and sort of capture the overridden value and then just add to it.
That was a little bit about build style settings. There's more in the documentation you can reach from the pink Help button there. Again, we talked about the command line settings. These are for use by Xcode Build. I'll talk about that a little bit later. Another thing that native targets have is build rules. These are just like the rules that you would define in Make or any language like that. They control how source files are processed.
Every rule consists of a condition and an action. The condition matches on the kind of file that you want to process. The first matching rule is used. What Xcode does is that it matches on either file type. This is the abstract file type, such as all C-like language files, which would include the Xcode file type. You can use the Xcode C, C++, Objective-C, that kind of stuff. Or you can match on specific patterns.
The file name pattern matching is particularly useful if you have something like a custom extension for a custom file format. Let's say you have a unique image file format that you use. You're developing a game. You can set up a rule that will take files with that suffix and pipe them through some translation tool that compiles the image or whatever so that it can load faster into your game. That's just an example.
You can use them to select the compiler and version. For example, GCC 3.5, there's a preview of that on the DVD that you got. And you can use build rules to select between GCC 3.3 and 3.5. The default is 3.3, but you can easily just change the pop-up to select 3.5. Another example is Java C versus JEGS.
And as I mentioned, you can also use them to generate, translate files, or even generate custom source code. The outputs of a build rule are piped back into the build rule machinery, so you can generate some cascading rule applications this way. You edit the build rules using, again, the target inspector. You go to the Rules tab this time.
Pretty obvious. We see here that you can define, you can use a custom script if you want, and there are some macros that you can access for the name of the file being processed. Again, the pink question mark icon there is your friend when it comes to help about what's going on here. As we see here, the system rules are defined down below and the custom rules are above. If you remember, I said the first matching rule is used. So custom rules at the target level override anything at the system.
We have a fairly extensive set of built-in types that Xcode recognizes. And if you notice at the bottom, well, it's pretty low, but the last item says source files matching. That's where you can type in, let's say, startup food. So that's the condition. The action shows all the compilers and translators that Xcode knows about.
And you can also type in a custom script in any scripting language you like. So that's a little bit about build rules. As I show you, you can define actually what the output path is going to be. This ties into Xcode's dependency analysis. So your script is only run if the inputs are newer than the outputs.
OK. So let me show a little demo, just show where some of these things are. Could I have the demo machine, please? Thanks. OK, so I have here just a version of Sketch. And what I wanted to show was a couple of things. In so many of these demos, you know, we've just kind of opened something and build it and shows that it builds. I figured I'd do something a little bit different and actually show you just where the items are, some of the more commonly asked questions.
So one of the big ones is if you select the project icon up here and then say Info, that's how you get access to the styles. They're not underneath the targets. The target has its own settings, so we can inspect that too. And actually, I inspected the target Smart Group, which has nothing. But I can select each target and show that.
But again, the project is what has the styles. If you notice here, there are a couple of items that are in bold. These are overridden. I've actually here, I've overridden the output location for both the intermediates and for the products. And so what's going to happen here is that when I build with the development build style, it's going to put everything into a subdirectory called debug. If you build with a deployment, it's going to put them just into the default location.
So that's fairly useful. This is also where you can define new styles up here in the project inspector and name your own build style, that kind of thing. You can obviously edit build styles and delete them and that kind of stuff. So one other thing that I wanted to show is that when we have string list settings, you can actually edit the value using a sheet now. And if I can find one, let's say preprocessor definitions. Can narrow it down there. Preprocessor defines. I can say edit setting, and I actually get the sheet to edit individual list items in here. So I can do that if I choose.
One other thing I wanted to show is the build rules. Again, here's the rules editor. Now, notice again that this is at the target level, so each target can define its own rules. Here are the inherited ones, and I can also, of course, add my own rule, and I can choose whatever I like here.
One thing that you may find useful is you notice I can only select the target up here. We actually have another pop-up menu that you can add to your toolbar. If you end up using build styles a lot, like I do, for example, you can actually just add another pop-up for the build style. And so the way to read this is, okay, I'm going to build the sketch target.
I'm going to build it with either -- for either deployment or development. And that can be a fairly handy thing to do. So just a little overview of this. Can we go back to the slides, So we talked about the project model a little bit. That was sort of a quick introduction or a quick refresher.
Let's talk really about optimizing build performance. That's why you're here, right? Increasing productivity with Xcode. So speeding up the builds. Well, what occurs during a build? Xcode has a fast, powerful build system and it keeps a lot of information internally. So some features are automatic. We talked about the dependency analysis that's always kind of being updated in the background so that when you hit the build hammer, we already know what's up to date and not. We talked about parallel builds. If you have a dual CPU machine, Xcode automatically takes advantage of both CPUs. Zero link, these are all things you've heard about.
Predictive compilation you may not have heard so much about. We'll talk a little bit more about that. The basic idea is that we can start compiling while you're still editing your file so that by the time you save your file and you actually hit the hammer, we're done compiling it. And that works really nicely, especially when you have single file turnaround.
So those are some automatic things. There are some things you can do to optimize your project. One of the biggest things is to add a precompiled header. Xcode comes, well, Mac OS X comes with a lot of SDKs and a lot of headers, and there's just a lot of declarations that the compiler has to see every time it compiles a source file. So we can speed up some of that work by doing it only once.
Another big thing is distributed builds. I'm sure you've heard of this too. Maybe you don't know quite how to enable it, configure it, that kind of stuff. We'll talk a little bit about that. Fix and continue is another thing that you saw in the introduction sessions. How many people are actually used then? OK.
Try it out. It works most of the time. There are some caveats that other sessions have gone into. If you change the size of a structure, that kind of thing. Basically, this patches your code while it's running, so you don't even have to quit and restart the app. And it can be really a time saver.
But let's talk a little bit more about the compile-- edit/compile/debug cycle. So let's say we're editing a file. That takes some time, right? And then we have-- let's say we edit four files. We end up-- this is the slow case. We end up compiling each of the four files, and then we link, and then we start the program.
So one of the things we can do, as I mentioned-- here's the timeline that this all takes. One of the things we can do is set up a PCH, a Precompiled Header. This cuts each compilation down to a fraction of what it was. So we shorten the overall time. OK, that's pretty straightforward. Another thing we can do is to use ZeroLink to enable that during development. It is enabled by default for development builds. So that cuts out the link during development. So now we've shortened it a little bit more.
Still, we have the CPU marching through each processor, like that, kind of just compiling it, and then finally we start the program. So let's do it in parallel. If we have dual CPU, we can have each CPU actually going through, compiling, and we're done in a shorter amount of time. But we can do even better. Suppose instead of just using two CPUs, we used one of ours or two of ours to take the data, preprocess it somehow, send it out across the wire, To other machines, let those compile the files and send the results back.
And then we just harvest the results. So that shortens the time. And that is great for large builds, especially when you have lots of files. Suppose we just take the single file case. Let's just go back to that. So we're still back to the one file. Even if we have dual CPUs, if we only touch one file, there's no point in having two CPUs. We can't really parallelize. We can just do that one. So one key observation is that editing is not very processor intensive. So we can actually make that a shorter box to indicate that.
And then what we see is we can actually slide the compile step in underneath the editing, meaning, especially in a dual CPU, if you have two, you can be editing with one and compiling with the other one. So what Xcode actually does is to send down the first part of the file and then wait until you hit Save, and then it sends the rest of it.
So that shortens the compile time. And then the last thing is, as I mentioned, fix and continue. We can get rid of restarting your app entirely in most cases. So you can just keep on patching the code in your app until you find that you need to change a structure or do anything more significant like that. So that's a fairly short compile time compared to what we started out with.
So precompiled headers. So that's a nice graphic, but how do we actually do this? Well, what you do is we use the observation that most of the code is in the headers. The compiler sees the same code over and over again, unless we're using precompiled headers. One example, I just found a small one, a little C++ file under developer examples. And this is the ucarbon-event.cpp. And it's actually 54 lines long.
And this is actually the whole text of that file. So it's a very tiny C++ file, right? But one of the interesting things here, and expensive ones, is we see that it says import Carbon slash Carbon.h. So what does that end up doing? Well, it actually ends up dwarfing the code that you typed, because this ends up being 90,944 lines that the compiler has to see. So this is actually not even the scale. The grade line there would have disappeared. It's actually 0.06% of the code that came from your project. And 99.94% came from the system headers. So the obvious thing to do is to create a precompiled header for a Carbon.h.
So, GCC 3.3 and 3.5 support precompiled headers and they're pretty easy to set up in Xcode. What you do is you create a prefix header that gets implicitly included in front of every source file in your target. It's at a per target level. And then, you just check the checkbox that says precompile this thing. Xcode automatically figures out which different versions of it it needs to do. For example, if you pass different preprocessor macros in different headers, it actually builds it correctly for you.
Keep in mind, though, the prefix header gets included for every source file in the target, so it has to be compatible with every source file. If you have C++ and Objective C++, then you need to use #ifdef appropriately. There's a really nice help page on that in the Xcode documentation. You search for PCH. The other thing to keep in mind is even if you have a correct prefix header, it can get precompiled too many times if you put change in headers in it.
So the trick here is to put all the stable code, typically the system code, which hardly ever changes except when you update the software, you put that in the precompiled header, and then you leave all your user code to be compiled every time. And the other thing I mentioned is Xcode does precompile the prefix header on demand, which means that if you change the input conditions so that the precomp is no longer valid, Xcode will rebuild it for you.
So that's a little bit about pre-compelled headers. Just shortly about distributed builds, there's some questions we get about that. Xcode automatically uses all CPUs, right? But why limit yourself to your own machine? There's lots of CPUs. We only ship dual CPUs, but you could have a 14 CPU machine if you use all the other machines in the offices next to you.
The way to do this, the way to enable this, is to go to the Xcode preferences. And there are two things you can set here. You can set whether your machine should use other machines to precompile-- to distribute to. And you can set up at the bottom there whether your own machine should be a distributed build server.
Some things to keep in mind. It's only available on the 3.3 and 3.5. That's not so big of an issue for most of you who are on 3.3. Obviously, the faster your network and the faster your machines are, the more benefit you're going to have. And it really does require 100BaseT network to be effective, because with machines, even the PowerBooks being so fast, anything slower than that, it's often faster just to compile locally than to send it out and get it back. Okay.
The ideal number of machines we've found is between 4 and 10. That's a very fuzzy number. It just really depends on your project. But it's probably not the case that adding 200 XSERVs is going to make it 200 times faster than not having distributed builds. So there is definitely a fall-off at some point.
Keep in mind the security of your source code. This is a concern for some of you and not at all for others. There are two modes. One is rendezvous. This means go out and find any server who's willing to compile my code and let it compile my code. But that means that you're sending your source code to that machine. So you need to make sure that that's something that you want to do.
The other option is to specify a list of servers. You could do that in the panel that I showed you in the previous slide. You can actually just specify a list. And if you enable rendezvous, it will actually show you the list of the servers that are within range. And you can check off which ones you want and limit the list that way to, let's say, a set of secure machines in a lab somewhere.
So we talk about precompiled headers and distributed builds, zero link. This is fairly automatic. There isn't much to talk about here, except to observe the link time is a significant part of the turnaround time. And typically, you're editing only a couple of source files. So the interesting thing here and where you need to be aware is that it runs the code directly from the object files. It does only work for applications and command line tools right now for technical reasons. The big thing to be aware of is keep in mind, it runs from the object code. So therefore, you don't want to do this when you deploy your app.
The deployment build style is set up to not use zero link by default so that you actually get a real app that gets linked. Don't ship zero link builds, because it's going to try to access the .o files back on your build machine. If you get this-- I don't know if you can see this-- but basically, it says zero link could not load .o file, and then it gives a path of the .o file. That indicates that-- you took the binary stub that was built for zero link and took it to another machine, and the binary stub is about 10k big, and all the .o files are still back in your old machine. That's a symptom of that.
And the fix, of course, is turn off Zerolink for deployment builds. Predictive compilation, I just want to mention a little bit. This is off by default, so here's something that you can do to increase your productivity. Normally, even if the PCH doesn't get used, nothing happens until you have build.
So here's the idea again that we can get a head start by using those idle cycles while you're editing and start compiling. We send the first couple of pound includes, which typically include heavyweights like Carbon.h or Cocoa.h, and get the compiler really chewing on that stuff first. And then while you're still typing your code, the compiler is just sitting idle. And then when you save, we send the rest of the data.
And then when you have build, oftentimes the compilation is already done and the .o file is just sitting there ready to be used. So it effectively essentially eliminates the compile step for single editing. Even if you're editing multiple files before hitting run again, if you edit them in sequence, it actually can eliminate the compile step entirely. The way to turn this on, there's a checkbox in the pref panel. So that's all you have to do for that. It's automatic after that. I'm going to show a little bit of that.
Okay, what I wanted to show first here is if we inspect the target again, see that I have--let me see here if I can find the prefix header. There we go. So I've set here SKT prefix. This is the project-related path actually to a file in a project.
So let's take a look at that. I just type prefix. You can use the handy detail view to get to it. All this one does is include Cocoa.h. If you had C++ in here, this would still work because of the guard macro. That's what I was talking about before.
The Precompile Prefix Header checkbox is obviously the one that causes it to be precompiled. You could have a Prefix Header even if you didn't want to precompile it. That's typically not very interesting. ZeroLink does show up in the target inspector because, after all, it inspects all of the settings. So if I choose zero here, I see that ZeroLink actually is overridden at the target level.
And the reason for that is the difference between the deployment and development builds. So I go to the project and inspect here. If I go to the styles, let me narrow down again on ZeroLink. I'll see that in the development style, ZeroLink is checked. In the deployment style, it's unchecked. So that's really how you want to do things.
Another thing I wanted to show here is the distributed builds again. Normally under Building, there's some options for the local machine. Distributed Builds is the prep pane that's dedicated to the distributed builds. I don't have set up for distributed builds because of the demo machines here. But it's pretty obvious what the settings do, but I wanted to show that this is the panel actually where you get access to that.
Because sometimes we get questions of, well, how do I set this up? Where do I even look? Under Building, there are some interesting options. One thing that Xcode supports, which is actually kind of nice, is to build to a shared location. This is particularly nice if you have multiple projects. Even if they're unrelated, you can get all the products to go into a particular location.
It's also good if you're working with source code off the network and you want to make sure that your binaries really are on the local disk. That's a really good idea for performance. The predictive compilation is off by default and disabled by battery power, but it's really a good thing to be turning on. We'll be turning that on as a default.
And another useful one is if you want explicit control over how many processes to run on your machine, we'll limit it to three because we've really seen that it can hurt the performance or the responsiveness of your machine if we launch too many compiles at once on the same portal of PowerBook.
So that's a little bit about that. One other thing I wanted to show, actually, is I showed how you can set settings at a target level. You can actually set settings at a particular source file level, too. You can either navigate down in the groups here. You can expand the sources phase, which are all the source code files for that target.
Or you can just see them here. In fact, if you select sources, you see them all listed. They'll have check marks because I haven't built the project yet. This means that it needs to build. One of the things you can do is you can actually select here and you can add additional compiler flags.
And if you add compiler flags to a particular source file, Xcode will take care of generating another version of the precompiled header for that if it needs to. So you don't have to worry about that. Okay, so just a little bit about those kinds of things. Can we have the slides back? Please? Thanks.
So we talked about optimizing build performance a little bit. I just wanted to show you where to go to set some of these settings because of the questions we get on the mailing list indicates sometimes people don't know that. Let's talk about a really interesting topic, though, is automating some of the common tasks.
We talked about how to make the things you already do, how to do those things in Xcode, faster. But automating common tasks gets into territory where you may be doing something totally by hand that you could actually automate. So what are some of the ways we can do this? We can automate the builds. That's one of the most obvious things. And there are a couple of ways to do this. One is to use the command line tool.
We have a command line tool called Xcode Build. I'll describe in detail. You can use AppleScript. Xcode is scriptable. So for those of you who know and love AppleScript, you can use that. And actually, there is an automator stage, an action that you can use to build a project.
Another way you can automate your workflow in Xcode, the build workflow, is to set up custom build rules. I mentioned that. I'll talk about some more examples. Some custom build phases have really interesting uses that I haven't thought of all of them, and I'm sure you can think of many good ways to use them. Same with external targets.
These are really kind of opening a porthole into the machinery so that you can actually hook in anything that you like into the build phase. Into the build sequence. Custom executables is another topic I want to talk about. This is sometimes underused, but can be extremely powerful, especially if you're working on libraries and plugins. We start with building from the command line. The tool is called Xcode Build, and it's in your default path.
So if you're running from terminal, you just type Xcode Build. It provides access to the exact same build system as the IDE uses, so there's no sort of compatibility mode or any of that stuff. It's actually the same share of the build. It's a library that's opening your project and building it. You can invoke it from the shell scripts, for example, or directly from the command line. Good for nightly builds, that kind of stuff.
The basic invocation is just Xcode Build, and that will just bring your project up to date, whatever project you have changed the working directory into. But it has several options. One of those is you can say what action you want. I mean, that's pretty obvious. You can remove your binaries. You can bring your project up to date. You can actually specify.
You can specify which target you want to build. And this is obviously useful if you have many targets. I showed the target pop-up where you can choose which target to build. This option, the dash target option, is actually the same thing as that pop-up. That's what you're controlling. Same with the build style.
You choose a build style development or deployment. Typically, if you're building from the command line, you've been building for deployment, and you'll want to put the results in an installer package, something like that. This is the same or controls the same setting as the pop-up for the build style in the Xcode IDE. And under the hood, it actually controls literally the same setting. So there's a high degree of fidelity there.
You can also override build settings. If you remember, the gray box at the very top of the precedence was command line options. That's what these are. So if you pass any of the command line options, either the ones that you've defined or that are built in, you can just override that on the command line. This is kind of a GNU Make style use of the Xcode IDE, but it's nice to have a command line tool like this for the automated stuff.
So if you don't want to use the command line, you can also use AppleScript. Xcode is scriptable. If you go to the script editor and you say Open Dictionary, Xcode shows up in there and you can see all the classes and commands we have. Not all facilities are available from AppleScript at this time, but we're working on completing that and rounding it out.
Project operations are, you can set build settings, you can add files, that kind of stuff. Here, for example, is just a little line of AppleScript that just builds a particular target with the current set of build settings. So this is another way that you could automate your build process for actually going from source code to final disk image that you ship to customers. So either or.
Custom build rules are a way of automating inside of the project, as I mentioned, inside of each target. I'm repeating some of the same information here, but I want to sort of try to give a couple of opportunities to help you remember it. The essence is that a rule controls how the target produces and processes a source file. So an action, it's any script you can run from terminal. You can even use Apple Scripts, you can use Perl, Ruby, that kind of stuff.
Obviously, a normal shell, Python. As I mentioned earlier, the build rules can declare the path that gets produced, the output files. So this gets related to the input files by Xcode. And so your rule only gets run if the input is newer than the output, or if the output doesn't exist.
And the produced files are fed right back into the rules system. So if you produce C files, then they get fed back in and the rule for C files gets applied and the C compiler is invoked. So that's fairly handy. For example, custom script to generate C files from whatever data files you have. In fact, the tool that you use to generate those C files could actually be built by another target in your project. We've seen that in some of the internal projects at Apple, actually.
Other example uses, preprocessing, generating code. You can invoke compilers that are not directly supported in Xcode. We have a list of some of the compilers that are supported. GCC is the only C compiler with built-in support. There are other C compilers and Fortran compilers, and you can use a rule for those.
Custom build phases are a little bit different from rules. The rules are condition, action kind of a thing. Build phases are just steps in the sequence to build your product, and so you can add them to any point in your native target. And the primary uses are things like copying arbitrary files and running arbitrary scripts. You may have Unix man pages. You may have custom files that go in custom places in your application. You can specify inputs and outputs, and again, these phases get run only if needed.
And the paths are fed directly into the Xcode dependency graph. So some examples, copying files and folders, that's a popular one. A lot of people have somewhat complicated applications, and they want to embed frameworks and that kind of stuff. You can set special permissions on files. If you have a little extension, that kind of thing, that needs some special flags.
You can also install files in arbitrary file system locations. So that's what I mentioned in the Unix man page. This is maybe for more of the sort of typical Darwin development that has specific places in the file system where files need to go. Typical Mac applications should be able to be run from anywhere.
External targets. I mentioned this early on. This is a way to call any kind of external build system you have. So you can specify the action to invoke for build, for clean. All these things are passed in the environment. So you can actually set your own options at the target level, and they get passed down through the environment.
And if you have a Perl script, let's say, or a Shell script, you can pick up those settings and modify the behavior of it based on that. So there's lots of opportunities for weaving your own custom scripts into the Xcode build process here to really get to a source to disk image kind of workflow. You can customize the command line, as I mentioned. You can pass any options you want.
If you have your own script that you're invoking to drive a part of the build system, that's maybe not so interesting because you could just change the script. But suppose you're using Ant or Make, you could do that. There are actually project templates to invoke the Ant build system that are installed as part of the Xcode install for Java development. So you can take a look at some of those for examples of this, actually.
Okay, so other examples. I think I talked about these. Some of them already make and, yeah, I talked about this. PackageMaker is actually another interesting use for this. We tend to think of external targets to invoke external build systems, but any tool can be invoked. So if you want to use it to create a package disk image, there are command line tools for that.
Xcode does not yet have built-in target types for building disk images and other kinds of deployment targets. That's definitely something on our list. But meanwhile, you can actually wire it up yourself with not that much work. Web Server Deployment Tools is another interesting one. You could FTP the result of your build over to some server, et cetera.
Custom executables are, I think, underused, but a very useful tool. Every time you create an executable target, such as an application or a command line tool, Xcode automatically creates an executable for you. And what's an executable? Well, it's just an execution context. So that names something to be run, the directory from which it should be run, any kind of arguments to be passed to it, if it has any on the command line.
You can set these up for anything you'd like. So if you're developing a Photoshop plugin, you can actually set up an executable to launch Photoshop and have it load your plugin. Launch parameters can be configured. And as I mentioned, you can create as many of these as you want, custom ones.
Plugin development is one. Libraries, too. If you're working on a framework or library, maybe you're, most of you are probably shipping an app or working on something like that. But if you are working on just a framework, what you probably have is you probably have a dozen test apps or more.
And so you could set up an executable for each of your test applications, each one configured to run with your built version of the library. And that's a great way to test your library. As you make a change to your framework code, you just hit build and run.
It will run some other app but load in your framework. and you can test your changes that way. And it's useful for quickly switching between them. Just as there is a development, like a build style and a target pop-up, there is an active executable pop-up, which controls what gets run when you hit the Run button. So that's another thing you can add to your toolbar.
So I want to show a couple of these things in action. This time I'm actually going to show, not just show where things are, but actually build a couple of things. So can we have the demo machine, please? Thanks. So what I'm going to do here is to-- first, let me just go ahead and build. The development build style here is the active one. I can actually add-- I'm just going to Control-click on here. Let me add the active executable to show you that sketch. Not surprisingly, executable is shown under here. And you can edit any kind of settings there.
What I wanted to do now, though, was to, let's say, create an external target. Let me go ahead and-- what I'm going to do is to go into the Project menu, just select Sketch here, go and say New Target. And what I'm going to do is create a shell script target. And let's say I want to create a disk image. So that's what I'm going to choose. And I want to add it to the Sketch project. So that's pretty simple.
So now we have the info here. By default, this target, it doesn't build any kind of product at all. All it has is a single phase that runs a shell script. So I'm going to inspect the shell script build phase. Shell script goes here. So I could type in a whole shell script here if I wanted to. But what I'm actually going to do, I have a ready-made one that I'll just add to the project. So I'm just going to drag that in, drop it here under the Groups and Files. I want to copy this into the project, actually.
It lives on my desktop right now, so I'll just copy it in. I'm not going to add it to any target, because I don't actually want to install the script as part of any build. If I added it to the Sketch target, it would get added as a resource in the Resources bucket. But I don't want to do that. So I'll just go ahead and click Add.
So now I have my shell script here. I can-- let's see, take a look at that. And in this case-- it's a fairly-- eh, not too complicated. It uses AppleScript, actually. Most of the complications use AppleScript to configure a disk image. What it does is just create a disk image, use AppleScript to set position of files and icons and color and stuff like that. It compresses the disk image, and then it tells the finder to show it to us. So let's go ahead and take a look at that.
Now, I've added the file to my project. But what I also want to do is, of course, to edit the shell script build phase itself. So I'm just going to type in a shell command here to, in this case-- just invoke the shell script that's in my project.
The shell script here gets started up from the project folder. So that's a fairly succinct way of doing that. If you notice at the bottom, there's the input files and the output files. This is how I mentioned that Xcode weaves in the shell script into its dependency graph so that it knows when to run it. So what I'm going to do here is I'm actually going to add-- I'm going to use some of the build settings that are defined by default. I'm going to say that in my build products directory, there's going to be a application called sketch.app.
could drag this into. These build settings, how do I know about build product store? Well, we have a menu item for bringing up the HTML page for this. We have a bunch of built-in build settings. Normally, if you're just building an app in Xcode, if you're not doing anything particular like automating the workflow here, you don't really need to know about these. But they're very useful if you do want to do anything more, a little bit more complicated like this. Another thing I'm going to do is I'm going to make the shell script here depend on the shell script file that I am actually including.
And what this is going to do is it's going to mean that my shell script gets run if the product is out of date and also going to get run if I modify the script that builds the disk image. So that's kind of nice. And what does it produce? We need to know what to compare against. So what this script produces is it puts in target builder. That's another one of the standard defined settings. It's going to create a sketch disk image. So now we have this all set up. And let's see what happens when we build.
One of the things that I do is to make sure I pick the deployment target so I don't try to ship the zero link binaries. And I'm just going to select the create disk image target. There's one more thing I forgot to do. Does anybody know? Yeah, so right now we have two independent targets.
You can actually have two totally separate dependency trees, or two or more if you want, in Xcode. But to relate these two targets to each other, what I want to do is I want to bring up the inspector again. And I want to add a dependency from the createDiskImage target to the Sketch target. So this means that Sketch will get updated before I even try to build the disk image. So let me get rid of a couple of the inspectors here. And let's just go ahead and build this.
So it's going to go ahead and build Sketch as it normally does. And actually, what I'm going to do is-- it's linking. It's not zero linking, so that's a good sign. So it's running the custom script. So it's actually creating the disk image and mounting it and setting the background color and all that good stuff. So now we actually end up with a compressed disk image that contains my built version of Sketch. And I can ship this to my customers. And moreover-- Thank you.
Moreover, if I go into Xcode again and I hit Build, see it just says Build Succeeded. We can look at that in more detail if we bring up the Show Detailed Results. I'm just going to hide the editor here because I don't really need that right now. Nothing happened here.
Well, that's because Xcode knows that I haven't updated Sketch, I have not updated the script, and so I don't need to do anything. If I go in here and I update the script, one of the things I can do to update the script is to say, okay, here are some of the input settings that my script takes. Well, let me go ahead and copy one of these. I'm going to not define it in the script itself, so I'm going to save this. What I'm actually going to do is I'm going to set this in the Create Disk Image Target.
So I go here and I add my own setting. And I'm just going to make this be, let's say, zero. So now when I build-- and it knows that I need to build because I modified one of the inputs. So now when I build, it runs the custom shell script.
And it's going off to finder again. And now we get a different color. And there again, if I hit Build once more, nothing happens. So this is a nice way to make sure that we only actually do work when we need to. So that's where the shell script inputs and outputs come in.
All right. So that's kind of interesting. But we can actually do something even a little bit more interesting with shell script build phases. Let me actually add another shell script build phase to the same target. You can either create a new target and have it depend on other things.
You typically do that if you have your own build system like Ant or Make. If you just want to add a little step to happen at the end of updating a target, you would just add another shell script build phase. So I'm going to go ahead and do that. I can do that in several ways. I can go to the project menu here, say new build phase.
I'm just going to control click here on the target actually. And I can say add new build phase. I'm going to add a shell script build phase, just another one. I get another inspector. It looks exactly the same because it's just another shell script. The sequence of these matters. So Xcode tries to run your shell scripts in sequence.
It will, however, respect input and output dependencies as a higher--. It's a higher order of precedence than respecting the order. In other words, if you say that the first shell script depends on the output of the second shell script, it will actually run the second one first. So it will respect the dependencies.
What I'm going to do in this case is I'm actually going to use Apple script now. So I'm going to use a tool called osascript. And what I want to do is I have another little script here that I'm going to drag into my project. I'm going to copy it in. I'm not going to add it because I don't want to copy it into my product. This is just a helper script while I'm building my product. And so here's a little Apple script.
And what I want to do is actually--. I'm going to see if I can't get a little notification here when a build completes. So what I want to do is I want to get an SMS when my build finishes. So I'm just going to go ahead and have a little script that uses Apple script to do that. Let me go ahead and just type in the name of it.
Notify completion.scpt. I don't actually need any inputs and outputs. In this case, I just want it to run at the very end. Just always run. If I need to build something, if anything needs to be updated, I just want this to always run. So we're just going to go ahead and build again. And we're already up to date with the disk image, so we shouldn't really need to do much. What we did, though, was we ran an Apple script to send me a little SMS.
And--. Okay. So now I'm going to go ahead and run this. And I'm going to go ahead and run this. Okay. So the build finished. And actually, it says here build succeeded, but you can't see that. So that's an innovative use, I guess, for build phases. So go forth and experiment with that.
All right. So can we have the slides back, please? OK, so just to summarize-- OK, slides were out of order. Just to summarize, one of the things we-- I mentioned, Xcode has a lot of built-in features. A lot of them you don't have to know about. Some of them you just have to enable, like predictive compilation.
Zero link, all this kind of stuff just works out of the box. Some are transparent, right? Zero link, predictive compilation, that kind of stuff. Others do require some setup because there are inputs that Xcode cannot know, such as with your prefix header, what's the nature of your project? What kind of headers is it that you use that are not changing? There are some things Xcode can do to analyze this. We're thinking about how to make this even more automatic in the future. But for now, set up a prefix header.
Make sure it's pre-compiled. This will save a lot of times during build and really get some good build performance. Distributed builds is another way. If I'm sitting in a coffee shop with my PowerBook, this isn't going to help me much. But if I'm in the office-- and I have a build farm that's just a couple of meters away-- this can really speed up, especially a batch build. If you have a large application and you do nightly builds, this can be a real time saver.
Custom build phases and build rules, a little bit different, right? They're not helping you speed up the things that you're already doing, such as building your app, but they help you automate some of the things that you might be having to do by hand. And even if you have existing scripts to do something, like build a disk image, we've seen how you can actually weave that into Xcode and to get Xcode to invoke it at the right time.
So explore the possibilities and use the mailing list, the Xcode help, and learn about these features, and use the great documentation that talks about these features. So for some of the more information, there's actually sample code and some example projects here at connect.apple.com. I think you've seen this address in many other sessions. Some sessions, there's the samples that we show, the demos. You can actually download those, play with that yourself. Obviously, the Xcode build system and the reference library, especially for the build settings, they're all documented. They're in the HTML pages. There's a normal Xcode help.