Previously when integrating existing library code with new projects I would have simply copied the necessary groups from from Xcode project to another. Then I would choose to copy the files to the new project to be sure that they got included in the source tree. Otherwise the project would not build for other people accessing the same source control management (SCM) server.
Now with Xcode 4 this technique no longer works. You simply cannot drag groups between workspaces. The question that interests us today is how we can add an existing GitHub project to our own.
Let’s see if we can figure out a simple and duplicatable method to achieving this. It would make our lives much easier to not having to duplicate component code for each new project.
First order of business is to create a new project for our experiment. Git it so tightly integrated that you can even create a new local repository just by checking a box.
This gives us a project where the only initial modified file is the project.pbxproj contained in the .xcodeproj bundle. This is shown by the M (for modified) next to the project root.
There might be an additional step necessary for totally blank systems that I am missing because I already set my name and e-mail address as global git preference on this machine. As a reminder, this is how that’s done in terminal:
git config --global user.name "Your Name Comes Here" git config --global user.email firstname.lastname@example.org
Now for our actual test case. Let’s assume that we want to be using the NSAttributedStrings+HTML project in this new project of ours. Christopher Pickslay kindly gave me the necessary hint as to how this would be done. The necessary steps have to be executed in the terminal.
Change into the project root folder.
The URL for the remote repository can be found on the GitHub page. On your own repos you will see an SSH link that has read and write access. Then there’s a HTTP link and a GIT Read-Only link. You should generally avoid the HTTP link as it is less efficient when transferring the files as it’s basically the same as scraping a website, whereas the GIT and SSH links have some optimizations built in that speeds up the copy process. Since GIT always transfers the entire past history of a repository you don’t want to waste resources.
So I might use the SSH link, you should use the GIT read-only link. Though I found by testing this that the SSH link even works for other people to read the files. So SSH is ideal for me: I can read/write, other people can read. And if I update something in the submodule I can commit and push it to the master.
With this link we can add the repo as a submodule into our project root, again in terminal.
git submodule add email@example.com:Cocoanetics/NSAttributedString-Additions-for-HTML.git
You will see that git clones the repository into a sub-folder that has the same name as the project minus the dot git extension. Such a clone is a fully fledged repository with it’s own management and history, independent from the repo that Xcode created for us previously.
If you enter the NSAttributedString-Additions-for-HTML folder and try git remote -v you can see that a remote link has been also set up. At the same time the project root does not have any.
Now as a final step we want to access this code from our Xcode project. There are two possible scenarios: either we want to access a product of the submodule, i.e. link to a library that this is producing. Or we just want to add specific classes.
First thing is to add the additional frameworks required: CoreText.framework and MediaPlayer.framework. This is done by clicking the project root, going to “Build Phases” and in the “Link Binary With Libraries” you choose these. Oddly Xcode puts these references into the project root, so we move them to be together with their siblings in the “Frameworks” group.
NSAttributedStrings+HTML does not have a library product, so we make a new group “Externals” and via the context menu we “Add Files to ..”
We choose the NSAttributedStrings-HTML subfolder to first import everything. Since the sub-module-project also contains files to make a demo project and unit tests we then have to remove all unnecessary files, always only removing the reference. We don’t want to remove the files from disk because this would mess up the pristine status of the cloned submodule. But if they are not visible in the Xcode project then they get ignored anyway.
In this specific example we remove all files that have Test in their name (the unit tests), the info.plists, the .git and .gitignore, the xcodeproj, the “Other Sources” and “Resources” groups and the files named “DemoSomething” in Classes. You might get a duplicate symbol error by the linker, this is caused if you did not remove all remnants of the demo app. “Remove References Only”, don’t forget that. In the end we only have the Classes and the LICENSE file.
At this stage you can use all the methods contained in the submodule in your project. To test that the following lines in the app delegates didFinishLaunching prove to be working. No extra paths are required to be set up in the target.
NSString *hex = @"FF"; NSInteger i = [hex integerValueFromHex]; NSLog(@"%d", i);
For the other scenarios where you want to link to a library product of the submodule-project you can add the other project’s xcodeproj file to either your outer project or – something that’s new in Xcode 4 – to the work space. Then you should be able to select the library product in the “Link Binary with Libraries” screen we touched upon earlier.
The link to the submodules is kept in .gitmodules in the project root.
[submodule "NSAttributedString-Additions-for-HTML"] path = NSAttributedString-Additions-for-HTML url = firstname.lastname@example.org:Cocoanetics/NSAttributedString-Additions-for-HTML.git
A word of caution: if you change code in the submodule, make sure that you commit and push these to the master repository on GitHub. Or else you might produce a state where other users of your main project cannot update because a newer version of the submodule is required than is available online. So first commit the submodule, then commit the outer project changes.
To update the submodule you issue this command in the project root:
git submodule update
Like most people I am new to Xcode 4 and just as new to git. So if you think that this guide is incomplete or wrong, please let me know in the comments.