#!/usr/bin/perl
#
# PERFORCE BUNDLE WRAPPER v0.2
# Rachel 'Sparks' Blackman <sparks@noderunner.net>
#
# NO GUARANTEES OF OPERATION WHATSOEVER ARE MADE. You're welcome to use
# this file however you want (though I'd love credit to be left in it),
# but I can't be held responsible for the operation of it. It works on
# my machines, I can't promise more. I'd welcome changes being submitted
# back, this is an ugly hack and I'm sure there are better ways to do it. :)
#
# This wrapper will watch for Perforce operations performed on
# files matching certain rulesets, and execute those rules before
# actually passing the operations on to the real p4 client.
#
# This is intended to allow MacOS X .nib files and other 'bundles'
# to be tarballed on p4 operations so they can be treated as a single
# file, for purposes of making Xcode work with Perforce.
#
# It could, however, be presumably used for other files easily enough,
# as it should work under any UNIX p4 client situation as well. Right
# now, however, it's just written to do .nib files.
#
# THE ONE SIGNIFICANT GOTCHA IS THAT YOU MUST MANUALLY CHECK OUT NIB FILES.
# Interface Builder will whine if you open a nib file which is not
# opened for edit (I managed that much), but Interface Builder does not
# know to run an SCM edit command. However, you can use the SCM 'Edit'
# command from the project window, and it will properly check out the
# NIB file and set it up for edit. Yay!
#
# Occasionally, Xcode seems to get brain-damaged about the 'add' command
# and lose track of what it's trying to do with .nib files, and lose the
# 'SCM Edit' option in the UI. You can bring it back in sync from the
# command-line, but if anyone knows a good way to fix this, let me know.
#
# Also, at present, any p4 command which requires stdin to the user doesn't
# work with this wrapper, i.e., 'p4 submit' doesn't work if you don't
# provide the changelist description on the command line. If someone can
# think of a good way around that, I'd love to hear it. :)
#
# To install, just rename your existing p4 to p4.orig, and copy this
# script (set executable) into the directory as p4.
#
# If you are modifying this wrapper, you have my deepest sympathies. ;)
#
####
use Getopt::Std;
my $p4 = "";
# First we find the actual local Perforce executable
# choosing from a list of possibilities. We prefer '.orig'
# because most likely /we/ are installed as 'p4', after all!
#
for $p4chk ("/usr/local/bin/p4.orig","/usr/bin/p4.orig",
"/usr/local/bin/p4","/usr/bin/p4") {
if (!$p4 && -e $p4chk) {
$p4 = $p4chk;
}
}
# A little bit of sanity checking, just in case we didn't find
# what we wanted..
#
if (!$p4) {
print (STDERR "Perforce wrapper unable to find Perforce executable.\n");
}
# Parse all of the command line parameters
getopts('GVsc:C:d:H:L:p:P:u:x:', \%opts);
# Get our command...
#
$command = $ARGV[0];
# And all the parameters to it. There's probably a prettier way to
# do this, but let's see you write one at 3am! ;)
#
@params = @ARGV;
@params = reverse(@params);
pop(@params);
@params = reverse(@params);
# Okay. We now have all our data, so we can check for specific types
# of things. Really, what we most care about are 'add', 'submit',
# 'fstat' and 'sync' calls.
#
# All of these require changes to the call we make to the p4 client
# command, but sync will also require some post-processing when
# the command is done. Ugh!
#
if (($command eq 'add') || ($command eq 'submit')) {
# Add and submit are similar because both will need to actually
# tarball any files matching our patterns.
#
for ($count = 0; $count < @params; $count++) {
$basefile = $params[$count];
$revision = "";
$file = $basefile;
if ($basefile =~ /(.*)#([0123456789]+)/) {
$file = $1;
$revision = $2;
}
if (($file =~ /(.*)\.nib/i) || ($file =~ /(.*)\.framework/i)) {
# We have a bundle. Let's build a tarball.
#
$dir = $opts{'d'};
if ($dir eq "") {
$dir = '.';
}
$tarball = "${file}.tar";
$cmd = "cd $dir ; tar -cf $tarball $file";
system("$cmd");
if ($revision ne "") {
$tarball .= "#${revision}";
}
$params[$count] = $tarball;
}
}
}
if (($command eq 'fstat') || ($command eq 'sync') || ($command eq 'filelog')
|| ($command eq 'revert') || ($command eq 'print') || ($command eq 'edit')) {
# These are all similar because we need to modify
# the filename parameters
#
for ($count = 0; $count < @params; $count++) {
$basefile = $params[$count];
$revision = '';
$file = $basefile;
if ($basefile =~ /(.*)#([0123456789]+)/) {
$file = $1;
$revision = $2;
}
if (($file =~ /(.*)\.nib/i) || ($file =~ /(.*).framework/i)) {
$tarball = "${file}.tar";
if ($revision ne '') {
$tarball .= "#${revision}";
}
$params[$count] = $tarball;
}
}
}
# Assemble the command again with all of our options,
# and any modifications we've made to the parameters.
# This command will then be passed on to the real p4 command,
# transparently. By this point, we have all our tarball conversions
# which we need going INTO p4.
#
$p4cmd = $p4;
foreach $opt (sort(keys %opts)) {
$p4cmd .= " -$opt";
if ($opts{$opt} != 1) {
$p4cmd .= " $opts{$opt}";
}
}
$p4cmd .= " $command";
$p4cmd .= " @params";
# FOR DEBUG PURPOSES FOR NOW:
# open (P4LOG, ">> /tmp/p4wrap.log");
# print (P4LOG "$p4cmd\n");
# close (P4LOG);
# Now comes the fun part; we have to run the p4 command, and
# /if/ it's a sync, we have to watch for our .nib.tar files,
# in order to unpack them /and/ toss the proper .nib filename
# out for Xcode to parse. Fun stuff.
#
open (P4PIPE, "$p4cmd |") || die ("Couldn't execute Perforce command!");
while ($line = <P4PIPE>) {
chomp ($line);
if ($command eq "sync") {
# Watch for .nib.tar files in the sync, untar, and output
# the line with just a .nib instead.
#
if ($line =~ /(.*)#([0123456789]+) - ([^ ]*)(.*)/) {
$depotfile = $1;
$revision = $2;
$action = $3;
$clientfile = $4;
if ($clientfile =~ /(.*)\.nib\.tar/) {
# It's a nib. We'll map the filename back, and we
# want to untar it as well.
#
$dir = $opts{'d'};
if ($dir eq '') {
$dir = '.';
}
$tarcmd = "cd $dir ; tar xf $clientfile";
system($tarcmd);
$clientfile = "${1}.nib";
$modcmd = "cd $dir ; chmod -R a-w $clientfile";
system($modcmd);
}
elsif ($clientfile =~ /(.*)\.framework\.tar/) {
# It's a framework. We'll map the filename back, and we
# want to untar it as well.
#
$dir = $opts{'d'};
if ($dir eq '') {
$dir = '.';
}
$tarcmd = "cd $dir ; tar xf $clientfile";
system($tarcmd);
$clientfile = "${1}.framework";
$modcmd = "cd $dir ; chmod -R a-w $clientfile";
system($modcmd);
}
$line = "${depotfile}#${revision} - ${action} ${clientfile}";
}
}
elsif ($command eq "edit") {
# Watch for .nib.tar files in the edit, untar, and output
# the line with just a .nib instead.
#
if ($line =~ /(.*)#([0123456789]+) - (.*)/) {
$depotfile = $1;
$revision = $2;
$action = $3;
if ($depotfile =~ /(.*)\.nib\.tar/) {
# It's a nib. We'll map the filename back, and we
# want to untar it as well.
#
$dir = $opts{'d'};
if ($dir eq '') {
$dir = '.';
}
$depotfile = "${1}.nib";
$clientfile = "";
if ($depotfile =~ /\/\/(.*)\/([^\/]*)/) {
$clientfile = $2;
}
$tarcmd = "cd $dir ; chmod -R u+w $clientfile ; tar xf ${clientfile}.tar";
system($tarcmd);
}
elsif ($depotfile =~ /(.*)\.framework\.tar/) {
# It's a nib. We'll map the filename back, and we
# want to untar it as well.
#
$dir = $opts{'d'};
if ($dir eq '') {
$dir = '.';
}
$depotfile = "${1}.framework";
$clientfile = "";
if ($depotfile =~ /\/\/(.*)\/([^\/]+)/) {
$clientfile = $2;
}
$tarcmd = "cd $dir ; chmod -R u+w $clientfile ; tar xf ${clientfile}.tar";
system($tarcmd);
}
$line = "${depotfile}#${revision} - ${action}";
}
}
elsif ($command eq "fstat") {
# Watch for .nib.tar files in the fstat output, and change
# the line to just have a .nib file for the name.
#
if ($line =~ /^\.\.\. clientFile (.*)/) {
$file = $1;
# It's a nib. We map the filename back to what Xcode expects
#
if ($file =~ /(.*)\.nib\.tar/) {
$file = "${1}.nib";
}
elsif ($file =~ /(.*)\.framework\.tar/) {
$file = "${1}.framework";
}
$line = "... clientFile $file";
}
elsif ($line =~ /^\.\.\. depotFile (.*)/) {
$file = $1;
# It's a nib. We map the filename back to what Xcode expects
#
if ($file =~ /(.*)\.nib\.tar/) {
$file = "${1}.nib";
}
elsif ($file =~ /(.*)\.framework\.tar/) {
$file = "${1}.framework";
}
$line = "... depotFile $file";
}
}
elsif (($command eq "filelog") || ($command eq "print")) {
if ($line =~ /^\/\/(.*)/) {
$file = $1;
if ($file =~ /(.*).nib.tar/) {
$file = "${1}.nib";
}
elsif ($file =~ /(.*).framework.tar/) {
$file = "${1}.framework";
}
$line = "//$file";
}
}
elsif ($command eq "revert") {
if ($line =~ /^\/\/(.*)#([0123456789]+) - (.*)/) {
$file = $1;
$revision = $2;
$action = $3;
if ($file =~ /(.*).nib.tar/) {
$file = "${1}.nib";
$clientfile = "";
if ($file =~ /(.*)\/([^\/]*)/) {
$clientfile = $2;
}
$dir = $opts{'d'};
if ($dir eq '') {
$dir = '.';
}
# Make our .nib write-only so that Xcode will properly
# attempt to check it out... we hope?
$modcmd = "cd $dir ; chmod -R a-w $clientfile ; chmod u+w ${clientfile}.tar";
system($modcmd);
}
elsif ($file =~ /(.*).framework.tar/) {
$file = "${1}.framework";
$clientfile = "";
if ($file =~ /(.*)\/([^\/]*)/) {
$clientfile = $2;
}
$dir = $opts{'d'};
if ($dir eq '') {
$dir = '.';
}
# Make our .nib write-only so that Xcode will properly
# attempt to check it out... we hope?
$modcmd = "cd $dir ; chmod -R a-w $clientfile ; chmod u+w ${clientfile}.tar";
system($modcmd);
}
$line = "//${file}#${revision} - ${action}";
}
}
elsif ($command eq "add") {
if ($line =~ /^\/\/(.*)#([0123456789]+) - (.*)/) {
$file = $1;
$revision = $2;
$action = $3;
if ($file =~ /(.*).nib.tar/) {
$file = "${1}.nib";
}
elsif ($file =~ /(.*).framework.tar/) {
$file = "${1}.framework";
}
$line = "//${file}#${revision} - ${action}";
}
}
# Print our finalized line.
#
print "$line\n";
}
# ...all done! Xcode should be happy.