Archive for the 'Toolchain' Category

CocoaHeads Lake Forest videos

Monday, January 18th, 2010

It's been in editing for over a month, but now, it's finally done. I've just uploaded the second part of last month's meeting of CocoaHeads Lake Forest. There's one more to go from last month, and then I can start posting the video from this month.

This one's a lot more random than the first one. Portions of my own presentation are only part of what you'll see in this video.

More long-term, I've created a channel on Vimeo for all past and future CocoaHeads LF videos.

Ship-It Saturday: IconGrabber 2.0.1

Sunday, January 3rd, 2010

The last time I released a version of IconGrabber was only a week after Valve released Half-Life 2—way back in 2004. That game wasn't even on my radar then, since I couldn't run it on my PowerPC-based Mac!

Just over five years later, I've played all of the Half-Life 2 games and love them, and IconGrabber returns with some bug fixes and support for the new bigger icon sizes introduced in Tiger and Leopard. Version 2.0.1 is available from the IconGrabber home page.

Ship-It Saturday: Translate Text

Sunday, December 27th, 2009

Real artists ship.

Steve Jobs

With the idea that an application that's 95% finished and in active use is better than an application waiting for 100% in the seclusion of my hard drive, Ship-It Saturday is where I dust off a program that I have 95% finished, call it done, and just ship it already. I hope to make this a regular feature, although I have no idea how frequently I'll do it.

Today's winner is Translate Text, an app I wrote to make handling Adium feedback emails easier. Just select some text, then choose the service corresponding to the language it's in (or the auto-detect-language-and-then-translate service). A window will open with the original, a couple of language pop-up menus, and the translation.

Screenshot of myself invoking the French-to-English service through a contextual menu.

More information, both screenshots, and the downloads at the Translate Text web page.

Warnings I turn on, and why

Saturday, November 7th, 2009

I've started turning on most of Xcode's warning options and one warning-related build setting in all of my personal projects. I suggest you do the same.

There are some warnings I don't turn on, for any of several reasons:

  • They're inapplicable. For example, “‘Effective C++’ Violations” doesn't apply to me, because I don't write C++ code.
  • They don't help anything. An example is “Four Character Literals”, which is about 'abcd' literals for four-byte-code types such as OSType. These sacrifice something convenient for no benefit, so I leave them off.
  • They are impossible for me to fix. An example is “Multiple Definition Types for Selector”. Cocoa raises that one all over its headers, and I can't do anything about Cocoa.

