Sunday, January 18, 2015

Doing an Xcode build from within Sublime Text 3

It is possible to create a comfortable build environment for Xcode projects with Sublime Text 3. Here are step by step instructions for how I did it.

First issue - achieving a build.


Sublime can invoke command line programs to do a build. Xcodebuild is a command line tool that comes with Xcode that does it. To do a build, sublime needs a sublime-project file with a recipe in it.

It's a json file that you put in the root folder of your project, and it should look something like this, assuming that the xcodeproj file is in the same folder, and is named MyProject.xcodeproj.

You'll notice that I am overriding SYMROOT and OBJROOT because I am doing an out-of-source build into ~/build. You can omit that if you are fine with xcode putting build products in the usual spots. I greatly prefer an out of source build for reasons, which include being able to make a build utterly clean by simply deleting the build folder.

Command-B will invoke a build, and Control-C will stop it instantly, which is awesome.

MyProject.sublime-project

{
    "folders": [
        {
            "follow_symlinks": true,
            "path": "."
        }
    ],
    "build_systems": [
        {
            "name": "Xcode Build",
            "cmd": [ "xcodebuild",
                     "-project", "$project_path/$project_base_name.xcodeproj",
                     "-configuration", "Debug",
                     "-scheme", "MyProject",
                     "SYMROOT=~/build/Products/Debug", "OBJROOT=~/build" ],

            "variants": [
                {
                    "name": "Xcode Run Debug",
                    "cmd": [ "~/build/Products/Debug/$project_base_name.app/Contents/MacOS/$project_base_name" ],
                },
                {
                    "name": "Xcode Build Release",
                    "cmd": [ "~/local/bin/xctool",
                             "-project", "$project_path/$project_base_name.xcodeproj",
                             "-configuration", "Release",
                             "SYMROOT=~/build/Products/Release", "OBJROOT=~/build" ],
                },
                {
                    "name": "Xcode Run Release",
                    "cmd": [ "$~/build/Products/Release/$project_base_name.app/Contents/MacOS/$project_base_name" ],
                }
            ]
        }
    ]
}


Second Issue - Optional - excess verbiage


The next issue you will notice is that when you do a build, there is an excess of verbiage that appears in the build window, making it exceedingly difficult to find real problems. You might be fine with that, or have magical regexs to find warnings and errors, and if so you can skip this step.

I used git to clone Facebook's xctool, and built it following the instructions on their page.

https://github.com/facebook/xctool

After building, a build folder appeared. A couple of layers deep there is a Products/Release directory. I have a particular place I store /bin and /lib files, and I copied xctool out of Products/Release/bin to my bin, I copied the dylibs from Release/lib to /lib, and I copied the Release/reporters folder to a /reporters folder that I created as a sibling to my /bin and /lib. (To be clear, the result was /reporters/stuff, not reporters/reporters/stuff.)

I changed the cmd in the project from xcodebuild to xctool.

Finally, I entered this

os.environ["PATH"] = os.environ["PATH"]+":/my/local/bin"

to point to my local bin from within Sublime's python console. This is necessary in my case because I haven't yet figured out how to communicate my shell path to launchctl so that the path Sublime knows about is the same as my shell path.

Inconveniently I have to redo that every time I start Sublime, but I'll work out the right answer eventually.

Now, builds are much tidier to read (my eyes are much happier), and the results include timings, which is nifty.


Third Issue - Sublime's Build Window is Pretty Much Useless


This issue could be PEBCAK (Problem Exists Between Computer and Keyboard), but some searching around places like Stack Overflow make me think otherwise.

In a nutshell the build window vanishes if you do pretty much anything interesting, like touching the mouse or keyboard. Also, the only operation it supports during its brief existence is scrolling. Heaven forbid you want to search for the word warning, or error, or something like that.

Also I was not for the life of me able to make the Sublime "go to next error" regex stuff work. It was mostly a world of hang, so I looked for an alternative.

