/*
* The MIT License
*
* Copyright (c) 2008-2013, Jenkins project, Seiji Sogabe
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.plugins.phing;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.plugins.phing.console.PhingConsoleAnnotator;
import hudson.tasks.Builder;
import hudson.util.ArgumentListBuilder;
import hudson.util.VariableResolver;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Properties;
import java.util.Set;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* Phing Builder Plugin.
*
* @author Seiji Sogabe
*/
public final class PhingBuilder extends Builder {
@Extension
public static final PhingDescriptor DESCRIPTOR = new PhingDescriptor();
/**
* Optional build script.
*/
private final String buildFile;
/**
* Identifies {@link PhingInstallation} to be used.
*/
private final String name;
/**
* List of Phing targets to be invoked.
* If not specified, use "build.xml".
*/
private final String targets;
/**
* Optional properties to be passed to Phing.
* Follow {@link Properties} syntax.
*/
private final String properties;
/**
* Whether uses ModuleRoot as working directory or not.
* @since 0.9
*/
private final boolean useModuleRoot;
/**
* Additional options to be passed to Phing.
* @since 0.12
*/
private final String options;
public String getBuildFile() {
return buildFile;
}
public String getName() {
return name;
}
public String getTargets() {
return targets;
}
public String getProperties() {
return properties;
}
public boolean isUseModuleRoot() {
return useModuleRoot;
}
public String getOptions() {
return options;
}
@DataBoundConstructor
public PhingBuilder(String name, String buildFile, String targets, String properties,
boolean useModuleRoot, String options) {
super();
this.name = Util.fixEmptyAndTrim(name);
this.buildFile = Util.fixEmptyAndTrim(buildFile);
this.targets = Util.fixEmptyAndTrim(targets);
this.properties = Util.fixEmptyAndTrim(properties);
this.useModuleRoot = useModuleRoot;
this.options = Util.fixEmptyAndTrim(options);
}
public PhingInstallation getPhing(EnvVars env, BuildListener listener)
throws IOException, InterruptedException {
PhingInstallation.DescriptorImpl desc = getPhingInstallationDescriptor();
for (PhingInstallation inst : desc.getInstallations()) {
if (name != null && name.equals(inst.getName())) {
PhingInstallation pi = inst.forNode(Computer.currentComputer().getNode(), listener);
return pi.forEnvironment(env);
}
}
return null;
}
private PhingInstallation.DescriptorImpl getPhingInstallationDescriptor() {
return (PhingInstallation.DescriptorImpl) Jenkins.getInstance().getDescriptor(PhingInstallation.class);
}
@Override
public Descriptor<Builder> getDescriptor() {
return DESCRIPTOR;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
ArgumentListBuilder args = new ArgumentListBuilder();
EnvVars env = build.getEnvironment(listener);
PhingInstallation pi = getPhing(env, listener);
if (pi != null) {
env.overrideAll(pi.getEnvVars());
}
// PHP Command
if (launcher.isUnix()) {
args.add(computePhpCommand(pi, env));
}
// Phing Command
args.add(computePhingCommand(pi, launcher));
VariableResolver<String> vr = build.getBuildVariableResolver();
// build.xml
String script = (buildFile == null) ? "build.xml" : buildFile;
FilePath buildScript = lookingForBuildScript(build, env.expand(script), listener);
if (buildScript == null) {
listener.getLogger().println(Messages.Phing_NotFoundABuildScript(script));
return false;
}
args.add("-buildfile", buildScript.getRemote());
Set<String> sensitiveVars = build.getSensitiveBuildVariables();
args.addKeyValuePairs("-D", build.getBuildVariables(), sensitiveVars);
args.addKeyValuePairsFromPropertyString("-D", env.expand(properties), vr, sensitiveVars);
// Targets
String expandedTargets = Util.replaceMacro(env.expand(targets), vr);
if (expandedTargets != null) {
args.addTokenized(expandedTargets.replaceAll("[\t\r\n]+", " "));
}
// Options
String expandedOptions = Util.replaceMacro(env.expand(options), vr);
if (expandedOptions == null || !expandedOptions.contains("-logger ")) {
// avoid printing esc sequence
args.add("-logger", "phing.listener.DefaultLogger");
}
if (expandedOptions != null) {
args.addTokenized(expandedOptions.replaceAll("[\t\r\n]", " "));
}
if (!launcher.isUnix()) {
args = args.toWindowsCommand();
}
// Working Directory
// since 0.9
FilePath working = useModuleRoot ? build.getModuleRoot() : buildScript.getParent();
listener.getLogger().println(Messages.Phing_WorkingDirectory(working));
final long startTime = System.currentTimeMillis();
try {
PhingConsoleAnnotator pca = new PhingConsoleAnnotator(listener.getLogger(), build.getCharset());
int result;
try {
result = launcher.launch().cmds(args).envs(env).stdout(pca).pwd(working).join();
} finally {
pca.forceEol();
}
return result == 0;
} catch (final IOException e) {
Util.displayIOException(e, listener);
final long processingTime = System.currentTimeMillis() - startTime;
final String errorMessage = buildErrorMessage(pi, processingTime);
e.printStackTrace(listener.fatalError(errorMessage));
return false;
}
}
private String computePhpCommand(PhingInstallation pi, EnvVars env) {
String command = env.get("PHP_COMMAND");
if (command == null && pi != null) {
if (pi.getPhpCommand() != null) {
command = pi.getPhpCommand();
}
}
return env.expand(command);
}
private String computePhingCommand(PhingInstallation pi, Launcher launcher)
throws IOException, InterruptedException {
if (pi == null) {
return PhingInstallation.getExecName(launcher);
}
return pi.getExecutable(launcher);
}
private FilePath lookingForBuildScript(AbstractBuild<?, ?> build, String script, BuildListener listener)
throws IOException, InterruptedException {
PrintStream logger = listener.getLogger();
FilePath buildScriptPath = build.getModuleRoot().child(script);
logger.println("looking for '" + buildScriptPath.getRemote() + "' ... ");
if (buildScriptPath.exists()) {
return buildScriptPath;
}
FilePath workspace = build.getWorkspace();
if (workspace != null) {
buildScriptPath = workspace.child(script);
logger.println("looking for '" + buildScriptPath.getRemote() + "' ... ");
if (buildScriptPath.exists()) {
return buildScriptPath;
}
}
buildScriptPath = new FilePath(new File(script));
logger.println("looking for '" + buildScriptPath.getRemote() + "' ... ");
if (buildScriptPath.exists()) {
return buildScriptPath;
}
// build script not Found
return null;
}
private String buildErrorMessage(final PhingInstallation pi, final long processingTime) {
final StringBuffer msg = new StringBuffer();
msg.append(Messages.Phing_ExecFailed());
if (pi == null && processingTime < 1000) {
PhingInstallation[] installations = getPhingInstallationDescriptor().getInstallations();
if (installations.length == 0) {
msg.append(Messages.Phing_GlocalConfigNeeded());
} else {
msg.append(Messages.Phing_ProjectConfigNeeded());
}
}
return msg.toString();
}
}