package org.jfrog.hudson.pipeline.steps.conan; import com.google.inject.Inject; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.model.Run; import hudson.model.TaskListener; import hudson.remoting.VirtualChannel; import hudson.util.ArgumentListBuilder; import hudson.util.IOUtils; import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution; import org.jenkinsci.plugins.workflow.steps.StepContextParameter; import org.jfrog.build.api.Build; import org.jfrog.hudson.pipeline.Utils; import org.jfrog.hudson.pipeline.types.buildInfo.BuildInfo; import org.kohsuke.stapler.DataBoundConstructor; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; public class RunCommandStep extends AbstractStepImpl { private String command; private String conanHome; private String buildLogPath; private BuildInfo buildInfo; @DataBoundConstructor public RunCommandStep(String command, String conanHome, String buildLogPath, BuildInfo buildInfo) { this.command = command; this.conanHome = conanHome; this.buildInfo = buildInfo; this.buildLogPath = buildLogPath; } public String getCommand() { return command; } public String getConanHome() { return conanHome; } public BuildInfo getBuildInfo() { return buildInfo; } public String getBuildLogPath() { return buildLogPath; } public static class Execution extends AbstractSynchronousNonBlockingStepExecution<BuildInfo> { private static final long serialVersionUID = 1L; @StepContextParameter private transient Run build; @StepContextParameter private transient TaskListener listener; @StepContextParameter private transient Launcher launcher; @Inject(optional = true) private transient RunCommandStep step; @StepContextParameter private transient FilePath ws; @StepContextParameter private transient EnvVars env; @Override protected BuildInfo run() throws Exception { BuildInfo buildInfo = Utils.prepareBuildinfo(build, step.getBuildInfo()); FilePath conanHomeDirectory = new FilePath(launcher.getChannel(), step.getConanHome()); persistBuildProperties(buildInfo, conanHomeDirectory); EnvVars extendedEnv = new EnvVars(env); extendedEnv.put(Utils.CONAN_USER_HOME, step.getConanHome()); execConanCommand(extendedEnv); FilePath logFilePath = execConanCollectBuildInfo(extendedEnv); Build regularBuildInfo = Utils.getGeneratedBuildInfo(build, listener, launcher, logFilePath.getRemote()); buildInfo.append(regularBuildInfo); return buildInfo; } // Conan collect buildInfo as part of the tasks execution. // In order to transform the collected buildInfo into Artifctory buildInfo format we need to execute the conan_build_info command. // The conan_build_info command exepect to get a path for the output file. private FilePath execConanCollectBuildInfo(EnvVars extendedEnv) throws IOException, InterruptedException { FilePath logFilePath = ws.createTextTempFile("conan", "build-info", "", false); ArgumentListBuilder args = new ArgumentListBuilder(); args.add("conan_build_info"); args.add(step.getBuildLogPath()); args.add("--output"); args.add(logFilePath.getRemote()); Utils.exeConan(args, ws.getRemote(), launcher, listener, build, extendedEnv); return logFilePath; } private void execConanCommand(EnvVars extendedEnv) { ArgumentListBuilder args = new ArgumentListBuilder(); args.add("conan"); args.addTokenized(step.getCommand()); Utils.exeConan(args, ws.getRemote(), launcher, listener, build, extendedEnv); } private void persistBuildProperties(BuildInfo buildInfo, FilePath conanHomeDirectory) throws IOException, InterruptedException { FilePath buildProperties = new FilePath(conanHomeDirectory, ".conan").child("artifacts.properties"); final String buildName = buildInfo.getName(); final String buildNumber = buildInfo.getNumber(); final long startTime = buildInfo.getStartDate().getTime(); buildProperties.touch(0); buildProperties.act(new FilePath.FileCallable<Boolean>() { public Boolean invoke(File conanProperties, VirtualChannel channel) throws IOException, InterruptedException { final String propsPrefix = "artifact_property_"; Properties props = new Properties(); props.setProperty(propsPrefix + "build.name", buildName); props.setProperty(propsPrefix + "build.number", buildNumber); props.setProperty(propsPrefix + "build.timestamp", String.valueOf(startTime)); FileOutputStream fos = null; try { fos = new FileOutputStream(conanProperties.getCanonicalFile()); props.store(fos, "Build properties"); } finally { IOUtils.closeQuietly(fos); } return true; } }); } } @Extension public static final class DescriptorImpl extends AbstractStepDescriptorImpl { public DescriptorImpl() { super(RunCommandStep.Execution.class); } @Override public String getFunctionName() { return "RunConanCommand"; } @Override public String getDisplayName() { return "Run a Conan command"; } @Override public boolean isAdvanced() { return true; } } }