I installed sublime-text-2-buildview, which despite it's name, works fine in Sublime Text 3. It captures the output from the ephemeral build window and redirects it to a normal text editing window, once again, a small bit of awesome. (I used Package Control to install it.)


Bonus Round - xcconfigs


For the bonus round, I moved all of my project settings out of the xcodeproj and into an xcconfig file so that I could control things like search paths, preprocessor settings, and warning levels from the comfort of my text editor.

I followed this tutorial - http://www.jontolof.com/cocoa/using-xcconfig-files-for-you-xcode-project/, setting my files up exactly as he suggests. Note that if you are copying and pasting a setting and it doesn't show up properly in the text editor (ie, if they paste as only the name of the setting without a value), it's because Debug and Release are different. You need to select them individually and paste them into the appropriate xcconfig file.

I didn't rest until all my settings showed up as coming from the xcconfigs.


Conclusion


Once you've got all this set up, a command line build is equally easy to achieve. I put together a little sh script for myself that contains roughly the contents of the cmd field above, and it worked a treat.

Especially when I need my laptop's battery to last a long time, building in Sublime is a huge win.

There's obviously some rough edges in this set up, particularly the environment setting if you use xctool, and it's hard to believe that the build window is as terrible as it seems - have I missed something obvious? Nonetheless, I hope you find this helpful, and would welcome suggestions or improvements from anyone.







12 comments:

  1. Regarding $PATH in Sublime, are you looking for something like this: http://apple.stackexchange.com/questions/106355/setting-the-system-wide-path-environment-variable-in-mavericks

    ReplyDelete
  2. I worked through all the techniques on that page, and the linked page as well... Several of them work for terminal shells, but none of them affected the path Sublime sees (and presumably any .app), even after restarts. I've been checking by opening the console, and then doing an os.getenv("PATH").

    ReplyDelete
  3. Yosemite or Mavericks? I'm still on Mavericks, but adding to /etc/launchd.conf worked there.

    SHELL:
    $ cat /etc/launchd.conf
    setenv PATH /usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin

    SUBLIME:
    >>> os.getenv("PATH")
    '/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin'

    ReplyDelete
  4. I probably restarted my machine for it to take effect, but can't remember ... this was many months ago.

    ReplyDelete
  5. Yosemite. Not even /etc/launchd.conf shenanigans + restart worked.

    ReplyDelete
  6. Yet one more reason for me to delay my upgrade, I guess :P Sorry!

    ReplyDelete
  7. Thanks for the helpful guide!

    The regex isn't too bad and allows you to F4 between errors. Here's an example for xcodebuild building swift code (which uses swiftc to compile)
    "file_regex": "(/Users/dale/ProjectName.*swift):([0-9]+):([0-9]+):(.*)"

    This works for errors that look like this
    /Users/dale/ProjectName/Foo/AppDelegate.swift:14:26: consecutive declarations on a line must be separated by ';'

    You just have to know a little bit of regex syntax, and what file_regex is asking for. It wants 4 things in groups (i.e. parentheses): (1) file name, (2) line number, (3) column number, and (4) error string. So write a regex for your error strings that are output then put parentheses around each of those 4 items. It's not too bad to generalize it a bit more from my example for an even better regex

    ReplyDelete
  8. To use something besides F4 to use the next_result command, the below keymap entry does the trick (I changed it to enter)
    { "keys": ["enter"], "command": "next_result", "context":
    [{"key": "panel", "operand": "output.exec"}, {"key": "panel_visible", "operator": "equal", "operand": true}]
    }

    As a sidenote, I am using xcpretty since it just does formatting and it is not its own build tool like xctool

    ReplyDelete
  9. @Rob Pieké ~ the environment variable bugs should all be fixed in El Capitan. Time to upgrade ;)

    ReplyDelete
    Replies
    1. 10.10.5 is the first Yosemite build where I can get a rock solid WiFi connection to my router. I don't dare upgrade again :)

      Delete
  10. @Dale Matthews - excellent guidance, thanks a ton!

    ReplyDelete
  11. A little follow up- xcpretty is here: https://github.com/supermarin/xcpretty

    ReplyDelete