/*******************************************************************************
* Copyright (c) 2017 Synopsys, Inc
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Synopsys, Inc - initial implementation and documentation
*******************************************************************************/
package jenkins.plugins.coverity;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Proc;
import hudson.model.*;
import hudson.remoting.Channel;
import hudson.tasks.Builder;
import jenkins.plugins.coverity.CoverityTool.CovBuildCompileCommand;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* A decorated {@link Launcher} that puts the given set of arguments as a prefix to any commands that it invokes.
*/
public class CoverityLauncher extends Launcher {
private static final Logger logger = Logger.getLogger(CoverityLauncherDecorator.class.getName());
private final Launcher decorated;
private String[] prefix;
private final String toolsDir;
private final Node node;
private EnvVars envVars;
public CoverityLauncher(Launcher decorated, Node node) {
super(decorated);
this.decorated = decorated;
this.node = node;
this.toolsDir = node.getRootPath().child("tools").getRemote();
}
private String[] getPrefix() {
String[] tp = prefix.clone();
if(tp.length > 0){
tp[0] = CoverityUtils.getCovBuild(decorated.getListener(), node);
}
return tp;
}
@Override
public Proc launch(ProcStarter starter) throws IOException {
EnvVars buildEnvVars = CoverityUtils.getBuildEnvVars(listener);
if (envVars == null || envVars.isEmpty()) {
envVars = buildEnvVars;
} else if (buildEnvVars != null) {
envVars.overrideAll(buildEnvVars);
}
AbstractBuild build = CoverityUtils.getBuild();
AbstractProject project = build.getProject();
CoverityPublisher publisher = (CoverityPublisher) project.getPublishersList().get(CoverityPublisher.class);
// Setup(or resolve) intermediate directory
setupIntermediateDirectory(build, this.getListener(), node);
// Any Coverity Post-build action such as cov-analyze, cov-import-scm, etc will not be wrapped
// with the cov-build.
if (CoverityLauncherDecorator.CoverityPostBuildAction.get()) {
String[] starterEnvVars = starter.envs();
starterEnvVars = CoverityUtils.addEnvVars(starterEnvVars, envVars);
starter = starter.envs(starterEnvVars);
return decorated.launch(starter);
}
boolean isCoverityBuildStepEnabled = false;
// Check if there are any Coverity Build Step configured.
// This is required to support backward compatibility.
if (project instanceof Project) {
List<Builder> builders = ((Project)project).getBuilders();
for (Builder buildStep : builders) {
if (buildStep.getDescriptor() instanceof CoverityBuildStep.CoverityBuildStepDescriptor) {
isCoverityBuildStepEnabled = true;
break;
}
}
}
// The first condition is for the case where there are no coverity build steps.
// Then we want to wrap the build steps with cov-build. This is to support backward compatibility
// The second condition is for the case where there are coverity build step and
// the current build step is the coverity build step.
if (!isCoverityBuildStepEnabled
|| (isCoverityBuildStepEnabled && CoverityLauncherDecorator.CoverityBuildStep.get())) {
InvocationAssistance invocationAssistance = CoverityUtils.getInvocationAssistance();
String home = publisher.getDescriptor().getHome(node, envVars);
if(invocationAssistance != null && invocationAssistance.getSaOverride() != null) {
home = new CoverityInstallation(
CoverityUtils.evaluateEnvVars(invocationAssistance.getSaOverride(), envVars, invocationAssistance.getUseAdvancedParser())).forEnvironment(envVars).getHome();
}
List<String> cmds = starter.cmds();
if (invocationAssistance != null) {
List<String> args = new CovBuildCompileCommand(build, decorated, decorated.getListener(), publisher, home, envVars).constructArguments();
prefix = args.toArray(new String[args.size()]);
cmds.addAll(0, args);
} else {
prefix = new String[0];
}
boolean useAdvancedParser = false;
if(invocationAssistance != null && invocationAssistance.getUseAdvancedParser()){
useAdvancedParser = true;
}
//skip jdk installations
String lastArg = cmds.get(cmds.size() - 1);
if(lastArg.startsWith(toolsDir) && lastArg.endsWith(".sh")) {
logger.info(lastArg + " is a tools script, skipping cov-build");
return decorated.launch(starter);
}
/**
* Overrides environment variables on the ProcStarter.
*
* First, get the environment variables from the ProcStarter. Then, add more environment variables to it.
* Finally, creates a new ProcStarter object identically to the first one but with overridden variables.
*/
String[] starterEnvVars = starter.envs();
starterEnvVars = CoverityUtils.addEnvVars(starterEnvVars, envVars);
starter = starter.envs(starterEnvVars);
cmds = CoverityUtils.prepareCmds(cmds, envVars, useAdvancedParser);
starter = starter.cmds(cmds);
boolean[] masks = starter.masks();
if(masks == null) {
masks = new boolean[cmds.size()];
starter = starter.masks(masks);
} else {
starter = starter.masks(prefix(masks));
}
CoverityLauncherDecorator.CoverityBuildStep.set(false);
}
return decorated.launch(starter);
}
@Override
public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String, String> envVars) throws IOException, InterruptedException {
String lastArg = cmd[cmd.length - 1];
if(lastArg.startsWith(toolsDir) && lastArg.endsWith(".sh")) {
logger.info(lastArg + " is a tools script, skipping cov-build");
decorated.launchChannel(cmd, out, workDir, envVars);
}
return decorated.launchChannel(prefix(cmd), out, workDir, envVars);
}
/*
* When running remotely, overriding this method makes sure that the platform is detected correctly.
*/
@Override
public boolean isUnix() {
return decorated.isUnix();
}
@Override
public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
decorated.kill(modelEnvVars);
}
private String[] prefix(String[] args) {
String[] newArgs = new String[args.length + prefix.length];
System.arraycopy(getPrefix(), 0, newArgs, 0, prefix.length);
System.arraycopy(args, 0, newArgs, prefix.length, args.length);
return newArgs;
}
private boolean[] prefix(boolean[] args) {
boolean[] newArgs = new boolean[args.length + prefix.length];
System.arraycopy(args, 0, newArgs, prefix.length, args.length);
return newArgs;
}
/**
* Resolves environment variables on the specified intermediate directory. If the result is a null object or the
* path is empty an exception is thrown.
*/
public FilePath resolveIntermediateDirectory(AbstractBuild<?,?> build, TaskListener listener, Node node, String idirInput){
FilePath idirFilePath = null;
try {
String idir = EnvParser.interpolateRecursively(idirInput, 1, envVars);
if(idir == null || idir.isEmpty()){
throw new Exception("The specified Intermediate Directory is not valid: " + idirInput);
}
idirFilePath = new FilePath(node.getChannel(), idir);
} catch (ParseException e) {
CoverityUtils.handleException(e.getMessage(), build, listener, e);
} catch (Exception e){
CoverityUtils.handleException("An error occured while setting intermediate directory: " + idirInput, build, listener, e);
}
return idirFilePath;
}
/**
* Sets the value of environment variable "COV_IDIR" and creates necessary directories. This variable is used as argument of "--dir" for
* cov-build.
*
* If the environment variable has already being set then that value is used. Otherwise, the value of
* this variable is set to either a temporary directory or the idir specified on the UI under the
* Intermediate Directory option.
*
* Notice this variable must be resolved before running cov-build. Also this method creates necessary directories.
*/
public void setupIntermediateDirectory(@Nonnull AbstractBuild<?,?> build, @Nonnull TaskListener listener, @Nonnull Node node){
Validate.notNull(build, AbstractBuild.class.getName() + " object can't be null");
Validate.notNull(listener, TaskListener.class.getName() + " object can't be null");
Validate.notNull(node, Node.class.getName() + " object can't be null");
Validate.notNull(envVars, EnvVars.class.getName() + " object can't be null");
if(!envVars.containsKey("COV_IDIR")){
FilePath temp;
InvocationAssistance invocationAssistance = CoverityUtils.getInvocationAssistance(build);
try {
if(invocationAssistance == null || invocationAssistance.getIntermediateDir() == null ||
invocationAssistance.getIntermediateDir().isEmpty()){
FilePath coverityDir = node.getRootPath().child("coverity");
coverityDir.mkdirs();
temp = coverityDir.createTempDir("temp-", null);
} else {
// Gets a not null nor empty intermediate directory.
temp = resolveIntermediateDirectory(build, listener, node, invocationAssistance.getIntermediateDir());
if (temp != null) {
File idir = new File(temp.getRemote());
String workspace = envVars.get("WORKSPACE");
if (idir != null && !idir.isAbsolute() && !StringUtils.isEmpty(workspace)) {
String path = FilenameUtils.concat(workspace, temp.getRemote());
if (!StringUtils.isEmpty(path)) {
temp = new FilePath(temp.getChannel(), path);
}
}
temp.mkdirs();
}
}
if(invocationAssistance != null){
build.addAction(new CoverityTempDir(temp, invocationAssistance.getIntermediateDir() == null));
} else{
build.addAction(new CoverityTempDir(temp, true));
}
} catch(IOException e) {
throw new RuntimeException("Error while creating temporary directory for Coverity", e);
} catch(InterruptedException e) {
throw new RuntimeException("Interrupted while creating temporary directory for Coverity");
}
if (temp != null) {
envVars.put("COV_IDIR", temp.getRemote());
}
}
}
}