Automated Build Output Management Using the Plain Java Client Libraries

Summary

Rational Team Concert (RTC) and the Jazz Build Engine (JBE) provide continuous integration through build and build result publishing. The build results are part of the Jazz object model and are tightly integrated into the development workflow. The build result views provide all of the essential capabilities to assess published results, including compiler and unit test results.

The article Build Artifacts Publishing Using HTTP Servers in Rational Team Concert demonstrates how to use a HTTP server to publish build output to build results. This approach has many advantages, but lacks the automation for pruning the build data that is no longer needed from the repository.

This article shows how this pruning, and more, can be implemented using the Plain Java Client Libraries (PJCL).

License Statement

This article contains source code. License terms that govern use of this source code can be found here.

Current Situation

At the end of the article Build Artifacts Publishing Using HTTP Servers in Rational Team Concert we have a system of one or more build engines that run on one or more machines and perform builds for a CCM (RTC) application.

Distributed topology with two buid engines running on different machines

Each build creates a unique build output folder that contains all of the build artifacts contributing to the build, and the resulting data. The build output folders are published and accessible via HTTP. The RTC build results created contain links back to this published build output, as shown in the image below.

Build result with link to build output

Each build output folder requires a unique name to allow publishing. Since each build creates its own build output folder, the amount of disk space used grows quite quickly. This can be a potential issue. How do we address this?

RTC has a ‘Pruning Policy’, that can be enabled and configured for each build definition as shown below.

Enable build pruning in the build engine

This policy allows for cleaning of the build result list without user intervention. In addition it allows the marking of a build result as a release or deliverable. These build results are marked as “not for deletion”, and they are not pruned from the repository results.

However, this works only for build results in the RTC repository. The published build output folders on disk are not deleted. It would be desirable to have some automated solution that deletes the build output folders that are no longer needed. Ideally this solution shouldn’t cause broken links from still existing build results to deleted build output folders.

Solution Considerations

What would an automated solution for managing the build output folders look like and which general approaches are available?

1. Age Based Deletion

One trivial, but valid, approach for this requirement is “expiration by age”. Look at the age of the folder and delete them after passing a certain threshold. This is easy enough to implement. However it has some drawbacks:

  • It does not take any information available in Rational Team Concert into account, especially not data that became available after the build completed. Specifically it does not take into account build results that are marked as a deliverable and should be preserved. It is possible to end up with build results in RTC that are linked to build output that is already deleted.
  • The expiration age would be totally unrelated to the build result pruning in RTC

2. Server Extension

Another option would be to somehow extend the RTC server, and act on the deletion of build results. The issues with this approach are:

  • The RTC server might neither have access, nor the necessary privileges, to delete files on the build machine.
  • It could only delegate deletion to some other tool such as the Jazz Build Engine (JBE). So the JBE would have to be notified, potentially using a build request. That would require a special build definition which makes it hard to maintain and error prone.
  • It would be hard to implement a solution, requiring that several build engines be able to work on the same build queue.
  • It would probably be quite complex to implement and maintain as a server extension.

3. Plain Java Client Libraries Client

The third option would be to create a tool that uses an API, like the Plain Java Client Libraries (PJCL), to query the RTC server for data. Based on that data it could delete or preserve folders. The advantages of such a solution would be:

  • The ability to act on the information for the build results that are still available and delete the build output for the ones no longer available.
  • The ability to run on any machine with enough privileges to delete the folders created during builds.
  • The ability to deploy the solution on several machines that are used for building and to manage the build output folders.
  • Running the solution would only require starting a Java application, which could be easily automated using a scheduler or even the JBE.
  • It would be possible to perform additional tasks. One example would be creating a backup for build output that is considered a deliverable.
  • Maintenance and enhancing would be simpler, using the documentation provided for the Plain Java Client Libraries.
There is one, not quite obvious, disadvantage with this approach:
  • The solution is supposed to delete folders that represent build output for build results that have been deleted.

