Terminal at My Fingertips

August 19th, 2005

IMG 0281 1If you’re like me, you take great comfort in the presence of the Terminal in Mac OS X. I love a great GUI, but the fact is that many tasks on the Mac are still best accomplished from the behind the wheel of a shiny new pseudo-tty.

Usually I get the need for command-line access to an object just as I’m browsing near it in the Finder. In the bad old days, I would laboriously open the Terminal, type “cd “, then drag a copy of the item I’m interested in to the Terminal, and if it was a file, torturously edit out the leaf-name part of the path before hitting return!

Grr! I’m getting stressed out just thinking about it! Things have gotten better for me since I sat down and wrote a handy script to accomplish all of this for me. My Terminal At Location script does just what its title implies: opens a new terminal window with the current path set to the object of your current Finder-fixation. You can download this script from the Red Sweater AppleScript Page.

I have this script configured as an application-specific keyboard shortcut for the Finder. Whenever I need to “switch to command-line mode” I just hit Cmd-Shift-T and I’m off-and-running. If you don’t already have an application-specific keyboard shortcut tool for quickly running AppleScripts, then allow me to give my unbiased (ahem) endorsement of FastScripts. Create a Finder-specific scripts folder, drop the script in, and select it from the FastScripts menu while holding down the Cmd key to quickly set your desired shortcut.

If you spend a lot of time switching between the Finder and the Terminal, you will not forgive yourself for failing to set up a script like this sooner.

Update:Version 1.0.1 of the script fixes a bug that caused terminal parsing errors for paths with funny characters in them.

IMG 0281IMG 0281

Automatic Build Sub-Versioning in Xcode

August 14th, 2005
Editorial note: This entry describes at length the process I went through to achieve the “perfect” script for determining and inserting the Subversion revision number into my builds. If you’d rather skip the details and jump to the copy/pasteable script, click here.

Around the time I first tried Subversion, I noticed an interesting mailing list post from Axel Andersson, which gave great script for using Subversion’s “revision number” as the “build number” for an Xcode project. (Note the script in the above link is unusable, because the mailing list archive stripped out some meaningful variable expansions).

For those of you aren’t familiar with Subversion, it uses a simple system of versioning, in which every commit to the repository advances the “revision number” by one. So you start off at 1 and every change you make bumps the digit. (Coming from a CVS mindset, this makes me feel a little oogy, because I’m used to committing files one at a time, even though they’re all part of a single “intent,” but I’m getting over it).

The Subversion revision number is a great candidate for the “build number” that goes next to your application’s version in the standard about box. Instead of an arbitrary number that confirms to you and users that one version is indeed later than another, this number allows you to jump back with fair certainty to the exact sources that constituted a user’s build, even if you were too lazy to go out of your way to introduce a tag corresponding to that instant.

Axel’s technique is fantastic because it takes all the work and worry out of it. Every time you build, grab the latest revision number from Subversion and inject it into the Info.plist file for the target. It took me a while to notice a subtle problem with his script, however. The number that is generated using his example (on my machine, anyway) is not reliably advanced. It doesn’t pick up the “latest” revision, it only picks up the revision of the root directory. In my current repository, the latest revision is “10,” yet the method described by Axel fails to detect this:

% svn info | grep “^Revision:”
Revision: 1
%

I can’t figure out when or if this root directory’s revision ID will change, but it certainly doesn’t change on every commit. What we want to know is, what was the revision number caused by the last checkin?

After tinkering with svn info, I discovered to my dismay that there is no option for simply spitting out the latest revision number. It depends on receiving a specific entity to report information about. In fact, if you point it at the repository itself (not the checked out copy), then it will in fact report, among other things, the latest revision number. So once you know the repository URL, you can get the latest revision number by doing something like this:

% svn info file:///Users/daniel/Sources/SVNROOT | grep “^Revision”
Revision: 10
%

