|Most Shared

Rapid FindBugs tech coding java
20 Mar 2015at Alpharetta

Problem

  • Given a large multi-module maven project
  • and we need to run find-bugs or any static code analysis plugins
  • when I want my build to run faster,
  • then I need a mechanism to paralleize the code analysis using more threads, in addition to maven default concurrency setting

Solution

Wrote a custom Maven mojo RapidFindBugsMojo, that would start Runnable Rapider per module. Each thread running Rapider would run FindBugs across the class files in that module, and would dump results in a concurrent hashmap using a countdown latch. Finally a decision is made whether to pass or fail the build based on the bugs collected, and prints out a report. This custom rapid-maven-plugin cut 25-30% of our continuous integration cycle time.

Code

Please refer complete code checked here. Some excerpts below..

.

RapidFindBugsMojo

 package com.vijayrc.maven;

 import com.vijayrc.maven.output.AllResults;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;

 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;

 import static edu.umd.cs.findbugs.Priorities.*;
 import static java.io.File.separator;
 import static java.util.Arrays.binarySearch;

 /**
 * Goal which runs findbugs simultaneously on sub-modules
 */
     @Mojo(name = "findbugs")
     public class RapidFindBugsMojo extends AbstractMojo {

     @Parameter(property = "srcDir")
     private String srcDir;

     @Parameter(property = "includeFilterFile", defaultValue = "")
     private String includeFilterFile;

     @Parameter(property = "threadCount", defaultValue = "5")
     private int threadCount;

     @Parameter(property = "skip", defaultValue = "true")
     private boolean skip;

     @Parameter(property = "priority", defaultValue = "3")
     private int priority;

     @Parameter(defaultValue = "false")
     private boolean stopBuild;

     public void execute() throws MojoExecutionException, MojoFailureException {
         if (skip) return;

         Log log = getLog();
         log.info("rapid-findbugs: srcDir=" + srcDir);
         log.info("rapid-findbugs: includeFilterFile=" + includeFilterFile);
         log.info("rapid-findbugs: skip=" + skip);
         log.info("rapid-findbugs: threadCount=" + threadCount);
         log.info("rapid-findbugs: priority=" + priority);

         if (isEmpty(srcDir)) {
             log.error("rapid-findbugs: srcDir cannot be empty!");
             return;
         }
         if (isEmpty(includeFilterFile)) {
             log.error("rapid-findbugs: includeFilterFile cannot be empty!");
             return;
         }
         AllResults allResults = new AllResults();
         try {
             List<File> filteredModules = filter(new File(srcDir).listFiles());
             CountDownLatch latch = new CountDownLatch(filteredModules.size());
             ExecutorService executors = Executors.newFixedThreadPool(threadCount);
             try {
             for (File module : filteredModules) {
             String auxPath = module.getAbsolutePath() + separator + "target";
             String path = auxPath + separator + "classes";

             int priorityLevel = getPriorityLevel(priority);
             executors.submit(new Rapider(latch, allResults, includeFilterFile, path, log, auxPath, priorityLevel, module.getName()));
             }
            latch.await();
         } finally {
            executors.shutdown();
            log.info("rapid-findbugs: all modules completed");
         }
         } catch (Exception e) {
            e.printStackTrace();
            throw new MojoExecutionException(e.getMessage());
         }
         boolean codeSafe = allResults.isCodeSafe();
         log.info("rapid-findbugs: is code safe? " + codeSafe);
         if (!codeSafe) {
            String message = allResults.print();
             log.error(message);
             if (stopBuild)
                throw new MojoFailureException(message);
        }
     }

     private int getPriorityLevel(int priority) {
         int[] validPriorities = new int[]{EXP_PRIORITY, HIGH_PRIORITY, LOW_PRIORITY, IGNORE_PRIORITY, NORMAL_PRIORITY};
         return binarySearch(validPriorities, priority) != 0 ? priority : LOW_PRIORITY;
     }

     private List<File> filter(File[] modules) {
         List<File> filteredModules = new ArrayList<File>();
         for (File module : modules)
             if (module.isDirectory() && containsBinaries(module))
             filteredModules.add(module);
             return filteredModules;
     }

     private boolean containsBinaries(File module) {
         File[] files = module.listFiles();
         if (files == null) return false;
         for (File file : files) {
            if (file.getName().equals("pom.xml") || file.getName().equals("target")) return true;
         }
         return false;
     }

     private boolean isEmpty(String input) {
        return input == null || srcDir.trim().length() == 0;
     }
 }
    

