#!/usr/bin/perl # # PERFORCE BUNDLE WRAPPER v0.2 # Rachel 'Sparks' Blackman # # 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 = ) { 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.