The rest of the warnings, I turn on because either they make something clearer or they tell me about either real or potential (i.e., future real) bugs. They are:

  • Check Switch Statements

    Warn whenever a switch statement has an index of enumeral type and lacks a case for one or more of the named codes of that enumeration. The presence of a default label prevents this warning.

    Leave no case unhandled.

    Consider whether a default: label is appropriate for your enumeration. If your switch statement handles all possible values, cut out the default and assert that the value is one of the possible ones instead. An easy way to do this, if the enumeration values are serial and the enumeration is not part of an API you expose, is to have one extra name defined as the number of possible values:

    enum Foo {
    	kFooFoo,
    	kFooBar,
    	kFooBaz,
    	kFooNumberOfValidFooValues
    };
    

    Then, in your assertion macro, compare the value against that name:

    #define MyParameterAssertValidFoo(foo) \
    	NSAssert1((foo) < kFooNumberOfValidFooValues, @"Invalid Foo value: %d", (foo));

    When you add kFooQux, insert it above kFooNumberOfValidFooValues, and the value of kFooNumberOfValidFooValues will increase by one to fit it.

    The result is that your switch statement covers all known values for the enumeration (or you get a warning because it doesn't), and your method throws an exception (from the assertion) whenever anyone passes an unknown value.

  • Hidden Local Variables

    Warn whenever a local variable shadows another local variable, parameter or global variable or whenever a built-in function is shadowed.

    One common way to get this warning is to name a variable index, because there is a function by that name in the standard C library. That's not as much of a false positive as you may think: If you fail to declare your index variable, all your references to it will actually refer to the index function. You can see how it would be bad to send a message such as [myArray objectAtIndex:index] with this bug.

    The solution is simple: Never, ever name a variable index.

  • Implicit Conversion to 32 Bit Type

    Warn if a value is implicitly converted from a 64 bit type to a 32 bit type.

    This is most useful when converting old code to work correctly in a 64-bit architecture. Storing a pointer into an int variable (such as a reference constant) when targeting an LP64 architecture is a good way to get this warning, and rightly so.

  • Initializer Not Fully Bracketed

    Example, Here initializer for a is not fully bracketed, but that for b is fully bracketed.

    	int a[2][2] = { 0, 1, 2, 3 };
    	int b[2][2] = { { 0, 1 }, { 2, 3 } };

    This is a cleanliness warning. It also applies to structures, such as NSRect:

    NSRect warns = { 0.0f, 0.0f, 640.0f, 480.0f };
    NSRect doesNotWarn = { { 0.0f, 0.0f }, { 640.0f, 480.0f } };

    (In real code, I'm more likely to use NSZeroPoint instead of the { 0.0f, 0.0f } element above. It's harder to spell that wrong and get away with it than it is to get away with typing 9.9f, 1.1f, or 2.2f instead of 0.0f.)

  • Mismatched Return Type

    Causes warnings to be emitted when a function with a defined return type (not void) contains a return statement without a return-value. Also emits a warning when a function is defined without specifying a return type.

  • Missing Braces and Parentheses

    Warn if parentheses are omitted in certain contexts, such as when there is an assignment in a context where a truth value is expected, or when operators are nested whose precedence people often get confused about.

    Also warn about constructions where there may be confusion to which if statement an else branch belongs. Here is an example of such a case:

    	if (a)
    		if (b)
    			foo ();
    	else
    		bar ();

    In C, every else branch belongs to the innermost possible if statement, which in this example is if (b). This is often not what the programmer expected, as illustrated in the above example by indentation the programmer chose.

    This may appear to be just a cleanliness warning, but as you can see from the example, it can also warn you about code that may not flow the way you expect it to.

  • Missing Fields in Structure Initializers

    Warn if a structure's initializer has some fields missing. For example, the following code would cause such a warning, because "x.h" is implicitly zero:

        struct s { int f, g, h; };
        struct s x = { 3, 4 };

    This option does not warn about designated initializers, so the following modification would not trigger a warning:

        struct s { int f, g, h; };
        struct s x = { .f = 3, .g = 4 };

    I'm not sure why it warns about the former and not the latter, since all the members get initialized in both code examples (C99 §6.7.8 ¶21). If nothing else, this warning is good motivation for you to switch to designated initializers, which make your code more explicit about which members it's initializing.

  • Missing Newline At End Of File

    Another cleanliness warning—this one, about the cleanliness of diffs.

  • Sign Comparison

    Warn when a comparison between signed and unsigned values could produce an incorrect result when the signed value is converted to unsigned.

  • Strict Selector Matching

    Warn if multiple methods with differing argument and/or return types are found for a given selector when attempting to send a message using this selector to a receiver of type "id" or "Class". When this setting is disabled, the compiler will omit such warnings if any differences found are confined to types which share the same size and alignment.

    I don't turn this one on, because it's unnecessary. When the multiple declarations differ significantly (e.g., one method returns an object and the other returns a float), the compiler will raise the warning whether it's turned on or not. When the declarations don't differ significantly (e.g., both methods return an object), the difference won't cause a problem, so you don't need to worry about it.

    So, you should leave this one off.

  • Typecheck Calls to printf/scanf

    Check calls to printf and scanf , etc, to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense.

    The biggest reason to turn this on is that it checks your use of methods that take a nil-terminated list of arguments:

    NSArray *array = [NSArray arrayWithObjects:@"foo", @"bar"];

    That message should have a nil after the last argument. With this warning turned on, the compiler will point out that I don't.

    The ostensible main reason to turn this on is to have the compiler check your uses of printf and scanf formats. I don't use printf often (and I never use scanf), so that's not so important for me, but when I do, this could come in handy.

    Sadly, it doesn't work on NSLog calls.

  • Undeclared Selector

    Warn if a "@selector(...)" expression referring to an undeclared selector is found. A selector is considered undeclared if no method with that name has been declared before the "@selector(...)" expression, either explicitly in an @interface or @protocol declaration, or implicitly in an @implementation section.

    Another benefit of this warning is that you can use it to get a warning when you pass a wrong key to a KVC, KVO, KVV, or Bindings method. Uli Kusterer has a macro for that.

  • Unused Functions

    Warn whenever a static function is declared but not defined or a non-inline static function is unused.

    Works best with a policy of declaring any function as static that you don't need to be visible elsewhere in your program.

  • Unused Labels

  • Unused Values

  • Unused Variables

    These follow the general rule of “code you don't have is code you don't have to debug”. If you're not using a label, expression statement, or variable, you don't need it, and you will find your code clearer without it.

    You may notice that I don't turn on Unused Parameters. Most times when I trip that warning, it's a callback function or method, so I can't get rid of the argument. Rather than litter my code with bright yellow #pragma unused(foo) directives, I prefer to just turn this one off. (See my rule above about less code being better.)

Once I have turned on all of these warnings and then eradicated them from my code, I turn on two more build settings:

  • Treat Warnings as Errors

    I call this “hardass mode”.

    Remember what I said above: Almost all of these warnings represent real or potential (i.e., future) bugs in my program. Rather than tolerate them, I turn this setting on so that any time I write such a bug, I break the build.

    I haven't been able to turn this on yet in Adium or Growl, although I have turned it on in Adium's Spotlight-importer project. I do, however, turn it on in all of my solo projects.

  • Run Static Analyzer

    Activating this setting will cause Xcode to run the Clang static analysis tool on qualifying source files.

    The Clang Static Analyzer is the find-your-bugs-for-you tool you've heard so much about. This setting makes Xcode run it whenever you build. Thus, every build, you get all those warnings errors and your analysis results.

    Whenever possible, I leave this on; if there's a source file that it takes a long time to analyze (e.g., GrowlPluginController.m), then I turn it off, but only then.

UPDATE 2009-11-22: Jonathan “Wolf” Rentzsch wrote a script to turn on all of these settings in all of the projects you have open.

UPDATE 2009-11-28: Updated the entry on “Typecheck Calls to printf/scanf” after seeing that Jeremy W. Sherman pointed out a much better benefit of it in a comment on a Stack Overflow answer.

UPDATE 2009-12-05: Corrected the discussion of the index problem. You can't use index, or any other function, as a C-array subscript, so the problem only affects higher-level arrays, such as NSArray.

The peril of index(3)

Thursday, November 5th, 2009

This is mainly for Andy Finnell on Twitter, who wonders why some of us avoid naming variables index.

I pointed out that there is a function in standard C named index, and this causes one of two problems: If you declare a variable named index, you have shadowed the function and should get a warning for that; if you fail to declare the variable, you pass the pointer to the index function as your array index, which is probably not what you intended.

I say “should” there because, as he noted in his response, the shadowed-name warning is off by default. You should turn it on, because it catches bugs. In fact, the index bug is one that it can prevent.

Suppose you do name a variable index, and either you don't have the shadowed-name warning turned on or you ignore it. You initialize the variable with an index, but don't otherwise assign to it. Then, you attempt to access an object in an array by this index.

All well and good so far. index is a variable, so everything works as intended.

But then, one of several things happens:

  1. You comment out both the declaration and the usage of index, for whatever reason, but then you uncomment the usage but forget to uncomment the declaration.
  2. You update and/or merge in your version-control system, or otherwise apply one or more diffs. Usually, this works, but today isn't your lucky day: The merge breaks your source code. Perhaps it introduces conflicts, and you resolve them incorrectly. Or maybe it breaks the code silently (e.g., by merging in another branch's division of this function into two).
  3. You move the code to another location, but you forget to move half of it, or you move one half and delete another, forgetting that the declaration of index was in the code you deleted.