This disadvantage does not appear substantial, but trying to identify build output data that is related to deleted build results, is a real problem. There is no positive query that can be run to get the identifiers of the deleted build results. The only approach is to ask the server if a build result still exists. Assuming a build result has been deleted if it can not be found, could lead to data loss due to an incorrect query, or possibly leave unnecessary data. A more complex server based extension could potentially act on deletion events for build results, but has several other issues which would make using it difficult.

4. Listening to Build Result Deletion Feeds

Listening to a server feed containing build result deletion events would be another good concept. It would avoid a lot of the issues described above, especially the reliable detection of deleted build results. However Defect 193880: Build deletions don’t have events in build feeds currently prevents us from implementing it.

Solution Outline

After a lot of contemplation, I decided to try to start prototyping based on the Plain Java Client Libraries approach. During the prototyping phase, simple solution approaches came up that help mitigating it’s one disadvantage. As soon as the build result deletion feeds become available, a plain Java Client Libraries based solution could potentially be enhanced to remove this disadvantage entirely.

This article focuses on showing implementation hints for a Plain Java Client Libraries based approach, and will discuss how to create a small, stand-alone Java application for automation. The application will perform the following workflow:

  • Connect to a RTC server.
  • Iterate through a build output root folder containing build output folders. For each qualifying build output folder:
    • Identify the build result UUID for the build output and attempt to locate the corresponding RTC build result on the RTC server.
    • If the build result is no longer available, delete the build output folder.
    • If the build result is still available, open it and examine it for additional information.
      • If the build result is marked as a release, publish it to an asset management solution and include it in a backup. After this is completed, attach a link back to the asset to the build result.
    • Handle folders that can not be identified as build output folder.

As already mentioned a key concern for such a solution is to be able to detect a relationship between a build output folder and a build result that might be available on a server or already been deleted. Basically it is necessary to:

  • Detect if a folder is related to a build output
  • Detect if the build result related to the build output has been deleted or is still available

To allow an automation client to perform this check, the build result UUID of the build that was performed in an build output folder is required at a minimum.

This would assume that there is only one repository that contains the build results. In case of running multiple servers that could contain the build result, it would be good to know the URL of the server requesting the build.

For the purpose of this article, the approach used to get the required data to identify the repository and the build result UUID related to a candidate build output folder is to:

  • Expect a file named build.properties in the candidate folder.
  • Expect the build.properties file to contain the UUID of the build result and the URL of the repository that requested the build.

If the build.properties file exists, a folder can safely assumed to be a build output folder. The enclosed data provides sufficient information to verify the build result is deleted or still available. The image below shows the expected structure.

A file named build.properties is expected in the build output folder

The next image shows the file content assumed to be available.

The properties repositoryAddress and buildResultUUID are expected to be in the build.properties file

There are other possible approaches to identify the folders and retrieve the data required. For example it would be possible to code the build result UUID and potentially also the repository URL in the name of the build output folder.

Set up the test system

To develop such a solution, a test system to run against is required. We will use the Money That Matters sample that is available in RTC. Setup instructions for a test system can be found in the article Build Artifacts Publishing Using HTTP Servers in Rational Team Concert. In order to download the install files, a valid registration on Jazz.net is required. It is not necessary to implement the Apache™ HTTP Server publishing for this article.

It is also possible to follow this article using other test systems. In this case replace server names, ports and folder names used in the article to match the target system.

Set up for development

To install the required Plain Java Client Libraries, go to the Rational Team Concert 3.0.1.1 “All Downloads” page and scroll down to the Plain Java Client Libraries downloads in the Plain Zips section.

Sownload of the Plain Java Client Libraries

Download the Plain Java Client Libraries and the related API documentation.

Create a root folder to contain the development artifacts. In this article C:/RTC3011Dev is used as a root folder and <RTCDevDir> is used as a place-holder. When using different folder names or a different structure make sure to provide the correct values.

Create a folder <RTCDevDir>/PlainJavaLib to contain the Plain Java Client Libraries. Unzip the files downloaded files RTC-Client-plainJavaLib-3.0.1.1.zip and RTC-Client-plainJavaLib-API-javadoc-3.0.1.1.zip into this folder.

Plain Java Client Libraries folder structure after setup