Rapider

 package com.vijayrc.maven;

 import com.vijayrc.maven.output.AllResults;
 import com.vijayrc.maven.output.Result;
 import edu.umd.cs.findbugs.*;
 import edu.umd.cs.findbugs.config.UserPreferences;
 import org.apache.commons.io.FileUtils;
 import org.apache.maven.plugin.logging.Log;

 import java.io.File;
 import java.util.concurrent.CountDownLatch;

 import static java.io.File.separator;

 /**
 * a thread to run findbugs on a module
 */
 public class Rapider implements Runnable {

     private CountDownLatch latch;
     private AllResults allResults;
     private int priorityLevel;
     private Log log;

     private String includeFilterFile;
     private String srcDir;
     private String moduleName;
     private String auxPath;

     public Rapider(CountDownLatch latch, AllResults allResults, String includeFilterFile, String srcDir, Log log, String auxPath, int priorityLevel, String moduleName) {
         this.log = log;
         this.latch = latch;
         this.srcDir = srcDir;
         this.allResults = allResults;
         this.includeFilterFile = includeFilterFile;
         this.auxPath = auxPath;
         this.priorityLevel = priorityLevel;
         this.moduleName = moduleName;
       }

     public void run() {
             long id = Thread.currentThread().getId();
             try {
                 log.info("rapid-findbugs: start-module|" + moduleName + "|" + id);
                 Project project = getProject();
                 Result result = new Result();

                 FindBugs2 findBugs2 = new FindBugs2();
                 findBugs2.addFilter(includeFilterFile, true);
                 findBugs2.setProject(project);
                 findBugs2.setBugReporter(getBugReporter(project));
                 findBugs2.setUserPreferences(getUserPreferences());
                 findBugs2.setDetectorFactoryCollection(DetectorFactoryCollection.instance());
                 findBugs2.execute();

                 for (BugInstance bug : findBugs2.getBugReporter().getBugCollection().getCollection()) {
                 if (bug.getPriority() <= priorityLevel) {
                    result.addBug("priority="+bug.getPriority() + "|rank=" + bug.getBugRank() + "|desc=" + bug.getMessageWithPriorityType());
                 }
             }
             allResults.add(moduleName, result);
             log.info("rapid-findbugs: end-module|" + moduleName + "|" + id);

             } catch (Exception e) {
                 e.printStackTrace();
                 log.error("rapid-findbugs: end-module|" + moduleName + "|" + e.getClass());
             } finally {
                latch.countDown();
             }
     }

     private Project getProject() {
         Project project = new Project();
         project.addAuxClasspathEntry(auxPath);
         File dir = new File(srcDir);
         for (File classFile : FileUtils.listFiles(dir, new String[]{"class", "jar"}, true)) {
         if (!classFile.getPath().contains(separator + "test" + separator))
            project.addFile(classFile.getAbsolutePath());
         }
         return project;
     }

     private UserPreferences getUserPreferences() {
         UserPreferences preferences = UserPreferences.createDefaultUserPreferences();
         preferences.enableAllDetectors(true);
         return preferences;
     }

     private BugReporter getBugReporter(Project project) {
         BugReporter bugReporter = new BugCollectionBugReporter(project);
         bugReporter.setPriorityThreshold(priorityLevel);
         bugReporter.setErrorVerbosity(0);
         return bugReporter;
     }
 }
    
comments powered by Disqus

All content except noted photos and videos copyright © Vijayaraj Chakravarthy. All rights reserved. *Any images or videos not listed as mine are copyright to their respective owners and were used under creative common license or fair use standards. If a photo or video is your material and you do not wish it to be on the site, please email me vijayrc@outlook.com and I will remove it immediately.