You had a variable named index, but now you don't—but the index function is always there*. Since there is something named index, your code compiles. It's the wrong type, so you'll get a warning, but maybe you don't notice it.

Then you run the code and it crashes. Why? Because you passed a function as the index into an array.

In the worst possible case, it was #2 and you weren't aware that this code was affected. Maybe you'd been working on something else. Anyway, since you hadn't been working on the now-broken code, you aren't testing it**, so you don't know that it's now broken.

So you ship it. You ship this index-way-out-of-range crasher. And then your user runs the code and gets the crash.

This isn't theoretical. I've had this happen more than once (fortunately, not in the hands of a user). It's one reason why I turn on the shadowed-name warning and “Treat Warnings as Errors”, and it's the reason why I never use index as a variable name.

UPDATE 2009-12-05: To clarify, this problem does not affect C arrays, as C does not allow you to use a pointer in an array subscript. It mainly affects higher-level array interfaces, such as Cocoa's NSArray.

* Assuming that, directly or indirectly, you've included string.h. If you're using Cocoa, you have (Core Foundation includes it).

** Unless, of course, you have automated test cases covering this code, and you run those.

The other way to install the Mac Reference Library

Wednesday, September 2nd, 2009

Starting with Xcode 3.2, the Xcode installer package no longer includes a local copy of any developer documentation. Instead, you have to either go to the website (which is what the redesigned Documentation Viewer window does) or download the documentation within Xcode.

Both solutions have their own problems. Reading the docs on the website can be frustrating if you're streaming or downloading something in the background. And downloading within Xcode is dubious if your internet connection is not super-fast or is flaky, because Xcode cannot resume downloads.