The folder should now contain the folder doc containing the Javadoc documentation, the folders license and snippets and various Java jar files.

Please take some time to browse the folders.

  • Check the license folder for the license terms and conditions for using the Plain Java Client Libraries.
  • In the doc folder, open the index.html file to access the Javadoc documentation
  • In the snippets folder, review the snippets provided with the Plain Java Client Libraries.

Prepare the Build Definition

To provide the build.properties file and the other required data, the build definitions of the sample application need to be enhanced.

Start the RTC Eclipse Client and open a new workspace <RTCDevDir>/workspace. Connect to the test system https://clm.example.com:9443/ccm to be able to trigger builds and browse build results.

The Money That Matters sample uses Ant for building. The RTC Ant build configuration editor tab allows creating the build.properties file with the data required for the purpose of this article.

To enable its creation, open the build definition jke.dev. Switch to the Ant tab, scroll down to the Properties file entry and set the value of this entry to ${outputRootDirectory}${buildLabel}build.properties.

Prepare the build definition to create the build.properties file

Save the build definition.

Locate the build output root folder. In the Money That Matters sample, the build output folders are created into a folder JKEBuild relative to the Jazz Build Engine executable. The JBE executable for a given install folder <JBE_installDir> typically is in <JBE_installDir>/buildsystem/buildengine/eclipse.

Open the build output root folder <JBE_installDir>/buildsystem/buildengine/eclipse/JKEBuild. Run some builds for the build definition jke.dev and check that they are successful. Check the build output root folder. It should look similar to the image below. Each build folder will have a name composed of the buildLabelPrefix and the build label containing a date.

The build output root folder with the contained build output folders.

Open the newly created build output folders and check that there is a build.properties file that contains the required content. Fix issues with the build definition until the build output matches the requirement. Leave some folders that are missing the build.properties file for testing.

It is also possible to change the ‘Pruning Policy’ on the Overview tab in this step. But this is not required for this article. For testing, it is enough to manually delete some build results.

Setup a Project for Development

After these preparations it is possible to start developing the build output management solution further referred to as BuildOutputManager.

To implement the build output management solution open the Eclipse Java perspective and create a new Java project. Make sure to require at least a Java 1.5 JRE. Provide a meaningful name for the project. Throughout the article com.ibm.js.team.build.manager is used as the project name.

Create the new Project com.ibm.js.team.build.manager

Finish the wizard to create the project.

The project needs to be set up for development using the Plain Java Client Libraries (PJCL). Directions on how to set up a project that uses the PJCL can be found on the Jazz Team Wiki. The detailed description of how to set up projects for development with the Plain Java Client Libraries is very valuable, and describes several additional tricks that can make development and debugging for the PJCL easier. The approach of using a plug-in project, instead of a simple Java project, along with the RTC SDK set up in the Eclipse Plugin Development Environment (PDE) to allow easier debugging is invaluable. The Rational Team Concert 3.0.1 Extensions Workshop has also a very detailed description on how to set up the RTC SDK for development.

To shorten the description for the setup process, this article uses a simpler approach.

Open the context menu of the new Java project and open the Configure Build Path dialog as shown in the image below.

Open the Configure Build Path context menu

Switch to the Libraries tab and click Add External JARs.

Select Add External Jars on the Libraries Tab

In the selection dialog browse to the location where the Plain Java Client Libraries were unzipped. Select all Jar files and click Open.

Select the external JAR files to add

The project is now set up for development using the PJCL.

Implement the Java Code

Create a new Java class with a meaningful name in a package. Make sure that you select the check-box to create a main method stub, as shown below. In this article the class is named BuildOutputManager and the package is named com.ibm.js.team.build.manager.

Create the new Class com.ibm.js.team.build.manager.BuildOutputManager

Implement the Main Method

The code that needs to be implemented in the main method is very simple. Please review the Snippet1.java file in the snippets folder of the Plain Java Client Libraries as an example for the main method. Similar to the PJCL snippets the main method of the BuildOutputManager should look like this:

