blob: aadd19e8233e87b1eba0eb6dce9db8d441f61edb [file] [log] [blame]
package org.eclipse.cdt.managedbuilder.internal.core;
/**********************************************************************
* Copyright (c) 2002,2003 Rational Software Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM Rational Software - Initial API and implementation
* **********************************************************************/
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.search.ICSearchConstants;
import org.eclipse.cdt.core.search.ICSearchScope;
import org.eclipse.cdt.core.search.SearchEngine;
import org.eclipse.cdt.internal.core.model.Util;
import org.eclipse.cdt.internal.core.search.PathCollector;
import org.eclipse.cdt.internal.core.search.PatternSearchJob;
import org.eclipse.cdt.internal.core.search.indexing.IndexManager;
import org.eclipse.cdt.internal.core.search.matching.CSearchPattern;
import org.eclipse.cdt.internal.core.sourcedependency.DependencyQueryJob;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
public class MakefileGenerator {
// String constants for messages
private static final String MESSAGE = "ManagedMakeBuilder.message"; //$NON-NLS-1$
private static final String BUILD_ERROR = MESSAGE + ".error"; //$NON-NLS-1$
private static final String COMMENT = "ManagedMakeBuilder.comment"; //$NON-NLS-1$
private static final String MOD_LIST = COMMENT + ".module.list"; //$NON-NLS-1$
private static final String SRC_LISTS = COMMENT + ".source.list"; //$NON-NLS-1$
private static final String MOD_RULES = COMMENT + ".build.rule"; //$NON-NLS-1$
private static final String MOD_INCL = COMMENT + ".module.make.includes"; //$NON-NLS-1$
private static final String DEP_INCL = COMMENT + ".module.dep.includes"; //$NON-NLS-1$
private static final String AUTO_DEP = COMMENT + ".autodeps"; //$NON-NLS-1$
// String constants for makefile contents
protected static final String COLON = ":";
protected static final String DEPFILE_NAME = "subdir.dep"; //$NON-NLS-1$
protected static final String DOT = ".";
protected static final String MAKEFILE_NAME = "makefile"; //$NON-NLS-1$
protected static final String MODFILE_NAME = "subdir.mk"; //$NON-NLS-1$
protected static final String LINEBREAK = "\\";
protected static final String NEWLINE = System.getProperty("line.separator");
protected static final String LOGICAL_AND = "&&";
protected static final String SEPARATOR = "/";
protected static final String TAB = "\t";
protected static final String WHITESPACE = " ";
protected static final String WILDCARD = "%";
// Local variables needed by generator
protected IManagedBuildInfo info;
protected List modifiedList;
protected IProgressMonitor monitor;
protected List subdirList;
protected IProject project;
protected List ruleList;
protected IPath topBuildDir;
private String target;
private String extension;
/**
* This class is used to recursively walk the project and determine which
* modules contribute buildable source files.
*/
protected class ResourceProxyVisitor implements IResourceProxyVisitor {
private MakefileGenerator generator;
private IManagedBuildInfo info;
/**
* Constructs a new resource proxy visitor to quickly visit project
* resources.
*/
public ResourceProxyVisitor(MakefileGenerator generator, IManagedBuildInfo info) {
this.generator = generator;
this.info = info;
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.IResourceProxyVisitor#visit(org.eclipse.core.resources.IResourceProxy)
*/
public boolean visit(IResourceProxy proxy) throws CoreException {
// No point in proceeding, is there
if (generator == null) {
return false;
}
// Is this a resource we should even consider
if (proxy.getType() == IResource.FILE) {
// Check extension to see if build model should build this file
IResource resource = proxy.requestResource();
String ext = resource.getFileExtension();
if (info.buildsFileType(ext)) {
if (!generator.isGeneratedResource(resource)) {
generator.appendBuildSubdirectory(resource);
}
}
return false;
}
// Recurse into subdirectories
return true;
}
}
public class ResourceDeltaVisitor implements IResourceDeltaVisitor {
private MakefileGenerator generator;
private IManagedBuildInfo info;
/**
*
*/
public ResourceDeltaVisitor(MakefileGenerator generator, IManagedBuildInfo info) {
this.generator = generator;
this.info = info;
}
/* (non-javadoc)
* Answers a list of resource names in the workspace that depend on the resource
* specified in the argument.
*
* @param resource the root of the dependency tree
* @return IResource[]
*/
private IResource[] findDependencies(IResource resource) {
PathCollector pathCollector = new PathCollector();
ICSearchScope scope = SearchEngine.createWorkspaceScope();
CSearchPattern pattern = CSearchPattern.createPattern(resource.getLocation().toOSString(), ICSearchConstants.INCLUDE, ICSearchConstants.REFERENCES, ICSearchConstants.EXACT_MATCH, true);
IndexManager indexManager = CCorePlugin.getDefault().getCoreModel().getIndexManager();
indexManager.performConcurrentJob(
new PatternSearchJob(
(CSearchPattern) pattern,
scope,
pathCollector,
indexManager),
ICSearchConstants.WAIT_UNTIL_READY_TO_SEARCH,
null, null);
// We will get back an array of resource names relative to the workspace
String[] deps = pathCollector.getPaths();
// Convert them to something useful
List depList = new ArrayList();
IResource res = null;
IWorkspaceRoot root = null;
if (generator.project != null) {
root = generator.project.getWorkspace().getRoot();
}
for (int index = 0; index < deps.length; ++index) {
res = root.findMember(deps[index]);
if (res != null) {
depList.add(res);
}
}
return (IResource[]) depList.toArray(new IResource[depList.size()]);
}
private void handleHeaderDependency(IResource resource, boolean moved) {
// If this is a move and the resource is external to the project, touch that resource
if (resource.getProject().equals(generator.project)) {
generator.appendModifiedSubdirectory(resource);
} else {
if (moved) {
try {
resource.touch(new NullProgressMonitor());
} catch (CoreException e) {
}
}
}
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
*/
public boolean visit(IResourceDelta delta) throws CoreException {
// Should the visitor keep iterating in current directory
boolean keepLooking = false;
IResource resource = delta.getResource();
// What kind of resource change has occurred
if (resource.getType() == IResource.FILE) {
String ext = resource.getFileExtension();
boolean moved = false;
switch (delta.getKind()) {
case IResourceDelta.ADDED:
moved = delta.getFlags() == IResourceDelta.MOVED_TO;
if (!generator.isGeneratedResource(resource)) {
// This is a source file so just add its container
if (info.buildsFileType(ext)) {
generator.appendModifiedSubdirectory(resource);
} else if (info.isHeaderFile(ext)) {
// Add the container of the resource and any resources that depend on it
generator.appendModifiedSubdirectory(resource);
IResource[] deps = findDependencies(resource);
for (int i = 0; i < deps.length; ++i){
handleHeaderDependency(deps[i], moved);
}
}
}
break;
case IResourceDelta.REMOVED:
moved = delta.getFlags() == IResourceDelta.MOVED_FROM;
if (!generator.isGeneratedResource(resource)) {
// This is a source file so just add its container
if (info.buildsFileType(ext)) {
generator.appendModifiedSubdirectory(resource);
} else if (info.isHeaderFile(ext)) {
// Add the container of the resource and any resources that depend on it
generator.appendModifiedSubdirectory(resource);
IResource[] deps = findDependencies(resource);
for (int i = 0; i < deps.length; ++i){
handleHeaderDependency(deps[i], moved);
}
}
}
break;
case IResourceDelta.CHANGED:
if (!generator.isGeneratedResource(resource)) {
if (info.buildsFileType(ext)) {
keepLooking = true;
} else if (info.isHeaderFile(ext)) {
// Add the container of the resource and any resources that depend on it
generator.appendModifiedSubdirectory(resource);
IResource[] deps= findDependencies(resource);
for (int i = 0; i < deps.length; ++i){
handleHeaderDependency(deps[i], moved);
}
// That does it for this directory, so don't bother to keep looking
}
}
break;
default:
keepLooking = true;
break;
}
} if (resource.getType() == IResource.PROJECT) {
// If there is a zero-length delta, something the project depends on has changed so just call make
IResourceDelta[] children = delta.getAffectedChildren();
if (children != null && children.length > 0) {
keepLooking = true;
}
} else {
// If the resource is part of the generated directory structure don't recurse
if (!generator.isGeneratedResource(resource)) {
keepLooking = true;
}
}
return keepLooking;
}
}
/**
* @param project
* @param info
* @param monitor
*/
public MakefileGenerator(IProject project, IManagedBuildInfo info, IProgressMonitor monitor) {
super();
// Save the project so we can get path and member information
this.project = project;
// Save the monitor reference for reporting back to the user
this.monitor = monitor;
// Get the build info for the project
this.info = info;
// Get the name of the build target
target = info.getBuildArtifactName();
// Get its extension
extension = (new Path(target)).getFileExtension();
if (extension == null) {
extension = new String();
}
}
/* (non-javadoc)
* Calculates dependencies for all the source files in the argument. A source
* file can depend on any number of header files, so the dependencies have to
* be added to its dependency list.
*
* @param module
* @return
*/
protected StringBuffer addSourceDependencies(IContainer module) throws CoreException {
// Calculate the new directory relative to the build output
IPath moduleRelativePath = module.getProjectRelativePath();
String relativePath = moduleRelativePath.toString();
relativePath += relativePath.length() == 0 ? "" : SEPARATOR;
// Create the buffer to hold the output for the module and a dep calculator
StringBuffer buffer = new StringBuffer();
buffer.append(ManagedBuilderCorePlugin.getResourceString(AUTO_DEP) + NEWLINE);
IndexManager indexManager = CCorePlugin.getDefault().getCoreModel().getIndexManager();
/*
* Visit each resource in the folder that we have a rule to build.
* The dependency output for each resource will be in the format
* <relativePath>/<resourceName>.<outputExtension> : <dep1> ... <depn>
* with long lines broken.
*/
IResource[] resources = module.members();
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource.getType() == IResource.FILE) {
String inputExt = resource.getFileExtension();
if (info.buildsFileType(inputExt)) {
// Get the filename without an extension
String fileName = resource.getFullPath().removeFileExtension().lastSegment();
if (fileName == null) continue;
String outputExt = info.getOutputExtension(inputExt);
if (outputExt != null) {
fileName += DOT + outputExt;
}
// ASk the dep generator to find all the deps for this resource
ArrayList dependencies = new ArrayList();
try {
indexManager.performConcurrentJob(new DependencyQueryJob(project, (IFile)resource, indexManager, dependencies), ICSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, null, null);
} catch (Exception e) {
continue;
}
if (dependencies.size() == 0) continue;
buffer.append(relativePath + fileName + COLON + WHITESPACE);
Iterator iter = dependencies.listIterator();
while (iter.hasNext()) {
buffer.append(LINEBREAK + NEWLINE);
String path = (String)iter.next();
buffer.append(path + WHITESPACE);
}
buffer.append(NEWLINE);
}
}
}
return buffer;
}
/* (non-javadoc)
* @param buffer
* @param info
*/
protected StringBuffer addMacros() {
StringBuffer buffer = new StringBuffer();
// Add the ROOT macro
buffer.append("ROOT := .." + NEWLINE);
// Get the clean command from the build model
buffer.append("RM := ");
buffer.append(info.getCleanCommand() + NEWLINE + NEWLINE);
buffer.append(ManagedBuilderCorePlugin.getResourceString(SRC_LISTS) + NEWLINE);
buffer.append("C_SRCS := " + NEWLINE);
buffer.append("CC_SRCS := " + NEWLINE);
buffer.append("CXX_SRCS := " + NEWLINE);
buffer.append("CAPC_SRCS := " + NEWLINE);
buffer.append("CPP_SRCS := " + NEWLINE + NEWLINE);
// Add the libraries this project depends on
buffer.append("LIBS := ");
String[] libs = info.getLibsForTarget(extension);
for (int i = 0; i < libs.length; i++) {
String string = libs[i];
buffer.append(LINEBREAK + NEWLINE + string);
}
buffer.append(NEWLINE + NEWLINE);
// Add the extra user-specified objects
buffer.append("USER_OBJS := ");
String[] userObjs = info.getUserObjectsForTarget(extension);
for (int j = 0; j < userObjs.length; j++) {
String string = userObjs[j];
buffer.append(LINEBREAK + NEWLINE + string);
}
buffer.append(NEWLINE + NEWLINE);
buffer.append("OBJS = $(C_SRCS:$(ROOT)/%.c=%.o) $(CC_SRCS:$(ROOT)/%.cc=%.o) $(CXX_SRCS:$(ROOT)/%.cxx=%.o) $(CAPC_SRCS:$(ROOT)/%.C=%.o) $(CPP_SRCS:$(ROOT)/%.cpp=%.o)" + NEWLINE);
return (buffer.append(NEWLINE));
}
/* (non-javadoc)
* @return
*/
protected StringBuffer addSubdirectories() {
StringBuffer buffer = new StringBuffer();
// Add the comment
buffer.append(ManagedBuilderCorePlugin.getResourceString(MOD_LIST) + NEWLINE);
buffer.append("SUBDIRS := " + LINEBREAK + NEWLINE);
// Get all the module names
ListIterator iter = getSubdirList().listIterator();
while (iter.hasNext()) {
IContainer container = (IContainer) iter.next();
// Check the special case where the module is the project root
if (container.getFullPath() == project.getFullPath()) {
buffer.append("." + WHITESPACE + LINEBREAK + NEWLINE);
} else {
IPath path = container.getProjectRelativePath();
buffer.append(path.toString() + WHITESPACE + LINEBREAK + NEWLINE);
}
}
// Now add the makefile instruction to include all the subdirectory makefile fragments
buffer.append(NEWLINE);
buffer.append(ManagedBuilderCorePlugin.getResourceString(MOD_INCL) + NEWLINE);
buffer.append("-include ${patsubst %, %/subdir.mk, $(SUBDIRS)}" + NEWLINE);
buffer.append(NEWLINE + NEWLINE);
return buffer;
}
/* (non-javadoc)
* Answers a <code>StringBuffer</code> containing all of the sources contributed by
* a container to the build.
*
* @param module
* @return StringBuffer
*/
protected StringBuffer addSources(IContainer module) throws CoreException {
// Calculate the new directory relative to the build output
IPath moduleRelativePath = module.getProjectRelativePath();
String relativePath = moduleRelativePath.toString();
relativePath += relativePath.length() == 0 ? "" : SEPARATOR;
// String buffers
StringBuffer buffer = new StringBuffer();
StringBuffer cBuffer = new StringBuffer("C_SRCS += " + LINEBREAK + NEWLINE);
cBuffer.append("${addprefix $(ROOT)/" + relativePath + "," + LINEBREAK + NEWLINE);
StringBuffer ccBuffer = new StringBuffer("CC_SRCS += \\" + NEWLINE);
ccBuffer.append("${addprefix $(ROOT)/" + relativePath + "," + LINEBREAK + NEWLINE);
StringBuffer cxxBuffer = new StringBuffer("CXX_SRCS += \\" + NEWLINE);
cxxBuffer.append("${addprefix $(ROOT)/" + relativePath + "," + LINEBREAK + NEWLINE);
StringBuffer capcBuffer = new StringBuffer("CAPC_SRCS += \\" + NEWLINE);
capcBuffer.append("${addprefix $(ROOT)/" + relativePath + "," + LINEBREAK + NEWLINE);
StringBuffer cppBuffer = new StringBuffer("CPP_SRCS += \\" + NEWLINE);
cppBuffer.append("${addprefix $(ROOT)/" + relativePath + "," + LINEBREAK + NEWLINE);
StringBuffer ruleBuffer = new StringBuffer(ManagedBuilderCorePlugin.getResourceString(MOD_RULES) + NEWLINE);
// Put the comment in
buffer.append(ManagedBuilderCorePlugin.getResourceString(SRC_LISTS) + NEWLINE);
// Visit the resources in this folder
IResource[] resources = module.members();
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource.getType() == IResource.FILE) {
String ext = resource.getFileExtension();
if (info.buildsFileType(ext)) {
if (new String("c").equals(ext)) {
cBuffer.append(resource.getName() + WHITESPACE + LINEBREAK + NEWLINE);
} else if (new String("cc").equalsIgnoreCase(ext)) {
ccBuffer.append(resource.getName() + WHITESPACE + LINEBREAK + NEWLINE);
} else if (new String("cxx").equalsIgnoreCase(ext)) {
cxxBuffer.append(resource.getName() + WHITESPACE + LINEBREAK + NEWLINE);
} else if (new String("C").equals(ext)) {
capcBuffer.append(resource.getName() + WHITESPACE + LINEBREAK + NEWLINE);
} else {
cppBuffer.append(resource.getName() + WHITESPACE + LINEBREAK + NEWLINE);
}
// Try to add the rule for the file
addRule(relativePath, ruleBuffer, resource);
}
}
}
// Finish the commands in the buffers
buffer.append(cBuffer.append("}" + NEWLINE + NEWLINE));
buffer.append(ccBuffer.append("}" + NEWLINE + NEWLINE));
buffer.append(cxxBuffer.append("}" + NEWLINE + NEWLINE));
buffer.append(capcBuffer.append("}" + NEWLINE + NEWLINE));
buffer.append(cppBuffer.append("}" + NEWLINE + NEWLINE));
return buffer.append(ruleBuffer + NEWLINE);
}
/* (non-javadoc)
* Answers a <code>StrinBuffer</code> containing all of the required targets to
* properly build the project.
*
* @return StringBuffer
*/
protected StringBuffer addTargets(boolean rebuild) {
StringBuffer buffer = new StringBuffer();
// Assemble the information needed to generate the targets
String cmd = info.getToolForTarget(extension);
String flags = info.getFlagsForTarget(extension);
String outflag = info.getOutputFlag(extension);
String outputPrefix = info.getOutputPrefix(extension);
String targets = rebuild ? "clean all" : "all";
// Get all the projects the build target depends on
IProject[] deps = null;
try {
deps = project.getReferencedProjects();
} catch (CoreException e) {
// There are 2 exceptions; the project does not exist or it is not open
// and neither conditions apply if we are building for it ....
}
// Write out the all target first in case someone just runs make
// all: targ_<target_name> [deps]
String defaultTarget = "all:";
if (deps.length > 0) {
defaultTarget += WHITESPACE + "deps";
}
buffer.append(defaultTarget + WHITESPACE + outputPrefix + target + NEWLINE);
buffer.append(NEWLINE);
/*
* The build target may depend on other projects in the workspace. These are
* captured in the deps target:
* deps:
* <cd <Proj_Dep_1/build_dir>; $(MAKE) [clean all | all]>
*/
List managedProjectOutputs = new ArrayList();
if (deps.length > 0) {
buffer.append("deps:" + NEWLINE);
if (deps != null) {
for (int i = 0; i < deps.length; i++) {
IProject dep = deps[i];
String buildDir = dep.getLocation().toString();
String depTargets = targets;
if (ManagedBuildManager.manages(dep)) {
// Add the current configuration to the makefile path
IManagedBuildInfo depInfo = ManagedBuildManager.getBuildInfo(dep);
buildDir += SEPARATOR + depInfo.getConfigurationName();
// Extract the build artifact to add to the dependency list
String depTarget = depInfo.getBuildArtifactName();
String depExt = (new Path(depTarget)).getFileExtension();
String depPrefix = depInfo.getOutputPrefix(depExt);
if (depInfo.isDirty()) {
depTargets = "clean all";
}
managedProjectOutputs.add(buildDir + SEPARATOR + depPrefix + depTarget);
}
buffer.append(TAB + "-cd" + WHITESPACE + buildDir + WHITESPACE + LOGICAL_AND + WHITESPACE + "$(MAKE) " + depTargets + NEWLINE);
}
}
buffer.append(NEWLINE);
}
/*
* Write out the target rule as:
* targ_<prefix><target>.<extension>: $(OBJS) [<dep_proj_1_output> ... <dep_proj_n_output>]
* $(BUILD_TOOL) $(FLAGS) $(OUTPUT_FLAG) $@ $(OBJS) $(USER_OBJS) $(LIB_DEPS)
*/
buffer.append(outputPrefix + target + COLON + WHITESPACE + "$(OBJS)");
Iterator iter = managedProjectOutputs.listIterator();
while (iter.hasNext()) {
buffer.append(WHITESPACE + (String)iter.next());
}
buffer.append(NEWLINE);
buffer.append(TAB + cmd + WHITESPACE + flags + WHITESPACE + outflag + WHITESPACE + "$@" + WHITESPACE + "$(OBJS) $(USER_OBJS) $(LIBS)");
buffer.append(NEWLINE + NEWLINE);
// Always add a clean target
buffer.append("clean:" + NEWLINE);
buffer.append(TAB + "-$(RM)" + WHITESPACE + "$(OBJS)" + WHITESPACE + outputPrefix + target + NEWLINE + NEWLINE);
buffer.append(".PHONY: all clean deps" + NEWLINE + NEWLINE);
buffer.append(ManagedBuilderCorePlugin.getResourceString(DEP_INCL) + NEWLINE);
buffer.append("-include ${patsubst %, %/subdir.dep, $(SUBDIRS)}" + NEWLINE);
return buffer;
}
/* (non-javadoc)
*
* @param relativePath
* @param buffer
* @param resource
*/
protected void addRule(String relativePath, StringBuffer buffer, IResource resource) {
String rule = null;
String cmd = null;
String buildFlags = null;
String inputExtension = null;
String outputExtension = null;
String outflag = null;
String outputPrefix = null;
// Is there a special rule for this file
if (false) {
}
else {
// Get the extension of the resource
inputExtension = resource.getFileExtension();
// ASk the build model what it will produce from this
outputExtension = info.getOutputExtension(inputExtension);
/*
* Create the pattern rule in the format
* <relative_path>/%.o: $(ROOT)/<relative_path>/%.cpp
* $(CC) $(CFLAGS) $(OUTPUT_FLAG) $@ $<
*
* Note that CC CFLAGS and OUTPUT_FLAG all come from the build model
* and are resolved to a real command before writing to the module
* makefile, so a real command might look something like
* source1/%.o: $(ROOT)/source1/%.cpp
* g++ -g -O2 -c -I/cygdrive/c/eclipse/workspace/Project/headers -o $@ $<
*/
rule = relativePath + WILDCARD + DOT + outputExtension + COLON + WHITESPACE + "$(ROOT)" + SEPARATOR + relativePath + WILDCARD + DOT + inputExtension;
}
// Check if the rule is listed as something we already generated in the makefile
if (!getRuleList().contains(rule)) {
// Add it to the list
getRuleList().add(rule);
// Add the rule and command to the makefile
buffer.append(rule + NEWLINE);
cmd = info.getToolForSource(inputExtension);
buildFlags = info.getFlagsForSource(inputExtension);
outflag = info.getOutputFlag(outputExtension);
outputPrefix = info.getOutputPrefix(outputExtension);
buffer.append(TAB + cmd + WHITESPACE + buildFlags + WHITESPACE + outflag + WHITESPACE + outputPrefix + "$@" + WHITESPACE + "$<" + NEWLINE + NEWLINE);
}
}
/**
* Adds the container of the argument to the list of folders in the project that
* contribute source files to the build. The resource visitor has already established
* that the build model knows how to build the files. It has also checked that
* the resource is not generated as part of the build.
*
* @param resource
*/
public void appendBuildSubdirectory(IResource resource) {
IContainer container = resource.getParent();
if (!getSubdirList().contains(container)) {
getSubdirList().add(container);
}
}
/**
* Adds the container of the argument to a list of subdirectories that are part
* of an incremental rebuild of the project. The makefile fragments for these
* directories will be regenerated as a result of the build.
*
* @param resource
*/
public void appendModifiedSubdirectory(IResource resource) {
IContainer container = resource.getParent();
if (!getModifiedList().contains(container)) {
getModifiedList().add(container);
}
}
/**
* Check whether the build has been cancelled. Cancellation requests
* propagated to the caller by throwing <code>OperationCanceledException</code>.
*
* @see org.eclipse.core.runtime.OperationCanceledException#OperationCanceledException()
*/
public void checkCancel() {
if (monitor != null && monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
/**
* Clients call this method when an incremental rebuild is required. The argument
* contains a set of resource deltas that will be used to determine which
* subdirectories need a new makefile and dependency list (if any).
*
* @param delta
* @throws CoreException
*/
public void generateMakefiles(IResourceDelta delta) throws CoreException {
/*
* Let's do a sanity check right now.
*
* 1. This is an incremental build, so if the top-level directory is not
* there, then a rebuild is needed.
*/
IFolder folder = project.getFolder(info.getConfigurationName());
if (!folder.exists()) {
regenerateMakefiles();
return;
}
// Visit the resources in the delta and compile a list of subdirectories to regenerate
ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(this, info);
delta.accept(visitor);
// See if the user has cancelled the build
checkCancel();
// The top-level makefile needs this information
ResourceProxyVisitor resourceVisitor = new ResourceProxyVisitor(this, info);
project.accept(resourceVisitor, IResource.NONE);
checkCancel();
// Regenerate any fragments that are missing for the exisiting directories NOT modified
Iterator iter = getSubdirList().listIterator();
while (iter.hasNext()) {
IContainer subdirectory = (IContainer)iter.next();
if (!getModifiedList().contains(subdirectory)) {
// Make sure a fragment makefile and dependency file exist
IFile makeFragment = project.getFile(subdirectory.getFullPath().addTrailingSeparator().append(MODFILE_NAME));
IFile depFragment = project.getFile(subdirectory.getFullPath().addTrailingSeparator().append(DEPFILE_NAME));
if (!makeFragment.exists() || !depFragment.exists()) {
// If one or both are missing, then add it to the list to be generated
getModifiedList().add(subdirectory);
}
}
}
// Re-create the top-level makefile
topBuildDir = createDirectory(info.getConfigurationName());
IPath makefilePath = topBuildDir.addTrailingSeparator().append(MAKEFILE_NAME);
IFile makefileHandle = createFile(makefilePath);
populateTopMakefile(makefileHandle, false);
checkCancel();
// Regenerate any fragments for modified directories
iter = getModifiedList().listIterator();
while (iter.hasNext()) {
populateFragmentMakefile((IContainer) iter.next());
checkCancel();
}
}
/* (non-javadoc)
*
* @return List
*/
private List getModifiedList() {
if (modifiedList == null) {
modifiedList = new ArrayList();
}
return modifiedList;
}
/* (non-javadoc)
* Answers the list of known build rules. This keeps me from generating duplicate
* rules for known file extensions.
*
* @return List
*/
private List getRuleList() {
if (ruleList == null) {
ruleList = new ArrayList();
}
return ruleList;
}
/* (non-javadoc)
* Answers the list of subdirectories contributing source code to the build
*
* @return List
*/
private List getSubdirList() {
if (subdirList == null) {
subdirList = new ArrayList();
}
return subdirList;
}
/* (non-javadoc)
* @param string
* @return IPath
*/
private IPath createDirectory(String dirName) throws CoreException {
// Create or get the handle for the build directory
IFolder folder = project.getFolder(dirName);
if (!folder.exists()) {
// Make sure that parent folders exist
IPath parentPath = (new Path(dirName)).removeLastSegments(1);
// Assume that the parent exists if the path is empty
if (!parentPath.isEmpty()) {
IFolder parent = project.getFolder(parentPath);
if (!parent.exists()) {
createDirectory(parentPath.toString());
}
}
// Now make the requested folder
try {
folder.create(true, true, null);
}
catch (CoreException e) {
if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED)
folder.refreshLocal(IResource.DEPTH_ZERO, null);
else
throw e;
}
}
return folder.getFullPath();
}
/* (non-javadoc)
* @param makefilePath
* @param monitor
* @return IFile
*/
private IFile createFile(IPath makefilePath) throws CoreException {
// Create or get the handle for the makefile
IWorkspaceRoot root = CCorePlugin.getWorkspace().getRoot();
IFile newFile = root.getFileForLocation(makefilePath);
if (newFile == null) {
newFile = root.getFile(makefilePath);
}
// Create the file if it does not exist
ByteArrayInputStream contents = new ByteArrayInputStream(new byte[0]);
try {
newFile.create(contents, false, monitor);
}
catch (CoreException e) {
// If the file already existed locally, just refresh to get contents
if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED)
newFile.refreshLocal(IResource.DEPTH_ZERO, null);
else
throw e;
}
return newFile;
}
/**
* Answers the <code>IPath</code> of the top directory generated for the build
* output, or <code>null</code> if none has been generated.
*
* @return IPath
*/
public IPath getTopBuildDir() {
return topBuildDir;
}
/**
* Answers <code>true</code> if the argument is found in a generated container
* @param resource
* @return boolean
*/
public boolean isGeneratedResource(IResource resource) {
// Is this a generated directory ...
IPath path = resource.getProjectRelativePath();
String[] configNames = info.getConfigurationNames();
for (int i = 0; i < configNames.length; i++) {
String name = configNames[i];
IPath root = new Path(name);
// It is if it is a root of the resource pathname
if (root.isPrefixOf(path)) return true;
}
return false;
}
/* (non-javadoc)
* Create the entire contents of the makefile.
*
* @param fileHandle The file to place the contents in.
* @param rebuild FLag signalling that the user is doing a full rebuild
* @throws CoreException
*/
protected void populateTopMakefile(IFile fileHandle, boolean rebuild) throws CoreException {
StringBuffer buffer = new StringBuffer();
// Add the macro definitions
buffer.append(addMacros());
// Append the module list
buffer.append(addSubdirectories());
// Add targets
buffer.append(addTargets(rebuild));
// Save the file
Util.save(buffer, fileHandle);
}
/* (non-javadoc)
* @param module
* @throws CoreException
*/
protected void populateFragmentMakefile(IContainer module) throws CoreException {
// Calcualte the new directory relative to the build output
IPath moduleRelativePath = module.getProjectRelativePath();
IPath buildRoot = getTopBuildDir().removeFirstSegments(1);
if (buildRoot == null) {
return;
}
IPath moduleOutputPath = buildRoot.append(moduleRelativePath);
// Now create the directory
IPath moduleOutputDir = createDirectory(moduleOutputPath.toString());
// Create a module makefile
IFile modMakefile = createFile(moduleOutputDir.addTrailingSeparator().append(MODFILE_NAME));
StringBuffer makeBuf = new StringBuffer();
makeBuf.append(addSources(module));
// Create a module dep file
IFile modDepfile = createFile(moduleOutputDir.addTrailingSeparator().append(DEPFILE_NAME));
StringBuffer depBuf = new StringBuffer();
depBuf.append(addSourceDependencies(module));
// Save the files
Util.save(makeBuf, modMakefile);
Util.save(depBuf, modDepfile);
}
/**
* @throws CoreException
*/
public void regenerateMakefiles() throws CoreException {
// Visit the resources in the project
ResourceProxyVisitor visitor = new ResourceProxyVisitor(this, info);
project.accept(visitor, IResource.NONE);
// See if the user has cancelled the build
checkCancel();
// Populate the makefile if any source files have been found in the project
if (getSubdirList().isEmpty()) {
return;
}
// Create the top-level directory for the build output
topBuildDir = createDirectory(info.getConfigurationName());
// Create the top-level makefile
IPath makefilePath = topBuildDir.addTrailingSeparator().append(MAKEFILE_NAME);
IFile makefileHandle = createFile(makefilePath);
populateTopMakefile(makefileHandle, true);
checkCancel();
// Now populate the module makefiles
ListIterator iter = getSubdirList().listIterator();
while (iter.hasNext()) {
populateFragmentMakefile((IContainer)iter.next());
checkCancel();
}
}
}