/* Copyright (C) 2009 Mobile Sorcery AB
This program is free software; you can redistribute it and/or modify it
under the terms of the Eclipse Public License v1.0.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License v1.0 for
more details.
You should have received a copy of the Eclipse Public License v1.0 along
with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html
*/
package com.mobilesorcery.sdk.internal;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import com.mobilesorcery.sdk.core.CoreMoSyncPlugin;
import com.mobilesorcery.sdk.core.IBuildVariant;
import com.mobilesorcery.sdk.core.IProcessConsole;
import com.mobilesorcery.sdk.core.IPropertyOwner;
import com.mobilesorcery.sdk.core.MoSyncBuilder;
import com.mobilesorcery.sdk.core.MoSyncProject;
import com.mobilesorcery.sdk.core.MoSyncTool;
import com.mobilesorcery.sdk.core.ParameterResolver;
import com.mobilesorcery.sdk.core.ParameterResolverException;
import com.mobilesorcery.sdk.core.PropertyUtil;
import com.mobilesorcery.sdk.core.Util;
import com.mobilesorcery.sdk.core.LineReader.ILineHandler;
public class PipeTool {
public static final String BUILD_RESOURCES_MODE = "-R";
public static final String BUILD_C_MODE = "-B";
public static final String BUILD_LIB_MODE = "-L";
public static final String BUILD_GEN_CPP_MODE = "-B -cpp";
public static final String BUILD_GEN_CS_MODE = "-B -cs";
public static final String BUILD_GEN_JAVA_MODE = "-B -java";
/**
* The default heap size; if no <code>-heapsize</code> argument is provided
* to pipe tool, then this is the size that will be used.
*/
public static final int DEFAULT_HEAP_SIZE_KB = 512;
/**
* The default stack size; if no <code>-stacksize</code> argument is provided
* to pipe tool, then this is the size that will be used.
*/
public static final int DEFAULT_STACK_SIZE_KB = 128;
/**
* The default data size; if no <code>-datasize</code> argument is provided
* to pipe tool, then this is the size that will be used.
*/
public static final int DEFAULT_DATA_SIZE_KB = 1024;
private static final String RESOURCE_DEPENDENCY_FILE_NAME = "resources.deps";
private static final int MAX_PIPE_TOOL_ARG_LENGTH = 162;
/**
* A return code from pipe tool that instructs the builder to skip
* any remaining build steps.
*/
public static final int SKIP_RETURN_CODE = -10000;
private IPath outputFile;
private String[] inputFiles;
private IProcessConsole console;
private IPath[] libraryPaths;
private String mode;
private boolean sld = true;
private IPath[] libraries;
private boolean dce;
private IPath exeDir;
private boolean noVerify;
private String[] extra;
private IProject project;
private ILineHandler linehandler;
private boolean collectStabs;
private String appCode;
private IPropertyOwner argumentMap;
private ParameterResolver resolver;
private IBuildVariant variant;
public PipeTool() {
}
public void setLibraryPaths(IPath[] libraryPaths) {
this.libraryPaths = libraryPaths;
}
public void setLibraries(IPath[] libraries) {
this.libraries = libraries;
}
public void setConsole(IProcessConsole console) {
this.console = console;
}
public void setOutputFile(IPath outputPath) {
this.outputFile = outputPath;
}
public void setInputFiles(String[] objectFiles) {
this.inputFiles = objectFiles;
}
public void setGenerateSLD(boolean sld) {
this.sld = sld;
}
public void setVariant(IBuildVariant variant) {
this.variant = variant;
}
/**
* <p>Sets some of the arguments of pipe tool;
* this class maps it to the proper command line
* arguments.</p>
* <p><emph>TODO:</emph>Could we move some other arguments here, eg "extras"
* @param argumentMap
*/
public void setArguments(IPropertyOwner argumentMap) {
this.argumentMap = argumentMap;
}
/**
* <p>Runs pipetool and returns the error code
* if it is either <code>0</code> or {@link #SKIP_RETURN_CODE}
* (Otherwise it will throw an exception).</p>
* @return
* @throws CoreException
* @throws ParameterResolverException
*/
public int run() throws CoreException, ParameterResolverException {
IPath pipeTool = MoSyncTool.getDefault().getBinary("pipe-tool");
ArrayList<String> args = new ArrayList<String>();
args.add(pipeTool.toOSString());
if (extra != null && Util.join(extra, "").trim().length() > 0) {
args.addAll(Arrays.asList(Util.replace(extra, resolver)));
}
if (appCode != null) {
args.add("-appcode=" + getAppCode());
}
if (collectStabs) {
args.add("-stabs=stabs.tab");
}
boolean programMode = BUILD_RESOURCES_MODE != mode;
if (programMode) {
if (argumentMap != null) {
addMemoryArg(args, argumentMap, MoSyncBuilder.MEMORY_HEAPSIZE_KB, DEFAULT_HEAP_SIZE_KB, "-heapsize");
addMemoryArg(args, argumentMap, MoSyncBuilder.MEMORY_STACKSIZE_KB, DEFAULT_STACK_SIZE_KB, "-stacksize");
addMemoryArg(args, argumentMap, MoSyncBuilder.MEMORY_DATASIZE_KB, DEFAULT_DATA_SIZE_KB, "-datasize");
}
if (sld) {
args.add("-sld=sld.tab");
}
if (libraryPaths != null) {
args.addAll(Arrays.asList(assembleLibraryPathArgs(libraryPaths)));
}
}
if (dce) {
args.add("-elim");
}
if (noVerify) {
args.add("-no-verify");
}
// Split up multiple arguments
for ( String a : mode.split( " " ) )
args.add( a );
if (BUILD_RESOURCES_MODE == mode) {
IPath depsFile = getResourcesDependencyFile(project, variant);
depsFile.toFile().getParentFile().mkdirs();
// Pipetool only accepts -depend files in exeuction dir
args.add("-depend=" + depsFile.toOSString());
}
args.add(outputFile.toOSString());
args.addAll(Arrays.asList(inputFiles));
if (programMode)
{
for (int i = 0; libraries != null && i < libraries.length; i++) {
args.add(Util.replace(libraries[i].toOSString(), resolver));
}
}
addMessage(Util.join(args.toArray(new String[0]), " "));
try {
assertArgLength(args);
boolean ensureOutputFileParentExists = outputFile.toFile().getParentFile().mkdirs();
if (ensureOutputFileParentExists && CoreMoSyncPlugin.getDefault().isDebugging()) {
CoreMoSyncPlugin.trace("Created directory {0}", outputFile.toFile().getParentFile());
}
getExecDir().mkdirs();
// Note where it's executed - this is where the sld will end up.
Process process = DebugPlugin.exec(args.toArray(new String[0]), getExecDir());
String cmdLine = Util.join(Util.ensureQuoted(args.toArray()), " ");
if (CoreMoSyncPlugin.getDefault().isDebugging()) {
CoreMoSyncPlugin.trace(cmdLine);
}
console.attachProcess(process, linehandler);
int result = process.waitFor();
if (result != 0 && result != SKIP_RETURN_CODE) {
throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID,
MessageFormat.format("Pipe tool failed. (See console for more information).\n Command line: {0}", cmdLine)));
}
return result;
} catch (CoreException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, e.getMessage(), e));
}
}
private void addMemoryArg(ArrayList<String> args, IPropertyOwner argumentMap,
String key, int defaultSize, String prefix) {
Integer size = PropertyUtil.getInteger(argumentMap, key);
if (size != null) {
int sizeInBytes = 1024 * size;
args.add(prefix + "=" + sizeInBytes);
}
}
/**
* Returns the app code of this pipe tool, corresponding
* to the -appcode switch
* @return
*/
public String getAppCode() {
return appCode;
}
/**
* Generates a random 4-character app code
* @return
*/
public static String generateAppCode() {
Random rnd = new Random(System.currentTimeMillis());
char[] CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
char[] result = new char[4];
for (int i = 0; i < result.length; i++) {
result[i] = CHARS[rnd.nextInt(CHARS.length)];
}
return new String(result);
}
/**
* Sets the app code of this pipe tool
* @param appCode
*/
public void setAppCode(String appCode) {
this.appCode = appCode;
}
private void assertArgLength(ArrayList<String> args) throws IOException {
/*for (String arg : args) {
if (arg.length() > MAX_PIPE_TOOL_ARG_LENGTH) {
throw new IOException(MessageFormat.format("Argument/file name too long: {0} (max length {1} characters, was {2} characters)", arg, MAX_PIPE_TOOL_ARG_LENGTH, arg.length()));
}
}*/
}
public static IPath getResourcesDependencyFile(IProject project, IBuildVariant variant) {
return MoSyncBuilder.getOutputPath(project, variant).append(RESOURCE_DEPENDENCY_FILE_NAME);
}
private String[] assembleLibraryPathArgs(IPath[] libraryPaths) throws ParameterResolverException {
String[] result = new String[libraryPaths.length];
for (int i = 0; i < result.length; i++) {
result[i] = Util.ensureQuoted("-s" + Util.replace(libraryPaths[i].toOSString(), resolver));
}
return result;
}
private void addMessage(String message) {
if (console != null) {
console.addMessage(message);
}
}
public void setMode(String mode) {
this.mode = mode;
}
public void setDeadCodeElimination(boolean dce) {
this.dce = dce;
}
public File getExecDir() {
//return getExecDir(project);
return outputFile.toFile().getParentFile();
}
public void setNoVerify(boolean noVerify) {
this.noVerify = noVerify;
}
public void setExtraSwitches(String[] extra) {
this.extra = extra;
}
public void setProject(IProject project) {
this.project = project;
}
public void setLineHandler(ILineHandler linehandler) {
this.linehandler = linehandler;
}
public void setCollectStabs(boolean collectStabs) {
this.collectStabs = collectStabs;
}
public void setParameterResolver(ParameterResolver resolver) {
this.resolver = resolver;
}
}