public static void main(String[] args) {
     TeamPlatform.startup();
     try {
          run(args);
     } catch (TeamRepositoryException e) {
          e.printStackTrace();
     } finally {
          TeamPlatform.shutdown();
     }
}

The code starts up the TeamPlatform and calls the method run() that handles the parameters and coordinates further work. At the end of the operation, it is necessary to shutdown the TeamPlatform. Some PJCL calls that work with the RTC CCM Application throw a TeamRepositoryException when failing. Any exceptions not caught in the code are handled at this level.

Since the project has been set up for PJCL, and has access to the Jar files, Eclipse should provide help to import the required packages. Using Quick Fix should work, as well as Content Assist. If this is not the case validate the project setup.

Process the Parameters

Now the missing run method needs to be implemented. Be sure to add a throws statement for TeamRepositoryException.

      private static void run(String[] args) throws TeamRepositoryException {

The BuildOutputManager requires the following parameters:

  • The URI of the team repository to connect to.
  • A user ID of a user with enough privileges to access the
    build results.
  • The password for this user.
  • The path to the folder that contains the build output
    folders

For simplicity the data is expected in a certain order. More logic for parameter checking could easily be added.

The run method stores the data in more convenient local variables.

      String repositoryURI = args[0];
      final String userId = args[1];
      final String password = args[2];
      String buildOutputRootFolder = args[3];

First the code should check if the folder containing the build output folders exists. If not there is nothing to do and the BuildOutputManager can stop.

      File buildOutputRoot = new File(buildOutputRootFolder);
      if (!buildOutputRoot.exists()) {
           return;
      }

To use the PJCL to communicate with a RTC server it is necessary to log into the team repository. Again, the code is very similar to the code in Snippet1.java from the PJCL. The code below shows how to get a connection to the TeamRepository, register a login-handler providing the user ID and password and call the method IteamRepository.login().

      ITeamRepository teamRepository = TeamPlatform.getTeamRepositoryService().getTeamRepository(repositoryURI);
      teamRepository.registerLoginHandler(new ITeamRepository.ILoginHandler(){
           public ILoginInfo challenge(ITeamRepository repository) {
                return new ILoginInfo() {
                public String getUserId() {
                     return userId;
                }
                public String getPassword() {
                     return password;
               }
           };
      }
      });
      teamRepository.login(null);

There is one noticeable detail that comes up often in PJCL code. Operations that could take some time to complete such as ITeamRepository.login() typically have a parameter for a so called progress monitor. The progress monitor can be used in user interfaces to indicate the application is busy or to show progress to the user. If implemented, the progress monitor also allows to requests to cancel the operation. An example implementation of a progress monitor, the class SysoutProgressMonitor.java, is provided in the PJCL snippets folder. In the code above, null is passed to indicate there is no progress monitor.

At this point the BuildOutputManager would either be successfully connected to the repository or terminated due to a TeamRepositoryException that has been thrown.

Implementing some more graceful handling and user feedback is possible but not really relevant for this article, so the code is left out.

To get out of the static context the run() method can now create a new BuildOutputManager class instance and call the manageFolders() method, to perform further work. After that method terminates the PJCL requires a call to ITeamRepository.logout() to return reserved resources.

      BuildOutputManager controller = new BuildOutputManager(teamRepository, buildOutputRoot);
      controller.manageFolders();
      teamRepository.logout();

Manage the Build Output Folders

Now the main control method manageFolders() can be implemented. Some exceptions will be handled in the main method, add a throws TeamRepositoryException statement.

      private void manageFolders() throws TeamRepositoryException {

The method manageFolders() implements the approach, as outlined in the Solution Outline. It iterates through all of the folders in the build output root folder, and checks each candidate found. The code below gets the content of the build output root folder, iterates through the content, or terminates if there is no content. Files that are not a directory folder are ignored.

      String[] children = this.aBuildOutputRootFolder.list();
      if (children == null)
           return;
      for (int i = 0; i < children.length; i++) {
           File aBuildOutputCandidate = new File(this.aBuildOutputRootFolder,children[i]);
           if (!aBuildOutputCandidate.isDirectory()) {
                continue;
           }

Please note if there are any compiler errors, then you probably need to add a closing bracket for the for loop. Development continues at the current level in the loop..

Check and Load the Build Propertie File

At this point in the loop, the manageFolders() method has identified a folder. Now manageFolders() can determine if the build output folder candidate represents a build output folder. As described above, this is done by checking if a build.properties file is available and can be loaded. Checking for existence and loading the file into a java.util.Properties class is delegated to the loadBuildProperties() method.

The code of the new method can be implemented later, for now create a method stub for loadBuildProperties() returning null, to get rid of compiler errors.

If the build.properties file can not be detected or read, the candidate folder does not qualify according to the rules of the solution. Those folders are passed for processing to a handleNotQualifyingFolders() method and the manageFolders() method continues with the next candidate folder. Create a stub for the missing method handleNotQualifyingFolders().

      Properties buildProperties = loadBuildProperties(aBuildOutputCandidate);
      if (null == buildProperties) {
           // no build properties, build may be incomplete
           // or not following the format
           handleNotQualifyingFolders(aBuildOutputCandidate);
                continue;
          }

There could be several reasons why the property file is missing. The build definition is not creating it or the build failed before Ant was successfully called. By implementing the method handleNotQualifyingFolders() it is possible to further enhance the BuildOutputManager.

Folders that don’t qualify can be handled according to the local assumptions, they could be deleted based on age or it would be possible to send an e-mail notification to someone responsible.

Get the Build Result ID

If the build.properties file can be loaded, the manageFolders() method can check for the buildResultUUID property needed to identify the corresponding build result on the server. If the property cannot be found, the candidate folder can again be handled in the handleNotQualifyingFolders() method and the manageFolders() method continues with the next folder.

      // Get the build result UUID 
      String buildResultID = buildProperties.getProperty("buildResultUUID");
      if (null == buildResultID )
      {
           handleNotQualifyingFolders(aBuildOutputCandidate);
           continue;
      }

Check for a Matching Repository Address

Likewise, the manageFolders() method can check the repositoryAddress property to identify the repository managing the result for the requested build. If there is no property value, the folder can be handled in the handleNotQualifyingFolders() method and manageFolders() can continue with the next folder.

      String buildRepositoryAddress = buildProperties.getProperty("repositoryAddress");
      if (null == buildRepositoryAddress) {
           handleNotQualifyingFolders(aBuildOutputCandidate);
           continue;
      }

The manageFolders() method can now check if the build output was created for the same repository it is working against. If this is not the case, this instance of the BuildOutputManager is not responsible for the build output folder and the code can continue with the next folder, assuming that another run with different parameters is responsible for handling it.

      if (!aTeamRepository.getRepositoryURI().equals(buildRepositoryAddress)) {
           continue;
      }

Check if the Build Result is Available

The manageFolders() method has now identified a build output folder with a valid build result UUID, and the build belongs to the team repository it is working against. It can now try to find the build result in the team repository. To do that the manageFolders() method creates a handle for the build result using the UUID and tries to fetch the build result from the team repository. The fetch operation used below, is permission aware. This allows it to distinguish the case where the users permission prevents them from fetching the build result, from the case where the result is really deleted. It also forces the itemManager to refresh its cache to get the latest data.

      IBuildResultHandle resultHandle = (IBuildResultHandle) IBuildResult.ITEM_TYPE.createItemHandle(UUID.valueOf(buildResultID), null);
      ArrayList<IBuildResultHandle> handleList = new ArrayList<IBuildResultHandle>();
      handleList.add(resultHandle);
      IFetchResult fetchResult = aTeamRepository.itemManager().fetchCompleteItemsPermissionAware(handleList, IItemManager.REFRESH, null);

If the fetch result contains permission denied items, the user lacks the permission to open and see the build result. In this case manageFolder() can not perform any additional inspections on the build result. It leaves the folder alone and continues with the next folder, assuming another run with other credentials will be performed. An enhancement would be to indicate the permission issue to a responsible person.

      if (fetchResult.hasPermissionDeniedItems()) {
           continue; // can't access but there is a build result
     }

If the fetch result indicates the build result can no longer be found on the server, the build result has been deleted, and the folder can therefore be deleted. The deletion is delegated to the checkDeleteFolder() method. The candidate folder is dealt with and the method continues with the next folder.

      if (fetchResult.hasNotFoundItems()) {
           checkDeleteFolder(aBuildOutputCandidate);
           continue;
      }

At this point the build result is detected to be still in the repository. The candidate folder is still referenced and needs to be preserved. The BuildOutputManager can perform a deeper analysis of the build result found in the repository and act on the results. The code below iterates the retrieved items and delegates the analysis of the build result to the processBuildResult() method.

      List retrieved = fetchResult.getRetrievedItems();
      for (Iterator iterator = retrieved.iterator(); iterator.hasNext();) {
           Object result = (Object) iterator.next();
           if (result instanceof IBuildResult) {
                IBuildResult buildResult = (IBuildResult) result;
                processBuildResult(aBuildOutputCandidate, buildResult);
           }
      }

This concludes the main control loop performed in the method manageFolders().

Process the Build Result

If a build result is available for the currently processed build output folder, the folder should not be deleted. Furthermore, it is possible to access the build result data and implement additional automation in the processBuildResult() method. The method would typically need the build output folder and the build result as parameter.

private void processBuildResult(File aBuildOutputCandidate, IBuildResult buildResult) throws TeamRepositoryException {

It is possible to access values in the RTC build result retrieved from the team repository. Example methods that are easy to use are IBuildResult.isDeleteAllowed()and IBuildResult.isPersonalBuild().

For the purpose of managing build output folders, it is interesting to know if the build is completed. If the build is not completed, there is no need to do anything with the folder. The IBuildResult.getStatus() method provides information about this. The code below shows how to perform the check. If the build is not completed, the processBuildResult() method returns.

      System.out.println(aBuildOutputCandidate.getAbsolutePath()+" : Process Build Result!");
      if(!buildResult.getState().equals(BuildState.COMPLETED)){
           return;
      }

If the build is completed, it is interesting to check to see if the build result is associated with a release. The image below shows how this is represented in the build result editor in RTC. If a build result is associated to a release, it is automatically marked as ‘deletion not allowed’. This can be changed again later by checking the deletion allowed check-box.

A buid result is related to a release and can not be deleted. The 'Deletion Aallowed' checkbox provides an override.

If the build result is associated to a release, also called a deliverable, the build output folder related to it is important, and special publishing or backup operations can be performed.

The code below shows how to find IDeliverables related to the build result. If there are any IDeliverables, the build is associated with a release and the build output folder should be preserved. It should be added to some repository to be available for audits. It would be possible to do additional testing or to create a deliverable to publish it in an asset management system like the IBM Rational Asset Manager. All of this work is delegated to the addToBackup() operation.

After backup or publishing, the folder is marked as being a deliverable by calling the method markAsDeliverable(). The method can, for example, create a unique file in the build output folder. This can be used to ensure that the backup or publishing is only performed once and not repeated in every run of the BuildOutputManager.

      IWorkItemCommon workItemCommon = (IWorkItemCommon) aTeamRepository.getClientLibrary(IWorkItemCommon.class);
      List<IDeliverable> deliverables = workItemCommon.findDeliverablesByArtifact(buildResult.getItemHandle(), IDeliverable.FULL_PROFILE,null);
      if (null != deliverables && !deliverables.isEmpty()) {
           addToBackUp(aBuildOutputCandidate,buildResult);
           markAsDeliverable(aBuildOutputCandidate);
      }

Handle Backup and Publishing

The addToBackup() method depends on the given infrastructure. Therefore the article shows only code relevant to the RTC part. Minimally the method would have the following signature:

 private void addToBackUp(File aBuildOutputCandidate, IBuildResult buildResult) throws TeamRepositoryException {

Before backup or publishing is done, the code can check if the folder has already been published. In this case, there is nothing to do.

      if (isMarkedAsDeliverable(aBuildOutputCandidate)) {
           return;
      }

The following example code assumes there is some code that publishes to an IBM Rational Asset Manager instance in the enterprise using the available Java API and returns the URL for the published artifact version.

      String theURL = publishToIRAM(aBuildOutputCandidate); 

The following code attaches the URL of the artefact version, returned by the publishing method to the build result. It creates a new build result contribution with the target URL and adds the contribution to the build result.

      IBuildResultContribution link = BuildItemFactory.createBuildResultContribution();
      link.setLabel("Download Artifact from Asset Manager");
      link.setExtendedContributionTypeId(IBuildResultContribution.LINK_EXTENDED_CONTRIBUTION_ID);
      link.setExtendedContributionProperty(IBuildResultContribution.PROPERTY_NAME_URL, theURL);       ITeamBuildClient buildClient = (ITeamBuildClient)aTeamRepository.getClientLibrary(ITeamBuildClient.class);
      buildClient.addBuildResultContribution((IBuildResultHandle) buildResult.getItemHandle(), link, null);

The last statement throws exceptions that would require handling at some point.

Additional code could copy the build output somewhere, or add it to a backup system.

Handle Unqualified Folders

There can be many reasons why a folder could fail to qualify to be a build output folder. Some examples are

  • Build definitions or implementations don’t conform to the rules of a build.properties file. 
  • Builds fail before the file is written. 
  • The folder structure is wrong. 

There is a delay between the creation of the folder and the creation of the build.properties file. In cases where the BuildOutputManager is run asynchronously, it would be dangerous to just delete the folders since the build may be running.

Here are some options for a deeper analysis of the folder and its content to help with a decision:

  • Check if the folder contains Jazz SCM metadata, typically stored in a folder named .jazz5
  • Check the age of the folder.

Since there is no general rule, the code for this method is left blank for the purpose of this article. It would be possible to:

  • Delete the folder based on its last modified date. For example, keep those folders only a week.
  • Build a list of these folders and notify a person about the folders that don’t conform and need to be checked. A large number of these folders could point to build problems or inconsistencies in the build output structure.

Add some System.out.println() statements to the method handleNotQualifyingFolders() for testing and enhance the example with your own business logic later.

private void handleNotQualifyingFolders(File aBuildOutputCandidate) {
      System.out.println(aBuildOutputCandidate.getAbsolutePath() + " does not qualify as Build Output folder");
}

Load the Build Properties

The method to load the build.properties file from disk still needs to be implemented. The code below shows a possible solution. It just uses the java.util.Properties class and its capability to load from a FileInputStream. If the file can not be located or anything else goes wrong, it returns a null value.

 private Properties loadBuildProperties(File aBuildOutputCandidate) {
      try {
           File buildOutputPopertyFile = new File(aBuildOutputCandidate,"build.properties");
           if (!buildOutputPopertyFile.exists()) {
                return null; // there was no property file
           }
           Properties buildOutputProperties = new Properties();
           FileInputStream fis = new FileInputStream(buildOutputProperties.getAbsolutePath());
           buildOutputPoperties.load(fis);
           fis.close();
           return buildOutputProperties;
      } catch (Exception e) { // Ignore
      }       return null;
}

Implement Missing Code

It is not necessary to implement the methods BuildOutputManager.markAsDeliverable() and BuildOutputManager.isMarkedAsDeliverable() to run some tests. The only side effect of not implementing these methods is, that build results associated to a release will get a new link to the published site attached in every run.

BuildOutputManager.markAsDeliverable() could create a file with a fixed name such as ‘DELIVERABLE’ in the build output folder.

private void markAsDeliverable(File aBuildOutputCandidate) {
      try {
           (new File(aBuildOutputCandidate, "DELIVERABLE")).createNewFile();
      } catch (IOException e) {
           // do something
      }
}

This would result in the following change to the build output folder:

A build output folder now contains the file that marks it as deliverable.

BuildOutputManager.isMarkedAsDeliverable() could then check if the file exists and protect the code from repeating the backup and build result manipulation.

 private boolean isMarkedAsDeliverable(File aBuildResultCandidate) {
      try {
           File isMarkedAsDeliverable = new File(aBuildResultCandidate, "DELIVERABLE");
           if (isMarkedAsDeliverable.exists()) {
                System.out.println(aBuildOutputCandidate.getAbsolutePath()+" : Is a Deliverable!");
                return true;
           }
           } catch (Exception e) {
                // do something
      }
      return false;
}

It is not necessary to implement the checkDeleteFolder() method for testing. The simplest implementation is to delete the files in a given folder and recursively delete the sub folders. Add some output statement for testing. Deliverables should not be deleted.

 private void checkDeleteFolder(File aBuildOutputCandidate) {
      if (!isMarkedAsDeliverable(aBuildOutputCandidate)) {
           System.out.println(aBuildOutputCandidate.getAbsolutePath()+" : Deleting!");
      }
}

The necessary code to publishToIRAM is not described here. It is sufficient to create a stub and return a valid URL for demonstration purposes.

 private String publishToIRAM(File aBuildOutputCandidate) {
      return "http://jazz.net/library";
}

Enhance the example with your own business logic.

Debug the Code

To run or debug the code in Eclipse, create a ‘Debug Configuration’.

Create a debug configuration to run the code.

In the following dialog, create a new ‘Java Application’ launch configuration. Select the correct project and the BuidOutputManager class with the main() method.

Create a new 'Launch Configuration' for the Java Application

Make sure to switch to the Arguments tab and to enter the Program Arguments like repository URL, user name, password and the build output root folder for example:

"https://clm.example.com:9443/ccm" "build" "build" "C:/RTC3011Dev/jazz/buildsystem/buildengine/eclipse/JKEBuild"

Specify the required parameters in the 'Arguments' tab of the launch configuration.

Hit debug to start debugging. It should be possible to follow the code and see the output in the console window.

Create a release for one of the public builds, run the BuildOutputManager again and check that it gets handled according to the requirements and has an external link published.

Build result that is marked as deliverable now contains link to published IBM Rational Asset Manager Asset Version

Deploying the Solution

Since the BuildOutputManager is a just a small Java application using the Plain Java Client Libraries, it can be deployed on all servers running a Jazz Build Engine. It is also possible to package it as a JAR file. It will run as long as the path to the installed Plain Java Client Libraries is provided.

  • It could be started with any scheduler that is available on the deployed system.
  • It could be also be run by the Jazz Build Engine e.g. using the command line. In this case create a build definition for each build engine to schedule build requests running the BuildOutputManager. If that data is provided in a stream including the PJCL libraries, then this would spare the separate install.

To start the tool outside of Eclipse make sure the class is compiled. The following lines in a script should run the tool assuming the eclipse project is in the folder <rtcdevdir>/workspace and named according to this article, otherwise change the paths, package and class names.

cd C:/RTC3011Dev/workspace/com.ibm.js.team.build.manager
java -cp ./bin;C:/RTC301Dev/installs/PlainJavaAPI/* com.ibm.js.team.build.manager.BuildOutputManager "https://clm.example.com:9443/ccm" "build" "build" "C:/RTC3011Dev/jazz/buildsystem/buildengine/eclipse/JKEBuild"

Since the BuildOutputManager searches for build results independent from projects, the login user provided needs to have sufficient permissions to at least read the build results for every project on the target RTC server. To modify the build results in the addToBackup() method this user needs additional permissions.

Summary

This article shows how the Plain Java Client Libraries can be used to implement automation for managing build output folders created by Jazz Build Engines for teams using continuous integration. This automation can be used to delete folders no longer needed. It will also publish folders related to deliveries to a backup system, as well as to any other asset management system that provides Java API. The automation provided helps to reduce manual work, as well as reducing required disk space and preserves build output that is needed for audits or delivery creation. Further enhancements to this implementation can enhance the build result with additional modifications and creates the opportunity for even more automation.

For more information


About the author

Ralph Schoon is a member of the Jazz Jumpstart team.  The Jazz Jumpstart team is a worldwide group of development specialists who bring new and advanced Jazz-based technologies to customers. Please direct feedback and comments to ralph.schoon@de.ibm.com .