There is a third solution.

  1. In Xcode's Preferences, click on an info button at the right edge of the list of docsets, or right-click on the docset and choose “Get Info”.
  2. Select the row labeled “Feed URL”, and copy it.
  3. In a text editor or text field, paste the text, then edit it (it's the whole row, not just the value) down to the URL alone. There's a period (full stop) at the end of the text; it's not part of the URL, so make sure you delete that.
  4. Open the URL in a feed reader.
  5. On the most recent entry, copy the URL for the enclosure.
  6. Paste it into your download manager or browser Downloads window.

Assuming your download manager or browser supports resuming downloads, you now have a way to pause the download for any reason that may arise, and resume it from that point.

Of course, then you have to install what you downloaded. You're probably not used to seeing xar files (although you've seen more than you think—flat packages are based on them), so you may not know what to do with them.

  1. Pop open a terminal. cd over to where you downloaded the xar archive.
  2. pushd /Developer/Documentation/DocSets
  3. sudo xar -xf ~+1/filename.xar
  4. Once that finishes, delete (or back up) the xar file.

Xcode's Documentation Viewer window (or your browser) should suddenly be much quicker.

Git fetch weirdness explained

Sunday, August 30th, 2009

In another tussle with Git, I performed the following sequence of commands:

  1. cd mach_star-rentzsch
  2. git fetch (from GitHub)
  3. cd ../mach_star-boredzo
  4. git fetch rentzsch (in this context, my git-remote alias for ../mach_star-rentzsch)
  5. git merge rentzsch/master

Step 5 failed with “Already up-to-date”.

What? I just fetched! I should have new commits to merge in!

Nope. For one thing, this output from step 4:

From ../mach_star-rentzsch
 * [new branch]      master     -> rentzsch/master

seems to mean “OK, here's a new name for the master branch of that other repository, but we didn't actually bring in any commits”.

The reason it didn't bring in any commits is because git fetch apparently only fetches commits that are ancestors of the source repository's current HEAD. In English: git fetch cares what you have checked out in the source repository.

It's because I had fetched in step 2, and thereby not changed my HEAD, that step 4 did not see anything new to fetch. I don't know why it works that way, or why they consider it useful.

Anyway, the “correct” sequence of steps is not much different: git pull, not fetch, in step 2 above. Or use Mercurial, which I've found makes a lot more sense in general.

It's entirely possible that I've figured this out the wrong way, so take this entire explanation with a grain of salt.

Documentation supplement: System vs. Target in PackageMaker

Saturday, July 25th, 2009

The PackageMaker user guide doesn't explain the difference between “system” and “target” in PackageMaker's pop-up of Requirement criteria:

For example, “System OS Version” versus “Target OS Version”.

So now that I've figured it out, I'll fill in the gap for you.

  • System is the volume that the installing user is booted from.
  • Target is the volume that the Requirement is testing. (Your Requirements are applied for each volume.)

So if you want to make your Installer package installable to any bootable volume, make it installable to any volume and add a Requirement for Target OS Version. (Another method you may try is “File Exists on Target: /Library”.)

If, on the other hand, you want to make your Installer package installable to the Home folder, make it installable only to the Home folder and add a Requirement for System OS version.

How you can get this wrong

If you make your package installable to the Home folder but test the Target OS Version, your package is broken: It does not work for those of us who have our Home folder on a non-bootable volume (in my case, separate from two other, bootable volumes). You must use the System OS Version, and hope for the best.

If you make your package installable to any volume but test the System OS Version, your package is broken: The user will be able to install your software to a volume whose version of the OS cannot run it. You must use the Target OS Version.

As far as I know, there's no way to make a package that does both properly, since the choice of any volume, booted volume only, or Home only is per-package, not per-choice or per-contents.

Dramatic twist ending

The above is good if you're targeting Leopard. If you still support Tiger, there's a twist. (Obligatory video link.)

GrowlMail is a good example. As a Mail bundle, it requires a couple of user-default settings to work. That makes installing to /Library pointless, because the settings will only be set for the user who installed it, so it won't work for any other users on the system.

Leopard allows installing to ~, so that's easy: I use System OS Version, as I suggested above.

But Tiger's Installer can't install to ~. The same Installer package that works on Leopard does not work on Tiger (I even tested with earlier betas—it has never worked in any 1.1.6 beta). I don't know how nobody noticed this, not even our Tiger testers.

The Installer package for Tiger must target /Library, since I can't do the proper thing on that OS version, so I must make separate GrowlMail packages for Tiger and Leopard.

  • The Leopard package installs to ~/Library and uses System OS version, as I suggested above.
  • The Tiger package installs to /Library and uses both Target OS Version and System OS Version:
    • If the user is running on 10.5 or later (System OS Version ≥ 10.5.0), the package tells them to use the other package. (The other package has a similar check.)
    • If a destination volume does not have 10.4 or later installed on it (Target OS Version < 10.4.0), the package tells them they can't install there.

