/* 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.builder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import com.mobilesorcery.sdk.core.CoreMoSyncPlugin;
import com.mobilesorcery.sdk.core.IBuildResult;
import com.mobilesorcery.sdk.core.MoSyncBuilder;
import com.mobilesorcery.sdk.core.MoSyncProject;
import com.mobilesorcery.sdk.core.MoSyncTool;
import com.mobilesorcery.sdk.core.ParameterResolverException;
import com.mobilesorcery.sdk.core.Util;
import com.mobilesorcery.sdk.core.LineReader.ILineHandler;
import com.mobilesorcery.sdk.internal.dependencies.CompoundDependencyProvider;
import com.mobilesorcery.sdk.internal.dependencies.DependencyManager;
import com.mobilesorcery.sdk.internal.dependencies.GCCDependencyProvider;
import com.mobilesorcery.sdk.internal.dependencies.IDependencyProvider;
import com.mobilesorcery.sdk.internal.dependencies.ProjectResourceDependencyProvider;
import com.mobilesorcery.sdk.internal.dependencies.ResourceFileDependencyProvider;
// TODO: The main responsibility of this class is no longer to
// visit projects - split into 2 classes!
// TODO: Replace all IResource references in build classes with IPath. (Huge refactoring)
public class MoSyncBuilderVisitor extends IncrementalBuilderVisitor {
private static final String GCC_WALL_STR = "-Wall";
private static final String GCC_WEXTRA_STR = "-Wextra";
private static final String GCC_WERROR_STR = "-Werror";
/**
* The standard extensions for C/C++ files
*/
public static final String[] C_SOURCE_FILE_EXTS = new String[] { "cpp", "c++", "c" };
/**
* The standard extensions for C++ files (no C file extensions)
*/
public static final String[] CPP_SOURCE_FILE_EXTS = new String[] { "cpp", "c++" };
public static final String[] C_HEADER_FILE_EXTS = new String[] { "hpp", "h++", "h" };
public static final String[] CPP_HEADER_FILE_EXTS = new String[] { "hpp", "h++" };
public static final String[] RESOURCE_FILE_EXTS = new String[] { "lst", "lstx" };
public MoSyncBuilderVisitor() {
}
private ArrayList<String> objectFiles = new ArrayList<String>();
private int errors;
private int compileCount = 0;
private IPath outputPath;
private String extraSwitches;
private IBuildResult buildResult;
private ILineHandler linehandler;
private int gccWarnings;
private boolean generateDependencies = true;
private CompoundDependencyProvider<IResource> dependencyProvider;
@Override
public boolean visit(IResource resource) throws CoreException {
boolean shouldVisitChildren = super.visit(resource);
if (isBuildable(resource)) {
IFile cFile = getCFile(resource, false);
if (cFile != null && outputPath != null) {
objectFiles.add(mapFileToOutput(cFile).toOSString());
}
}
return shouldVisitChildren;
}
/**
* Performs an incremental compile; either setChangedOrAddedResources
* and setDeletedResources must have been called, or a project must
* have been visited by this visitor (visit method)
* @param monitor
* @throws CoreException
* @throws ParameterResolverException
*/
public void incrementalCompile(IProgressMonitor monitor, DependencyManager<IResource> dependencies, DependencyManager.Delta<IResource> delta) throws CoreException, ParameterResolverException {
Set<IResource> recompileThese = computeResourcesToRebuild(dependencies);
IResource[] deletedResources = this.deletedResources.toArray(new IResource[0]);
for (int i = 0; i < deletedResources.length; i++) {
if (monitor.isCanceled()) {
return;
}
if (shouldBuild(deletedResources[i])) {
deleteBuildResult(deletedResources[i], dependencies);
}
}
for (IResource recompileThis : recompileThese) {
if (monitor.isCanceled()) {
return;
}
if (shouldBuild(recompileThis)) {
compile(recompileThis, delta);
}
}
}
private boolean shouldBuild(IResource recompileThis) {
return project.equals(recompileThis.getProject());
}
private void deleteBuildResult(IResource resource, DependencyManager<IResource> dependencies) {
IFile cFile = getCFile(resource, false);
if (cFile != null) {
IPath output = mapFileToOutput(cFile);
console.addMessage(MessageFormat.format("Deleting {0}", output.toOSString()));
output.toFile().delete();
}
if (dependencies != null) {
dependencies.clearDependencies(resource);
}
}
/**
* Returns a cast resource if the resource is a c source file,
* otherwise null. If the resource is a derived resource
* (such as for example generated C files), {@code null} is
* returned
* @param res
* @param if true, also header files are included in the filtering
* @return
*/
public static IFile getCFile(IResource resource, boolean header) {
if (hasExtension(resource, C_SOURCE_FILE_EXTS)) {
return (IFile) resource;
}
if (header && hasExtension(resource, C_HEADER_FILE_EXTS)) {
return (IFile) resource;
}
return null;
}
@Override
public boolean doesAffectBuild(IResource resource) {
return (MoSyncBuilderVisitor.hasExtension(resource, MoSyncBuilderVisitor.C_SOURCE_FILE_EXTS) ||
MoSyncBuilderVisitor.hasExtension(resource, MoSyncBuilderVisitor.C_HEADER_FILE_EXTS) ||
MoSyncBuilderVisitor.hasExtension(resource, MoSyncBuilderVisitor.RESOURCE_FILE_EXTS)) && super.doesAffectBuild(resource);
}
/**
* Returns true if resource is a file and if its extension
* is one of <code>extensions</code>.
* @param resource
* @param extensions
* @return
*/
public static boolean hasExtension(IResource resource, String... extensions) {
if (resource.getType() == IResource.FILE) {
IFile file = (IFile) resource;
String ext = file.getFileExtension();
for (int i = 0; i < extensions.length; i++) {
if (extensions[i].equalsIgnoreCase(ext)) {
return true;
}
}
}
return false;
}
public void compile(IResource resource, DependencyManager.Delta<IResource> dependenciesDelta) throws CoreException, ParameterResolverException {
if (!CoreMoSyncPlugin.isHeadless()) {
MoSyncBuilder.clearCMarkers(resource);
//clearCMarkers(resource.getProject());
}
IFile cFile = getCFile(resource, false);
if (cFile != null) {
// Assume unique filenames.
IPath output = mapFileToOutput(cFile);
IPath xgcc = MoSyncTool.getDefault().getBinary("xgcc");
MoSyncProject project = MoSyncProject.create(resource.getProject());
List<IPath> includePaths = new ArrayList<IPath>(Arrays.asList(MoSyncBuilder.getBaseIncludePaths(project, getVariant())));
// TODO: Too much 'secret sauce' here; add special dialogs for this instead,
// like JDT/CDT, to allow user to control this better. Like %output%?
includePaths.add(outputPath);
String[] includeStr = assembleIncludeString(includePaths.toArray(new IPath[0]));
ArrayList<String> args = new ArrayList<String>();
args.add(xgcc.toOSString());
args.add("-o");
args.add(output.toOSString());
args.add("-S");
args.add("-g");
if (generateDependencies) {
args.add("-MMD");
args.add("-MF");
args.add(mapToDependencyFile(output.toOSString()));
}
addGccWarnings(args);
args.add("-DMAPIP");
String[] extra = extraSwitches == null ? new String[0] : Util.parseCommandLine(resolve(extraSwitches));
args.addAll(Arrays.asList(extra));
args.add(cFile.getLocation().toOSString());
args.addAll(Arrays.asList(includeStr));
// Create output if it does not exist
output.toFile().getParentFile().mkdirs();
String argsAsArray[] = new String[args.size()];
args.toArray(argsAsArray);
// Display invocation in console
String cmdLine = Util.join(argsAsArray, " ");
console.addMessage(cmdLine);
try {
// Java automatically escapes the arguments when we call exec with an array instead of a string
Process process = Runtime.getRuntime().exec(argsAsArray, null, resource.getProject().getLocation().toFile());
console.attachProcess(process, linehandler);
int result = process.waitFor();
if (result != 0) {
errors++;
if (buildResult != null) {
buildResult.addError("Failed to compile " + cFile.getLocation());
}
}
compileCount ++;
} catch (Exception e) {
throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, e.getMessage(), e));
}
}
if (dependenciesDelta != null) {
dependenciesDelta.addDependencies(resource, getDependencyProvider());
}
}
public static String mapToDependencyFile(String filename) {
return filename + ".deps";
}
public void setGenerateDependencies(boolean generateDependencies) {
this.generateDependencies = generateDependencies;
}
private void addGccWarnings(List<String> args) {
if ((gccWarnings & MoSyncBuilder.GCC_WALL) != 0) {
args.add(GCC_WALL_STR);
}
if ((gccWarnings & MoSyncBuilder.GCC_WEXTRA) != 0) {
args.add(GCC_WEXTRA_STR);
}
if ((gccWarnings & MoSyncBuilder.GCC_WERROR) != 0) {
args.add(GCC_WERROR_STR);
}
}
public static String[] assembleIncludeString(IPath[] includePaths) {
String[] strs = new String[includePaths.length];
for (int i = 0; i < strs.length; i++) {
strs[i] = assembleIncludeString(includePaths[i]);
}
return strs;
}
private static String assembleIncludeString(IPath includePath) {
// Remove trailing separator, otherwise the \ will be considered an escape char.
return "-I" + includePath.removeTrailingSeparator().toOSString();
}
public IPath mapFileToOutput(IResource file) {
return mapFileToOutput(file, outputPath);
}
public static IPath mapFileToOutput(IResource file, IPath outputPath) {
String name = file.getName();
String newName = Util.replaceExtension(name, "s");
return outputPath.append(newName);
}
public String[] getObjectFilesForProject(IProject project) throws CoreException {
objectFiles = new ArrayList<String>();
project.accept(this);
return objectFiles.toArray(new String[0]);
}
public int getErrorCount() {
return errors;
}
public int getCompileCount() {
return compileCount;
}
public void setOutputPath(IPath outputPath) {
this.outputPath = outputPath;
}
public void setExtraCompilerSwitches(String extraSwitches) {
this.extraSwitches = extraSwitches;
}
public void setLineHandler(ILineHandler linehandler) {
this.linehandler = linehandler;
}
public void setBuildResult(IBuildResult buildResult) {
this.buildResult = buildResult;
}
public void setGCCWarnings(int gccWarnings) {
this.gccWarnings = gccWarnings;
}
protected String getName() {
return "C Compiler";
}
}