| 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(); |
| } |
| } |
| } |