But argh!, this will require that I dynamically figure out the repository address at compile time. I don’t want to hardcode it. I will have to use svn info on the root directory to obtain the repository, and then svn info again on the repository URL to obtain the revision. Too many steps! Poking around the web a little bit, I discover a hopeful lead with an example that uses the svn log command. It involves passing the –revision parameter and special revision tag “HEAD”. I tinker with this a bit and come up with something workable:

% svn log -q –incremental –revision HEAD | tail -n 1
r10 | daniel | 2005-08-14 12:25:56 -0400 (Sun, 14 Aug 2005)
%

I can get that “r10” at the beginning and parse it! Too bad it’s not the same format as Axel’s original post, though. Wait a minute – what happens if I pass the –revision parameter to svn info?

% svn info –revision HEAD | grep “^Revision: ”
Revision: 10
%

Success! Well, I thought so. Several reader comments below have convinced me to switch to svnversion. I was especially swayed by Axel’s observation that my technique won’t work as expected for repositories checked out to a specific revision number. To test this theory, I check out my project ot revision 5, and try my technique:

% svn info –revision HEAD | grep “^Revision: ”
Revision: 10
% svnversion ./
5
%

OK! I’m ready to play the svnversion game. But svnversion gets tricky when there’s any activity at all in the checked out repository. Going back to my working repository, I get output like this:

% svnversion ./
1:10M
%

Because some files in my directory have not been modified since revision 1, some were modified as late as revision 10, and some are modified but not yet checked in, I get this complex shorthand. I’m really happy with just using the right-most number, as Axel suggested in the comments area, but I decided that I might like to have the “M” too. It’s not usual to ship products with letters in the build version field, but then again, it’s not (shouldn’t be) usual to ship products with locally modified sources. I figure this might be a nice reminder and reality check. If somebody does get their hands on a version that has an “M” in it, I’ll know that it was a pre-release or one-off and I shouldn’t be too concerned about bugs that might only occur in that version.

I decide to use a Perl regular expression to parse out the desired information. I’m a regular expression “long time newbie” so I always have to pull up some kind of reference while I work with them. I’m sure I don’t do things the best way, but I get the job done and call it a day. This is what I’ve come up with as a pattern for stripping the right-most digit, as well as the “M” (or “S”) tag that might also be appended to the digits:

% svnversion . | perl -p -e “s/([\d]*:)(\d+[M|S]*).*/\$2/”
10M
%


Looks good. Now I can just plug that minor adjustment into Axel’s original script. For anybody interested in applying this to their own Subversion/Xcode project, the modified script is as follows:

# Xcode auto-versioning script for Subversion
# by Axel Andersson, modified by Daniel Jalkut to add
# "--revision HEAD" to the svn info line, which allows
# the latest revision to always be used.
        
use strict;
        
die "$0: Must be run from Xcode" unless $ENV{"BUILT_PRODUCTS_DIR"};
        
# Get the current subversion revision number and use it to set the CFBundleVersion value
my $REV = `/usr/local/bin/svnversion -n ./`;
my $INFO = "$ENV{BUILT_PRODUCTS_DIR}/$ENV{WRAPPER_NAME}/Contents/Info.plist";
        
my $version = $REV;

# (Match the last group of digits and optional letter M/S):

# ugly yet functional (barely) regex by Daniel Jalkut:
#$version =~ s/([\d]*:)(\d+[M|S]*).*/$2/;

# better yet still functional regex via Kevin "Regex Nerd" Ballard
($version =~ m/\d+[MS]*$/) && ($version = $&);

die "$0: No Subversion revision found" unless $version;
        
open(FH, "$INFO") or die "$0: $INFO: $!";
my $info = join("", <FH>);
close(FH);
        
$info =~ s/([\t ]+<key>CFBundleVersion<\/key>\n[\t ]+<string>).*?(<\/string>)/$1$version$2/;
        
open(FH, ">$INFO") or die "$0: $INFO: $!";
print FH $info;
close(FH);

