February 11, 2009

Using Perl with Perforce (via the P4Perl derived-API)

Integration
Traceability

There are a few options available for programmatic access to Perforce. You might choose to use the command line client, where you'd write scripts that call the "p4" executable directly (perhaps with Python marshaled output), or you can program using our client API, in one of the supported languages.

There are actually two interfaces to the client API. At the low-level, there's the original C++-language API, and at the high-end, we've got a number of derived APIs for use with popular scripting languages which provide a number of convenience routines to make your life easier.

In this post, I'll give you two things. First, we'll get the sample depot for you to test against, and after that's done, we'll use P4Perl to talk to it and get some data. You can skip the sample depot if you just want to use your existing server(s). I'm using a Linux machine here, so that's what the example commands assume. In the examples, I've linked directly to various resources, but in general it's a good idea to just go to the downloads page to get your files.

And on to the show! First, get the sample depot and the server.

wget ftp://ftp.perforce.com/perforce/tools/sampledepot.zip unzip sampledepot.zip
wget ftp://ftp.perforce.com/perforce/r08.2/bin.linux26x86_64/p4d chmod +x p4d

Now as a matter of good-practice, we can avoid confusion down the road by renaming the executable to include the major and minor version numbers.

./p4d -V
...
Rev. P4D/LINUX26X86_64/2008.2/179173 (2008/12/05).
...
mv p4d PerforceSample/p4d.82.179173

Now we'll replay the sample depot checkpoint and upgrade the database to match the version of the server that we downloaded. You don't need a license for this, as the server is good for two users and five clients without one. Use `pwd` so "p4 info" shows the server root.

cd PerforceSample
./p4d.82.179173 -r `pwd` -jr checkpoint
./p4d.82.179173 -r `pwd` -xu
./p4d.82.179173 -r `pwd` -p 1666 -L log.txt -vserver=3
Perforce Server starting...

You can leave off the "-L log.txt" if you want the server to print its log to STDOUT. Now, in another terminal window, we'll get P4Perl and the C P4API (both are necessary,) and install them. You'll need Perl 5.8 and a compatible C compiler. You'll see errors during "make" if either are unmet. The "apidir" argument is the absolute path to where you put the P4API.

wget ftp://ftp.perforce.com/perforce/r08.2/tools/p4perl.tgz
wget ftp://ftp.perforce.com/perforce/r08.2/bin.linux26x86_64/p4api.tgz
tar xzf p4perl.tgz
tar xzf p4api.tgz
cd p4perl-2008.2/
perl Makefile.PL --apidir=/tmp/p4/p4api-2008.2.179173
make
make install

The "make install" probably requires root access. If you don't have access rights to install Perl modules, you can fudge it in the following (unsupported and untested!) manner:

mkdir -p x86_64-linux/auto
cp -R blib/arch/auto/ x86_64-linux/
cp -R P4 x86_64-linux/
cp P4.pm x86_64-linux/

Now that we've got all our prerequisites taken care of, I can actually show you something interesting! Here's the source for "example.pl", which uses the P4Perl API, as documented here. And assuming you don't have a P4CONFIG file, you can invoke the example script like this (with PERL5LIB being necessary if you didn't run "make install". Point it to the directory you built P4Perl in.):

P4USER=user_name
P4PORT=1666
PERL5LIB=/tmp/p4/p4perl-2008.1
perl example.pl
use P4;print"The version string for P4Perl:\n"."-"x80 ."\n";print P4::Identify()."\n\n";$p4= new P4;# Set the API program name and version. Shows up in the server log like this:## 2008/12/31 11:27:12 pid 24680 user@ws 127.0.0.1 [P4PERL-test/.1] 'user-info'#$p4->SetProg("P4PERL-test");$p4->SetVersion(".1");# Connect to the server. Gets connection details from the environment since# we didn't specify any with SetUser(), SetPort(), etc.$p4->Connect();if($p4->ErrorCount()||$p4->WarningCount()){print"P4 Errors:\n\n".$p4->Errors()."\n"if$p4->ErrorCount();print"P4 Warnings:\n\n".$p4->Warnings()."\n"if$p4->WarningCount();$p4->Disconnect();print"Exiting on P4 error!\n";exit;}################################################################################print"\"p4 info\" output for \"".$p4->GetPort()."\":\n"."-"x80 ."\n";$info=($p4->Run("info"))[0];foreachmy$k(sortkeys%$info){$v= $$info{$k};print"$k: $v\n";}print"\n\n";################################################################################# Specify a charset for the server connection, if you have a preference.# See "p4 help charset" for details. Unnecessary for non-unicode servers.my$P4CHARSET="";# Trick to allow connections to UNICODE servers without knowing ahead of time# that you're using one.if(!$P4CHARSET){$info= $$info{"unicode"};if(defined$info&&$infoeq"enabled"){$P4CHARSET="utf8";# a safe default.}}$p4->SetCharset($P4CHARSET)if($P4CHARSET);################################################################################print"The last 5 submitted changelists:\n"."-"x80 ."\n";$changes=$p4->Run("changes","-L","-ssubmitted","-m 5");if( @$changes==0){print"Empty changes!\n";}else{# "$changes" is an array of hashes of changelists.# Similar to the output of "p4 -ztag changes -L -s submitted -m 5".foreachmy$hash( @$changes){foreachmy$key(sortkeys%$hash){my$val= $$hash{$key};print"$key: $val\n";}print"\n\n";}}################################################################################# A file with integration history.$file="//depot/www/live/Jamlang.html#1";print"Filelog for \"$file\":\n"."-"x80 ."\n";$filelog=$p4->RunFilelog("-l","-t","-m 1",$file);foreachmy$depotfile( @$filelog){my$revisions=$depotfile->Revisions();foreachmy$rev( @$revisions){print"depot file: ".$depotfile->DepotFile()."\n"."revision:  ".$rev->Rev()."\n"."description: ".$rev->Desc()."\n"."action:   ".$rev->Action()."\n"."change:   ".$rev->Change()."\n"."type:    ".$rev->Type()."\n"."time:    ".$rev->Time()."\n"."user:    ".$rev->User()."\n"."client:   ".$rev->Client()."\n"."digest:   ".$rev->Digest()."\n"."file size:  ".$rev->FileSize()."\n";my$integs=$rev->Integrations();print"\nintegration details:\n\n"if @$integs;foreachmy$i( @$integs){print"how:    ".$i->How()."\n"."file:    ".$i->File()."\n"."source rev: ".$i->SRev()."\n"."end rev:  ".$i->ERev()."\n";}}}print"\n\n";################################################################################# Fetch the job spec, "p4 jobspec -o".$jobspec=$p4->FetchJobSpec();print"Jobspec comments (via a convenience method):\n"."-"x80 ."\n".$jobspec->_Comments ."\n\n";print"Jobspec fields, recomposed (via the hash):\n"."-"x80 ."\n";foreach( @$jobspec{"Fields"}){my@f= @$_;for(my$i=0;$i<@f;$i++){# example: 108 Severity select 10 required@_=split(/\s+/,$f[$i]);printjoin("-",@_)."\n";}}################################################################################# Gracefully say goodbye to the server.$p4->Disconnect();

And there you have it. If Perl isn't your thing, check out the related software page for other language bindings.