This is what you'll find in Growl 1.1.6b4 and 1.1.6 final. It'll go away in 1.2, since we're dropping Tiger then.

My documentation viewer

Wednesday, July 15th, 2009

This is what I use to view Apple's documentation:

My web browser.
QuickTime/H.264, 960×538, 1.3 MiB

The application is OmniWeb. I have a series of entry points bookmarked in the (hidden) Favorites bar:

(Someday, Carbon will perish from the list, the ones after it will move up, and another framework—probably either QTKit or Core Animation—will become the new ⌘8.)

And yes, those are all file: links.* Your web browser is perfectly capable of displaying web pages stored locally, and that's all the Apple documentation is: locally-stored web pages.

With this arrangement, I can get to the reference information I'm looking for faster, and I can have multiple references (even multiple definitions) open at once because OmniWeb supports tabbed browsing.

Here are some other pages worth bookmarking:

You can use these and other bookmarks with a nice feature of OmniWeb which has also, more recently, appeared in Google Chrome: You can type any substrings from your bookmarks' names and URLs into the address bar, separated by whitespace, and it will know what you mean. So, for example, I can type “kt kit”, and OmniWeb knows I mean “QuickTime Kit”; I simply hit return, and it takes me to that framework reference.

UPDATE 2009-09-07: Updated links to Snow Leopard's docset name (where possible).

* On Leopard, change the docset name to com.apple.ADC_Reference_Library.CoreReference.docset.

Symbolicator 1.0.1

Friday, April 24th, 2009

It's two-thirds faster, it works on more crash logs, and thanks to Augie Fackler, it's now available from the Python Package Index. This means that you can just do this:

sudo easy_install symbolicator

If [you run a custom build of Python and] you don't want to install setuptools, you can always get the Symbolicator from its webpage.