To add this script to your build process, you simply create a new “Shell Script Phase” at the end of your target’s build phases, and copy & paste the contents from above into that phase. The script will edit your freshly-copied Info.plist file in place to reflect your latest Subversion revision.

Make sure you specify Perl as the shell for the script, I use “/usr/bin/perl -w” on my machine.

If any Subversion nerds stumble upon this entry and can suggest a better way of obtaining the latest revision for the project, please let me know!

The Bumpy Road to Subversion

August 14th, 2005

Not too long ago I was convinced to switch to Subversion for one of my most active projects. Unlike some folks, I didn’t run to Subversion with CVS blood on my fingers, but it was nonetheless a CVS nuisance that finally pushed me over the edge.

I don’t know what happened to CVS in Tiger, but on my machine it started causing the Repository files that CVS keeps in its maintenance folder to be ill-formatted. Every time I added a new directory to my project, I had to go in and manually edit the “Repository” file so it actually pointed at my local repository directory. Big pain in the butt! Finally, when I was poised to add a bunch of directories to my project, I realized I could either:

  1. Take the time to figure out what’s going wrong with CVS and address it.
  2. Grin and bear the CVS pain and manually edit a bunch of files again.
  3. Take the opportunity to figure out what’s so great about Subversion.

I decided to be heroic and take option three. Needless to say, the road was not bump-free. I was reasonably happy with subversion’s documentation and install process, but lots of things that “just work” with CVS seemed not to be with subversion. Xcode integration seemed flakier. I had to succumb to the learning curve of Subversion’s “simpler but nonetheless different” way of handling branches and tags. Finally, I lost my Subversion repository to a “corrupt database.” Grr! This never happened with CVS! Subversion fans like to say that it happened all the time with CVS but it just wasn’t smart enough to tell me. Well, my in-tact repositories thank CVS’ foolish ignorance!

I complained about this database corruption on the Xcode mailing list, which was met with a number of very helpful responses which essentially boil down to “Duh! You’re not supposed to use the Berkeley DB backend in Subversion!” Oh, silly me. I just installed it and used the default. The thing even insisted on me installing a compatible version of Berkeley DB. The bottom line is “Always use the fsfs backend.” This is apparently the default in more modern releases, but I wasn’t so lucky. I now have a fresh Subversion repository that picks up about 2 months after my CVS repository leaves off. Fortunately, it was a fairly lazy 2 months on the project.

I’m now “back on track” with Subversion and am now willing to give a tentative thumbs-up. It does make many things feel “warm and fuzzy.” With the fsfs backend to the repository, I feel confident that my repository won’t be corrupted, and if it is, I can still get my data out. On the Xcode front, something has happened to make Xcode more happy with my Subversion repository. Maybe there was a yucky residual file in my old repository, maybe it didn’t like the output from the version I was using. Who knows. It is not perfect, but it is much better.

If you haven’t switched to Subversion yet, and would like an easy path to installing it, I recommend the pre-built binaries from Matthew E. Porter. The binaries are recent and default to fsfs for the backend. And you should always use the fsfs backend.

Safari “Back Out of Mistake”

August 3rd, 2005

One of Safari’s really powerful tabbed-browsing features is the “Open in Tabs” option that appears at the bottom of any list (folder) of bookmarks you’ve placed in your Bookmarks Bar:

But what happens when you’re already looking at a bunch of links in tabs, and you accidentally land on this feature? Your active Safari window’s contents are obliterated, and replaced with all the new links! No warning, no appending, nothing.

I don’t know when this feature was added, but Safari 2.0 provides a sneaky “Get of Jail Free” card for when you meet with this situation and want to escape: just hit the back arrow!

In what appears to be a very special case behavior for the history stack, Safari will swap out the entire array of tabs and replace them with what was there before. Be careful not to click any other links, or the old tab state will be obliterated from your history. This feature appears to be a “use it or lose it” type of deal.