package com.mobilesorcery.sdk.core.build;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.IMemento;
import com.mobilesorcery.sdk.core.CommandLineExecutor;
import com.mobilesorcery.sdk.core.CoreMoSyncPlugin;
import com.mobilesorcery.sdk.core.IBuildResult;
import com.mobilesorcery.sdk.core.IBuildSession;
import com.mobilesorcery.sdk.core.IBuildState;
import com.mobilesorcery.sdk.core.IBuildVariant;
import com.mobilesorcery.sdk.core.IFileTreeDiff;
import com.mobilesorcery.sdk.core.IFilter;
import com.mobilesorcery.sdk.core.MoSyncBuilder;
import com.mobilesorcery.sdk.core.MoSyncProject;
import com.mobilesorcery.sdk.core.ParameterResolver;
import com.mobilesorcery.sdk.core.ParameterResolverException;
import com.mobilesorcery.sdk.core.PathExclusionFilter;
import com.mobilesorcery.sdk.core.PrivilegedAccess;
import com.mobilesorcery.sdk.core.Util;
import com.mobilesorcery.sdk.core.build.CommandLineBuildStep.Script;
import com.mobilesorcery.sdk.internal.builder.IncrementalBuilderVisitor;
public class CommandLineBuildStep extends AbstractBuildStep {
public final static String ID = "cmd";
public static class Script {
private String[][] commandLines;
public Script(String rawScript) {
parseRawScript(rawScript);
}
public Script(String[][] commandLines) {
this.commandLines = commandLines;
}
private void parseRawScript(String rawScript) {
String[] cmdLines = rawScript == null ? new String[0] :
rawScript.split("\\n");
this.commandLines = new String[cmdLines.length][];
for (int i = 0; i < cmdLines.length; i++) {
commandLines[i] = CommandLineExecutor.parseCommandLine(cmdLines[i]);
}
}
public String[][] getCommandLines() {
return commandLines;
}
}
public static class Factory implements IBuildStepFactory {
String rawScript;
boolean runPerFile = false;
String filePattern;
String name;
boolean failOnError = false;
private Script script;
public boolean shouldFailOnError() {
return failOnError;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public Factory() {
setName("Command Line");
}
@Override
public void load(IMemento memento) {
IMemento command = memento.getChild("cmd");
if (command != null) {
String rawScript = command.getTextData();
setRawScript(rawScript);
Boolean runPerFile = command.getBoolean("pf");
this.runPerFile = runPerFile == null ? false : runPerFile;
filePattern = command.getString("pt");
name = command.getString("name");
Boolean failOnError = command.getBoolean("foe");
this.failOnError = failOnError == null ? false : failOnError;
}
}
@Override
public void store(IMemento memento) {
IMemento command = memento.createChild("cmd");
command.putBoolean("pf", runPerFile);
command.putString("pt", filePattern);
command.putString("name", name);
command.putBoolean("foe", failOnError);
command.putTextData(rawScript);
}
@Override
public IBuildStep create() {
return new CommandLineBuildStep(this);
}
@Override
public String getId() {
return ID;
}
public Script getScript() {
return script;
}
public String getRawScript() {
return rawScript;
}
public void setRawScript(String script) {
this.rawScript = script;
this.script = new Script(rawScript);
}
public boolean shouldRunPerFile() {
return runPerFile;
}
public void setRunPerFile(boolean runPerFile) {
this.runPerFile = runPerFile;
}
public String getFilePattern() {
return filePattern;
}
public void setFilePattern(String filePattern) {
this.filePattern = filePattern;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean requiresPrivilegedAccess() {
return true;
}
@Override
public boolean isDefault() {
return false;
}
}
public static final class BuildStepParameterResolver extends ParameterResolver {
private IResource currentResource;
private final ParameterResolver fallback;
private String[] allResources;
public BuildStepParameterResolver(ParameterResolver fallback) {
this.fallback = fallback;
}
public void setCurrentResource(IResource currentResource) {
this.currentResource = currentResource;
}
@Override
public String get(String key) throws ParameterResolverException {
if (currentResource != null && "file".equals(key)) {
return currentResource.getFullPath().toFile().getAbsolutePath();
}
if ("@files".equals(key)) {
return Util.join(allResources, " ");
}
return fallback == null ? null : fallback.get(key);
}
public void setAllFiles(List<IResource> allResources) {
this.allResources = new String[allResources.size()];
int ix = 0;
for (IResource resource : allResources) {
this.allResources[ix] = resource.getFullPath().toFile().getAbsolutePath();
ix++;
}
}
@Override
public List<String> listPrefixes() {
ArrayList<String> result = new ArrayList<String>();
if (fallback != null) {
result.addAll(fallback.listPrefixes());
}
// TODO: Ehm... context sensitive perhaps!?
result.add("file");
result.add("@files");
return result;
}
@Override
public List<String> listAvailableParameters(String prefix) {
return fallback.listAvailableParameters(prefix);
}
}
private final Factory prototype;
public CommandLineBuildStep(Factory prototype) {
this.prototype = prototype;
setId(ID);
setName(prototype.name);
}
class Visitor extends IncrementalBuilderVisitor {
private PathExclusionFilter filter;
private final boolean runPerFile;
public Visitor(String filePattern, boolean runPerFile) {
this.runPerFile = runPerFile;
if (!Util.isEmpty(filePattern)) {
filter = PathExclusionFilter.parse(filePattern.split("\\s"));
}
}
@Override
public boolean doesAffectBuild(IResource resource) {
return filter == null || filter.inverseAccept(resource);
}
public void executeScript() throws IOException, ParameterResolverException {
// TODO: Refactor this!?
BuildStepParameterResolver resolver = (BuildStepParameterResolver) getParameterResolver();
resolver.setAllFiles(changedOrAddedResources);
if (runPerFile) {
for (IResource resource : changedOrAddedResources) {
resolver.setCurrentResource(resource);
CommandLineBuildStep.this.executeScript(resolver);
}
} else {
CommandLineBuildStep.this.executeScript(resolver);
}
}
@Override
public ParameterResolver getParameterResolver() {
BuildStepParameterResolver resolver = createParameterResolver(super.getParameterResolver());
return resolver;
}
}
@Override
public int incrementalBuild(MoSyncProject project, IBuildSession session,
IBuildVariant variant, IFileTreeDiff diff,
IBuildResult result, IProgressMonitor monitor) throws Exception {
if (prototype.requiresPrivilegedAccess()) {
PrivilegedAccess.getInstance().assertAccess(project);
}
Visitor visitor = new Visitor(prototype.filePattern, prototype.runPerFile);
visitor.setParameterResolver(getParameterResolver());
project.getWrappedProject().accept(visitor);
visitor.executeScript();
return CONTINUE;
}
public void executeScript(ParameterResolver resolver) throws IOException,
ParameterResolverException {
Script script = prototype.getScript();
String[][] cmdLines = script.getCommandLines();
for (int i = 0; i < cmdLines.length; i++) {
String[] cmdLine = cmdLines[i];
String[] resolvedCmdLine = new String[cmdLine.length];
for (int j = 0; j < cmdLine.length; j++) {
String arg = cmdLine[j];
String resolvedArg = Util.replace(arg, resolver);
resolvedCmdLine[j] = resolvedArg;
}
CommandLineExecutor executor = new CommandLineExecutor(MoSyncBuilder.CONSOLE_ID);
int exitCode = executor.runCommandLine(resolvedCmdLine);
if (prototype.shouldFailOnError() && exitCode != 0) {
throw new IOException(
MessageFormat.format(
"External command \"{0}\" failed: returned error code {1}",
prototype.name, exitCode));
}
}
}
public static BuildStepParameterResolver createParameterResolver(ParameterResolver fallback) {
return new BuildStepParameterResolver(fallback);
}
}