Oh, and there will be a 1.0.2 to fix some issues. At the moment, I'm going back to working on Growl things, so I won't start on those for awhile; if you want to beat me to them, fork the project on Bitbucket, commit your fixes, and send me a pull request. (Make sure you co-ordinate your efforts on the ticket! I don't want to have to choose from two independently-developed fixes.)

UPDATE 2009-04-25: Clarified above that installing setuptools is only necessary if you have installed Python yourself. Thanks to Augie for pointing this out in his comment.

How to make hg merge, hg resolve use FileMerge

Wednesday, April 22nd, 2009

Put this in your ~/.hgrc file:

[merge-tools]
filemerge.executable=opendiff
filemerge.args=$other $local -ancestor $base -merge $output
filemerge.gui=True

(Based on the original description of and Matt Mackall's comment on a Mercurial bug about merging.)

New tool: The Symbolicator

Sunday, April 19th, 2009

The most significant behind-the-scenes change in Growl 1.1.5 is that we now build using the DWARF-with-dSYM debug symbol format.

Now, we distribute a fully-stripped executable for every release—even betas—and we keep the dSYM bundles on hand separately. The idea here is that we can use the dSYM bundles to obtain symbolic information (function name, filename, and line number) for the bare addresses in users' crash logs.

Unfortunately, there are no good tools for symbolicating Mac OS X crash logs. If this were an iPhone app, we could just drag the crash logs into the Xcode Organizer window, but Xcode doesn't do this for Mac crash logs.

I tried all the other tools as well, and every one of them had one of two problems:

  1. Didn't work at all
  2. Needed the dSYM bundle and main bundle to be next to each other on disk

#2 is not a deal-breaker, but it is a hassle.

So I wrote my own symbolication tool.

The Symbolicator is a Python script that:

  1. Reads in a crash log.
  2. Finds the necessary dSYM bundles using Spotlight. (This means that you can put the dSYM bundles anywhere you want, and as long as Spotlight can find them, the Symbolicator will be able to use them.)
  3. Uses dwarfdump to extract the relevant symbol information.
  4. Replaces the address offsets with the symbol information (just like CrashReporter does when it has debug symbols to work with).
  5. Writes the symbolicated log to stdout.

Use it like this:

% symbolicator < unsymbolicated.crash > symbolicated.crash

Or use ThisService to make a service out of it.

If you want to see it in action right now, download Growl 1.1.5b1 and the corresponding dSYM bundles from the Growl beta page. Make Growl crash (killall -TRAP works well), then unpack the bundles and use the Symbolicator on the crash log. (If you unpack the bundles first, CrashReporter will symbolicate the log before you even get to it. Handy, unless you're trying to test the Symbolicator. ☺)

To make this work on your own app, follow the instructions in the aforementioned ADC article, then make sure you archive the dSYM bundles for every release, including betas. On Growl, I added code to our Release Makefile for this.

Note: If your app is closed-source, you should not put your dSYM bundles on your website, since the debug symbols are arguably trade secrets (information about your source code). Keep them locally, perhaps on a flash-memory drive. Disclaimer: IANAL.

Manpage Monday: PlistBuddy(8)

Monday, April 13th, 2009

PlistBuddy(8) is a command-line tool for editing property-list files, with a deeper reach than plutil defaults:

Entries consist of property key names delimited by colons. Array items are specified by a zero-based integer index. Examples:

  :CFBundleShortVersionString
  :CFBundleDocumentTypes:2:CFBundleTypeExtensions

Jonathan “Wolf” Rentzsch, in his del.icio.us bookmark for the manpage, says that PlistBuddy is “always at /usr/libexec/PlistBuddy on modern systems”.

hg precommit hooks and the Clang Static Analyzer

Friday, April 3rd, 2009

Fraser Speirs has a post about configuring your Git repository to vet commits with the Clang Static Analyzer.

The idea of pre-commit hooks is that you get to run a script before the commit happens. Depending on the result code of the script, the commit will either proceed or be aborted.

I wrote a wrapper around the scan-build tool, so that I could run the analyzer by hand with my preferred options at any time: …

The --status-bugs flag is the trick here: it makes scan-build return a non-zero status code if it detects bugs. That’s what we want with Git pre-commit hooks: a non-zero status indicates a possible bug, and that causes the Git commit to be aborted.

Mercurial, of course, has the same feature. The hg book has instructions; I'll show you how I set up my repository to do this.

First, I created a shell script named RunClang.zsh:

#!/bin/zsh -f
~/bin/checker-latest/scan-build \
    -checker-cfref -warn-objc-methodsigs -warn-objc-missing-dealloc -warn-objc-unused-ivars \
    --status-bugs -o checker.out \
    xcodebuild -configuration $1

Next, I added my precommit hook to the repository's .hg/hgrc file:

[hooks]
precommit.RunClang = ~/bin/RunClang.zsh Development

Here's what an example session looks like:

% echo 'Testing precommit testing hook' >> test.txt
% hg ci -m 'Testing precommit testing hook'
[churn churn churn]
** BUILD SUCCEEDED **
scan-build: 17 bugs found.
scan-build: Run 'scan-view [snip]/growl-boredzo-precommit-test/checker.out/2009-04-03-2' to examine bug reports.
abort: precommit.RunClang hook exited with status 1

(255)% hg log --limit=1
changeset:   4188:b208862a586d
tag:         tip
user:        Peter Hosey
date:        Fri Mar 13 05:40:09 2009 -0700
summary:     Fix encoding of the Norwegian Growl-WithInstaller strings file.

17 bugs—mostly leaks. Glad I didn't commit this test file!

So now you know how to have Mercurial block you from committing if the clang checker can find bugs. This should also work for your unit tests. And if you have 100% test coverage (lucky!), you can combine them: have scan-build build your test-bundle target. Then, the hook will prevent the commit if the checker can find bugs or any tests fail.

I don't think I'll actually use this set-up, though.

First, in order to find bugs, you need to build your entire main product. Any significantly large program is going to take a long time to build and analyze—Growl, for example, takes about one-and-a-quarter minutes for a clean build. Even committing to a Subversion repository over dial-up was quicker.

More significantly, precommit hooks like this interfere with patch queues. The mq extension implements patches as mutable commits, so any qnew or qrefresh will run the hook. It would be useful on qfinish, but it's just annoying on qnew and especially qrefresh, as the all-too-frequent builds thwart rapid iteration.

So, if you use patch queues, this won't work for you. But, if you don't, then this should work as well in Mercurial as it does in Git.

Adding Growl support to Mercurial

Wednesday, April 1st, 2009

Add this to your ~/.hgrc file:

[hooks]
changegroup.growl = ((echo "$HG_URL" | grep -vF 'file:' > /dev/null) && growlnotify -n Mercurial 'Pull successful' -m "Pulled at least $(hg log --template='\n' -r$HG_NODE:tip | wc -l | sed -e 's/ //g') changesets from $HG_URL") || true
outgoing.growl = (test "x$HG_URL" '!=' 'x' && growlnotify -n Mercurial 'Push successful' -m "Pushed to $HG_URL") || true

It's not perfect: Both notifications share the same notification name. As long as that isn't a problem, this works fine.

Manpage Monday: afconvert(1)

Monday, November 24th, 2008

afconvert is a command-line utility that uses Core Audio to convert audio files from one format to another.

The manpage is actually pretty sparse (like the rest of the Core Audio documentation); you're better off reading the command-line help:

% afconvert -h

A simple example is to convert an AIFF file to AAC in an MPEG-4 file:

% afconvert -f 'm4af' -d 'aac ' -b 98304 Recording.aiff

The resulting file is 96 kbps (98,304 bps) and is named “Recording.m4a”.

UPDATE 2008-11-26: Corrected permalink and title to include the section number.

New service: Insert Mac OS X Build Number

Friday, November 21st, 2008

File: InsertMacOSXBuildNumber.zip

A service that inserts the build number (for example, 9F33) of your current Mac OS X installation.

The main purpose for this is so that, when filing bugs in Radar, you can precisely specify which build of Mac OS X you're running. (Especially if you're running a pre-release build of a future version of Mac OS X.)

I created it with ThisService, of course.

(And yes, I am also working on tonight's Framework Friday post.)

New Mercurial extension: Bitbucket Extension

Monday, September 29th, 2008

If you've ever used Bazaar, you know that one of its features is a shorthand URL scheme for referring to Bazaar repositories on Launchpad. For example, to clone Sparkle's main repository:

% bzr clone lp:sparkle

I've created an extension that enables you to do the same thing in Mercurial with Bitbucket repositories.

% hg clone bb://boredzo/bitbucketextension

The Bitbucket extension adds two URL schemes: bb:, and bb+ssh:. The bb: scheme communicates with Bitbucket over HTTPS; you can guess what bb+ssh does.

(Note: As of Mercurial 1.0.2, you must include the double-slash, because hg pull will interpret the URL as a relative path without it.)

svn merging is full of fail

Thursday, August 7th, 2008

[SCENE: adium/branches/summer_of_code_2008/unit_testing. I'm merging changes from trunk to this branch.]

svn --version
svn, version 1.5.0 (r31699)
   compiled Jul  5 2008, 04:29:25

Copyright (C) 2000-2008 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - with Cyrus SASL authentication
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme
* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
  - handles 'http' scheme
  - handles 'https' scheme

___
svn up
At revision 24694.
___
svn st
?      Docs
?      DoxyCleaned
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/outgoing_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/incoming_icon.png

(~ = “versioned item obstructed by some item of a different kind”)

rm -Rf Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
___
svn up Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
[snip]
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing 
--- Merging r23937 through r24694 into 'branches/summer_of_code_2008/unit_testing':
[snip]
Conflict discovered in 'branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options: e
[I correct the plist to match what trunk has.]
Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (h) help for more options: r
___
plutil -lint branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj: file does not exist or is not readable or is not a regular file
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities\ Framework/AIUtilities.framework.xcodeproj/project.pbxproj
branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities Framework/AIUtilities.framework.xcodeproj/project.pbxproj: OK
___
cd branches/summer_of_code_2008/unit_testing   %~/Projects/@otherpeoplesprojects/adium(0)
___
xcodebuild -configuration Development
[snip]
** BUILD SUCCEEDED **
___
svn ci
Sending        unit_testing
[snip]
Transmitting file data .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit failed (details follow):
svn: Checksum mismatch for '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/.svn/text-base/Emoticons.plist.svn-base'; expected: '109d361ce47a49a66e53e98412251398', actual: 'a21c8336d1f839b9aaa8a2795a7e25f5'
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.2.tmp'
___
cd Resources/Emoticons
___
rm -Rf MSN.AdiumEmoticonset
___
svn up MSN.AdiumEmoticonset
A    MSN.AdiumEmoticonset
[snip]
Updated to revision 24694.
___
cd ../..
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing
svn: Working copy path 'Frameworks/Adium Framework/Source/AISharedAdium.h' does not exist in repository
___
svn merge -r23936:24694 trunk/Resources/Emoticons branches/summer_of_code_2008/unit_testing/Resources/Emoticons
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset
___
svn pl Emoticons.plist
___
svn st
___
popd
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
--- trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist       2008-06-26 20:50:51.000000000 -0700
+++ branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  2008-08-07 11:20:27.000000000 -0700
@@ -367,7 +367,7 @@
                        Name
                        Cool
                
-               I don't know.gif
+               I Don't Know.gif
                
                        Equivalents
                        
[Um, OK. What happened to all the differences it had from trunk that I had merged over? Suddenly, almost all the differences are gone, and the files are nearly the same!]
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008                %~/Projects/@otherpeoplesprojects/adium(0)
___
svn ci unit_testing
Sending        unit_testing
Transmitting file data ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit succeeded, but other errors follow:
svn: Error bumping revisions post-commit (details follow):
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.3.tmp'
___
svn st            %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
?      svn-commit.2.tmp
?      svn-commit.tmp
?      svn-commit.3.tmp
 ML    unit_testing
?      unit_testing/Docs
?      unit_testing/DoxyCleaned
  L    unit_testing/Release
  L    unit_testing/Release/openUp
  L    unit_testing/Release/Artwork
M      unit_testing/Release/Makefile
  L    unit_testing/Frameworks
  L    unit_testing/Frameworks/libgmodule.framework
  L    unit_testing/Frameworks/libgmodule.framework/Versions
[snip]

(L in third column = Locked)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
[So I get to manually clear the locks. Yay!]
___
tar cjf unit_testing.tbz unit_testing
___
cd unit_testing   %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
___
svn st | grep -Fw L | head
  L    Frameworks
  L    Frameworks/JSON Framework
  L    Frameworks/JSON Framework/Tests
A L+   Frameworks/JSON Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/types
A L+   Frameworks/JSON Framework/Tests/format
A L+   Frameworks/JSON Framework/Tests/rfc4627
  L    Frameworks/JSON Framework/Tests/Examples
  L    Frameworks/JSON Framework/Tests/Examples/JSONChecker
A L+   Frameworks/JSON Framework/Tests/jsonchecker
svn: Write error: Broken pipe
___
ls Frameworks/.svn
dir-prop-base format          prop-base/      text-base/
entries         lock            props/          tmp/
___
ls Frameworks/.svn/lock
Frameworks/.svn/lock
___
rm Frameworks/.svn/lock
___
svn st -N Frameworks
  L    Frameworks/JSON Framework
  L    Frameworks/libintl.framework
  L    Frameworks/libpurple.framework
  L    Frameworks/libmeanwhile.framework
  L    Frameworks/LMX.framework
R L+   Frameworks/Sparkle.framework
  L    Frameworks/AutoHyperlinks Framework
  L    Frameworks/libgobject.framework
  L    Frameworks/OTR.framework
  L    Frameworks/libglib.framework
  L    Frameworks/ShortcutRecorder
  L    Frameworks/libgthread.framework
  L    Frameworks/AIUtilities Framework
  L    Frameworks/OCMock.framework
  L    Frameworks/PSMTabBarControl.framework
  L    Frameworks/RBSplitView
  L    Frameworks/Growl-WithInstaller.framework
  L    Frameworks/Adium Framework
___
svn st -N Frameworks/JSON\ Framework
  L    Frameworks/JSON Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
rm Frameworks/JSON\ Framework/.svn/lock
___
svn st -N Frameworks/JSON\ Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
find . -name lock -print0 | xargs -0 rm
___
svn ci
svn: Working copy '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
rm ../.svn/lock
___
svn ci
Sending        unit_testing
[snip]
svn: Commit failed (details follow):
svn: Directory '/branches/summer_of_code_2008/unit_testing' is out of date
___
svn up ..
svn: Failed to add file '../unit_testing/Frameworks/JSON Framework/dmg.sh': a file of the same name is already scheduled for addition with history
___
rm Frameworks/JSON\ Framework/dmg.sh 
___
svn up
D    Frameworks/libgmodule.framework/Versions/2.0.0/Resources/English.lproj
G    Frameworks/JSON Framework/Tests/Types.m
svn: Failed to add directory 'Frameworks/JSON Framework/Tests/json.org': a versioned directory of the same name already exists
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org/1.json
A  +   Frameworks/JSON Framework/Tests/json.org/2.json
A  +   Frameworks/JSON Framework/Tests/json.org/1.plist
⋮
___
find . -name lock -print0 | xargs -0 rm
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org
⋮
___
cd ..
___
mv unit_testing unit_testing-fail
___
svn up unit_testing
A    unit_testing

With this fresh working copy, I'm able to build, so I believe I'm finally done.

New tool: sednames

Friday, June 20th, 2008

What if you could use sed to rename files?

Well, now you can.

sednames is a utility that lets you specify a program for sed on the command-line, which it then uses to rename the files that you also specify on the command-line.

The twist is that, unlike other batch-renamers, sednames also supports your VCS. For the most common cases (svn, hg, bzr, and git), there's a --vcs option:

% sednames -e 's/Replace/Mix/' --vcs=hg *

This command-line will use Mercurial (hg mv) to rename every file by replacing “Replace” with “Mix”. Of course, if a name doesn't contain “Replace”, then that name will be unchanged and sednames is smart enough to not try to rename that file.

Not only that, but just in case you're using some oddball VCS (or you want to copy instead of rename, or something), sednames supports using any program to rename your files, as long as it accepts both the before and after names in its arguments. The --help output is more specific, but to put it simply, it works similarly to find's -exec option.

You can download the current revision directly, or use the Mercurial repository to follow or contribute to its development.