Hanumant’s Java Workshop

Turbo Charged Java Development!

Continuous Integration using Perforce and Hudson

A little background

The basic premise of Continuous Integration is simple. You want to know, as soon as possible, whether the the code that you just checked into the source control is breaking anything or not. So, as soon as you checkin your code, the build server gets all your latest code and all other code that depends on your code and builds all the affected projects. Compilation error or failure of any of the test cases should raise an alarm (i.e. send an email to the group).

We are using Perforce as the source control and Hudson as our build server. All of our projects are Mavenized.  We have set up a trigger on our Depo in Perforce such that whenever anything is committed to our Depo, it makes an HTTP request with parameters such as the changelist number, clientspec, and userid.

When I joined this project, I inherited a small but a very smartly written servlet that handled the HTTP requests made by Perforce upon any commit. This servlet would:

  1. get the changelist number (from the HTTP parameters)
  2. get the list of files committed under that changelist by executing the perforce client (p4.exe): p4.exe -p p4server:port -u p4userid -P p4passwd fstat @1234,@1234
  3. Identify the projects affected by these files, and
  4. Kickoff Hudson build projects to do the build(s).

The catch was that this small code was written in Groovy (apparantly, it was small because it was written in Groovy) and nobody knew Groovy in our team. All of our projects are in Java and it doesn’t make sense to add a completely new programming language to the mix just to save a about 50 lines of code, when there is 1000s of lines of Java code base lying around.  So we wanted to convert this Groovy servlet into Java.

This post is about the problem that I faced in step 2 above (retrieving file names committed under a given change list id).

The problem

Even after setting the userid and password flags ( -u and -P) while calling p4.exe and even after setting P4USER and P4PASSWD environment variable, Perforce would not return any data. It would just send back an error message saying: “P4PASSWD is wrong or is unset.”

As it turns out, for some reason, you need first execute p4 login command before calling the p4 fstat command. And p4 client does not accept the password provided in the -P flag or P4PASSWD env variable. When you call p4 login, it asks for the password and waits on the stdin for the user to enter the password. Obviously, this is no good for the continuous integration. This process must execute without any manual intervention.

The solution

The following is the code snippet that did the trick for me.

//the following code is executed once to login
StringBuilder sb = new StringBuilder();
String loginCmd = "p4 -u " + this.userid + " -P " + this.passwd + " -p " + this.host + " login";

Runtime runtime = Runtime.getRuntime();
Process p = runtime.exec(loginCmd);
OutputStream os = p.getOutputStream();
int returncode = p.waitFor();
getOutput(stringBuilder, p.getInputStream()); 

if (returncode != 0) {
   throw new Exception("Problem in login. Return code: " + returncode + " Output:" + sb);


//In another method that gets the fstat output for a given changelist
Runtime runtime = Runtime.getRuntime();
String fstatCmd = "p4 fstat @" + changeListId + ",@" + changeListId;
Process p = runtime.exec(fstatCmd);
getOutput(sb, p.getInputStream());
int returncode = p.waitFor();
if (returncode != 0) {
      throw new Exception("Problem in running fstat. Return code: " + returncode + " Output:" + sb);



June 19, 2010 Posted by | Java | 1 Comment