From 2ea02e19824f84f23cea8bf44b5fb3c02cd6f969 Mon Sep 17 00:00:00 2001 From: Doug Schaefer Date: Fri, 25 Jul 2003 14:40:04 +0000 Subject: [PATCH] Patch for Sean Evoy: - Initial makefile generator for managed build. --- .../core/suite/AutomatedIntegrationSuite.java | 2 + .../core/build/managed/IManagedBuildInfo.java | 51 ++ .../build/managed/ManagedBuildManager.java | 80 ++- .../core/build/managed/ManagedBuildInfo.java | 113 +++- .../cdt/core/ManagedCProjectNature.java | 19 +- .../core/CCorePluginResources.properties | 14 +- .../core/GeneratedMakefileBuilder.java | 419 ++++++++----- .../cdt/internal/core/MakefileGenerator.java | 574 ++++++++++++++++++ 8 files changed, 1084 insertions(+), 188 deletions(-) create mode 100644 core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/MakefileGenerator.java diff --git a/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java b/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java index 13356e9c02a..8829b751be0 100644 --- a/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java +++ b/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java @@ -18,6 +18,7 @@ import junit.framework.TestSuite; import junit.textui.TestRunner; import org.eclipse.cdt.core.build.managed.tests.AllBuildTests; +import org.eclipse.cdt.core.build.managed.tests.StandardBuildTests; import org.eclipse.cdt.core.model.failedTests.CModelElementsFailedTests; import org.eclipse.cdt.core.model.tests.AllCoreTests; import org.eclipse.cdt.core.model.tests.BinaryTests; @@ -77,6 +78,7 @@ public class AutomatedIntegrationSuite extends TestSuite // Add all success tests suite.addTest(AllBuildTests.suite()); + suite.addTest(StandardBuildTests.suite()); suite.addTest(ParserTestSuite.suite()); suite.addTest(AllCoreTests.suite()); suite.addTest(BinaryTests.suite()); diff --git a/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/IManagedBuildInfo.java b/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/IManagedBuildInfo.java index 7684d41f982..baf3146f5c8 100644 --- a/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/IManagedBuildInfo.java +++ b/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/IManagedBuildInfo.java @@ -22,6 +22,15 @@ public interface IManagedBuildInfo { */ public void addTarget(ITarget target); + /** + * Answers true if the build system knows how to + * build a file with the extension passed in the argument. + * + * @param srcExt + * @return + */ + public boolean buildsFileType(String srcExt); + /** * Returns the name of the artifact to build for the receiver. * @@ -29,6 +38,21 @@ public interface IManagedBuildInfo { */ public String getBuildArtifactName(); + /** + * Answers the command needed to remove files on the build machine + * + * @return + */ + public String getCleanCommand(); + + /** + * Answers the name of the default configuration, for example Debug + * or Release. + * + * @return + */ + public String getConfigurationName(); + /** * Get the default configuration associated with the receiver * @@ -53,6 +77,16 @@ public interface IManagedBuildInfo { */ public String getOutputExtension(String resourceExtension); + /** + * Answers the flag to be passed to the build tool to produce a specific output + * or an empty String if there is no special flag. For example, the + * GCC tools use the -o flag to produce a named output, for example + * gcc -c foo.c -o foo.o + * + * @return + */ + public String getOutputFlag(); + /** * Get the target specified in the argument. * @@ -88,6 +122,21 @@ public interface IManagedBuildInfo { */ public String getFlagsForTarget(String extension); + /** + * Answers a string array containing the arguments to be passed to + * make. For example, if the user has selected a build that stops + * at the first error, the array would contain {"k"}. + * + * @return + */ + public String[] getMakeArguments(); + + /** + * Answers a String containing the make command invocation + * for the default target/configuration. + */ + public String getMakeCommand(); + /** * Returns a String containing the command-line invocation * for the tool associated with the source extension. @@ -120,5 +169,7 @@ public interface IManagedBuildInfo { * @param target */ public void setDefaultTarget(ITarget target); + + } diff --git a/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/ManagedBuildManager.java b/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/ManagedBuildManager.java index 54d2e7920e4..727b30681ca 100644 --- a/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/ManagedBuildManager.java +++ b/core/org.eclipse.cdt.core/build/org/eclipse/cdt/core/build/managed/ManagedBuildManager.java @@ -328,25 +328,6 @@ public class ManagedBuildManager implements IScannerInfoProvider { getExtensionTargetMap().put(target.getId(), target); } - private static void loadExtensions() { - if (extensionTargetsLoaded) - return; - extensionTargetsLoaded = true; - - IExtensionPoint extensionPoint = CCorePlugin.getDefault().getDescriptor().getExtensionPoint("ManagedBuildInfo"); - IExtension[] extensions = extensionPoint.getExtensions(); - for (int i = 0; i < extensions.length; ++i) { - IExtension extension = extensions[i]; - IConfigurationElement[] elements = extension.getConfigurationElements(); - for (int j = 0; j < elements.length; ++j) { - IConfigurationElement element = elements[j]; - if (element.getName().equals("target")) { - new Target(element); - } - } - } - } - private static ManagedBuildInfo loadBuildInfo(IProject project) { ManagedBuildInfo buildInfo = null; IFile file = project.getFile(FILE_NAME); @@ -369,6 +350,58 @@ public class ManagedBuildManager implements IScannerInfoProvider { return buildInfo; } + private static void loadExtensions() { + if (extensionTargetsLoaded) + return; + extensionTargetsLoaded = true; + + IExtensionPoint extensionPoint = CCorePlugin.getDefault().getDescriptor().getExtensionPoint("ManagedBuildInfo"); + IExtension[] extensions = extensionPoint.getExtensions(); + for (int i = 0; i < extensions.length; ++i) { + IExtension extension = extensions[i]; + IConfigurationElement[] elements = extension.getConfigurationElements(); + for (int j = 0; j < elements.length; ++j) { + IConfigurationElement element = elements[j]; + if (element.getName().equals("target")) { + new Target(element); + } + } + } + } + + /** + * @param project + * @return + */ + public static boolean manages(IResource resource) { + // The managed build manager manages build information for the + // resource IFF it it is a project and has a build file with the proper + // root element + IProject project = null; + if (resource instanceof IProject){ + project = (IProject)resource; + } else if (resource instanceof IFile) { + project = ((IFile)resource).getProject(); + } else { + return false; + } + IFile file = project.getFile(FILE_NAME); + if (file.exists()) { + try { + InputStream stream = file.getContents(); + DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = parser.parse(stream); + Node rootElement = document.getFirstChild(); + if (rootElement.getNodeName().equals(ROOT_ELEM_NAME)) { + return true; + } + } catch (Exception e) { + return false; + } + } + return false; + } + private static ManagedBuildInfo findBuildInfo(IResource resource, boolean create) { // Make sure the extension information is loaded first loadExtensions(); @@ -417,6 +450,11 @@ public class ManagedBuildManager implements IScannerInfoProvider { * Answers with an interface to the parse information that has been * associated with the resource specified in the argument. * + * NOTE: This method is not part of the registration interface. It has + * been made public as a short-term workaround for the clients of the + * scanner information until the redesign of the build information management + * occurs. + * * @param resource * @return */ @@ -450,6 +488,9 @@ public class ManagedBuildManager implements IScannerInfoProvider { } } + // TODO Remove all of the IScannerInfoProvider interface methods when + // the discovery mechanism is solidified + /* (non-Javadoc) * @see org.eclipse.cdt.core.parser.IScannerInfoProvider#managesResource(org.eclipse.core.resources.IResource) */ @@ -504,4 +545,5 @@ public class ManagedBuildManager implements IScannerInfoProvider { } } + } diff --git a/core/org.eclipse.cdt.core/build/org/eclipse/cdt/internal/core/build/managed/ManagedBuildInfo.java b/core/org.eclipse.cdt.core/build/org/eclipse/cdt/internal/core/build/managed/ManagedBuildInfo.java index 04502ea575e..3f6f451d67d 100644 --- a/core/org.eclipse.cdt.core/build/org/eclipse/cdt/internal/core/build/managed/ManagedBuildInfo.java +++ b/core/org.eclipse.cdt.core/build/org/eclipse/cdt/internal/core/build/managed/ManagedBuildInfo.java @@ -87,6 +87,22 @@ public class ManagedBuildInfo implements IManagedBuildInfo, IScannerInfo { targets.add(target); } + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IManagedBuildInfo#buildsFileType(java.lang.String) + */ + public boolean buildsFileType(String srcExt) { + // Check to see if there is a rule to build a file with this extension + IConfiguration config = getDefaultConfiguration(getDefaultTarget()); + ITool[] tools = config.getTools(); + for (int index = 0; index < tools.length; index++) { + ITool tool = tools[index]; + if (tool.buildsFileType(srcExt)) { + return true; + } + } + return false; + } + /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IResourceBuildInfo#getBuildArtifactName() */ @@ -96,6 +112,23 @@ public class ManagedBuildInfo implements IManagedBuildInfo, IScannerInfo { return name == null ? new String() : name; } + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IManagedBuildInfo#getCleanCommand() + */ + public String getCleanCommand() { + // TODO Get from the model + return new String("rm -rf"); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IManagedBuildInfo#getConfigurationName() + */ + public String getConfigurationName() { + // Return the human-readable name of the default configuration + IConfiguration config = getDefaultConfiguration(getDefaultTarget()); + return config == null ? new String() : config.getName(); + } + /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IResourceBuildInfo#getDefaultConfiguration() */ @@ -168,6 +201,51 @@ public class ManagedBuildInfo implements IManagedBuildInfo, IScannerInfo { return null; } + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IScannerInfo#getIncludePaths() + */ + public String[] getIncludePaths() { + // Return the include paths for the default configuration + ArrayList paths = new ArrayList(); + IConfiguration config = getDefaultConfiguration(getDefaultTarget()); + ITool[] tools = config.getTools(); + for (int i = 0; i < tools.length; i++) { + ITool tool = tools[i]; + IOption[] opts = tool.getOptions(); + for (int j = 0; j < opts.length; j++) { + IOption option = opts[j]; + if (option.getValueType() == IOption.INCLUDE_PATH) { + try { + paths.addAll(Arrays.asList(option.getIncludePaths())); + } catch (BuildException e) { + // we should never get here + continue; + } + } + } + } + paths.trimToSize(); + return (String[])paths.toArray(new String[paths.size()]); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IManagedBuildInfo#getMakeArguments() + */ + public String[] getMakeArguments() { + // TODO Stop hard-coding this + String[] args = {""}; + + return args; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IManagedBuildInfo#getMakeCommand() + */ + public String getMakeCommand() { + // TODO Don't hard-code this + return new String("make"); + } + /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IResourceBuildInfo#getOutputExtension(java.lang.String) */ @@ -185,6 +263,15 @@ public class ManagedBuildInfo implements IManagedBuildInfo, IScannerInfo { return null; } + /* (non-Javadoc) + * @see org.eclipse.cdt.core.build.managed.IManagedBuildInfo#getOutputFlag() + */ + public String getOutputFlag() { + // TODO Stop hard-coding this + String flag = new String("-o"); + return flag; + } + public IResource getOwner() { return owner; } @@ -319,31 +406,5 @@ public class ManagedBuildInfo implements IManagedBuildInfo, IScannerInfo { return symbols; } - /* (non-Javadoc) - * @see org.eclipse.cdt.core.build.managed.IScannerInfo#getIncludePaths() - */ - public String[] getIncludePaths() { - // Return the include paths for the default configuration - ArrayList paths = new ArrayList(); - IConfiguration config = getDefaultConfiguration(getDefaultTarget()); - ITool[] tools = config.getTools(); - for (int i = 0; i < tools.length; i++) { - ITool tool = tools[i]; - IOption[] opts = tool.getOptions(); - for (int j = 0; j < opts.length; j++) { - IOption option = opts[j]; - if (option.getValueType() == IOption.INCLUDE_PATH) { - try { - paths.addAll(Arrays.asList(option.getIncludePaths())); - } catch (BuildException e) { - // we should never get here - continue; - } - } - } - } - paths.trimToSize(); - return (String[])paths.toArray(new String[paths.size()]); - } } diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ManagedCProjectNature.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ManagedCProjectNature.java index 1c3a6f7d231..cf77ba2800d 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ManagedCProjectNature.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ManagedCProjectNature.java @@ -14,6 +14,7 @@ package org.eclipse.cdt.core; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Vector; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IProject; @@ -45,6 +46,22 @@ public class ManagedCProjectNature implements IProjectNature { // Add the builder to the project IProjectDescription description = project.getDescription(); ICommand[] commands = description.getBuildSpec(); + + // TODO Remove this when the new StandardBuild nature adds the cbuilder + for (int i = 0; i < commands.length; i++) { + ICommand command = commands[i]; + if (command.getBuilderName().equals("org.eclipse.cdt.core.cbuilder")) { + // Remove the command + Vector vec = new Vector(Arrays.asList(commands)); + vec.removeElementAt(i); + vec.trimToSize(); + ICommand[] tempCommands = (ICommand[]) vec.toArray(new ICommand[commands.length-1]); + description.setBuildSpec(tempCommands); + break; + } + } + + commands = description.getBuildSpec(); boolean found = false; // See if the builder is already there for (int i = 0; i < commands.length; ++i) { @@ -63,7 +80,7 @@ public class ManagedCProjectNature implements IProjectNature { newCommands[0] = command; description.setBuildSpec(newCommands); project.setDescription(description, null); - } + } } /** diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/CCorePluginResources.properties b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/CCorePluginResources.properties index 8e6744b513d..89a5370785c 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/CCorePluginResources.properties +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/CCorePluginResources.properties @@ -7,5 +7,15 @@ CBuilder.build_error= Build Error # Generated makefile builder messages -MakeBuilder.message.rebuild = Regenerating makefile for project {0} -MakeBuilder.message.incremental = Updating makefile for project {0} \ No newline at end of file +MakeBuilder.message.starting = Starting the build for project {0} +MakeBuilder.message.rebuild = Regenerating makefiles for project {0} +MakeBuilder.message.incremental = Updating makefiles for project {0} +MakeBuilder.message.make = Calling {0} for project {1} +MakeBuilder.message.error = Build error +MakeBuilder.message.finished = Build complete for project {0} +MakeBuilder.comment.module.list = # Every module must be described here +MakeBuilder.comment.source.list = # Each module must contribute its source files here +MakeBuilder.comment.build.rule = # Each module must supply rules for building sources it contributes +MakeBuilder.comment.module.make.includes = # Include the makefiles for each source module +MakeBuilder.comment.module.dep.includes = # Include automatically-generated dependency list: +MakeBuilder.comment.autodeps = # Automatically-generated dependency list: diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/GeneratedMakefileBuilder.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/GeneratedMakefileBuilder.java index 34bbb1811e5..5f81f777a85 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/GeneratedMakefileBuilder.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/GeneratedMakefileBuilder.java @@ -12,19 +12,34 @@ package org.eclipse.cdt.internal.core; * **********************************************************************/ import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.CommandLauncher; +import org.eclipse.cdt.core.ConsoleOutputStream; +import org.eclipse.cdt.core.ErrorParserManager; import org.eclipse.cdt.core.build.managed.IManagedBuildInfo; import org.eclipse.cdt.core.build.managed.ManagedBuildManager; +import org.eclipse.cdt.core.model.ICModelMarker; import org.eclipse.cdt.core.resources.ACBuilder; +import org.eclipse.cdt.core.resources.IConsole; import org.eclipse.cdt.core.resources.MakeUtil; -import org.eclipse.cdt.internal.core.model.Util; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; 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.IResourceStatus; +import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; @@ -33,18 +48,27 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; public class GeneratedMakefileBuilder extends ACBuilder { // String constants private static final String MESSAGE = "MakeBuilder.message"; //$NON-NLS-1$ - private static final String REBUILD = MESSAGE + ".rebuild"; //$NON-NLS-1$ + private static final String BUILD_ERROR = MESSAGE + ".error"; //$NON-NLS-1$ + private static final String BUILD_FINISHED = MESSAGE + ".finished"; //$NON-NLS-1$ private static final String INCREMENTAL = MESSAGE + ".incremental"; //$NON-NLS-1$ - private static final String FILENAME = "makefile"; //$NON-NLS-1$ - private static final String NEWLINE = System.getProperty("line.separator", "\n"); //$NON-NLS-1$ - private static final String COLON = ":"; - private static final String TAB = "\t"; //$NON-NLS-1$ + private static final String MAKE = MESSAGE + ".make"; //$NON-NLS-1$ + private static final String REBUILD = MESSAGE + ".rebuild"; //$NON-NLS-1$ + private static final String START = MESSAGE + ".starting"; //$NON-NLS-1$ + + // Status codes + public static final int EMPTY_PROJECT_BUILD_ERROR = 1; + + // Local variables + protected List resourcesToBuild; + protected List ruleList; + - public class MyResourceDeltaVisitor implements IResourceDeltaVisitor { + public class ResourceDeltaVisitor implements IResourceDeltaVisitor { boolean bContinue; public boolean visit(IResourceDelta delta) throws CoreException { @@ -67,102 +91,21 @@ public class GeneratedMakefileBuilder extends ACBuilder { super(); } - /** - * Add whatever macros we can figure out to the makefile. - * - * @param buffer - */ - private void addMacros(StringBuffer buffer, IManagedBuildInfo info) { - // TODO this should come from the build model - buffer.append("RM = rm -f" + NEWLINE); - buffer.append("MAKE = make" + NEWLINE); - buffer.append(NEWLINE); - } - - private void addRule(StringBuffer buffer, IPath sourcePath, String outputName, IManagedBuildInfo info) { - // Add the rule to the makefile - buffer.append(outputName + COLON + " " + sourcePath.toString()); - // Add all of the dependencies on the source file - - buffer.append(NEWLINE); - String ext = sourcePath.getFileExtension(); - String cmd = info.getToolForSource(ext); - String flags = info.getFlagsForSource(ext); - buffer.append(TAB + cmd + " " + flags + " " + "$?" + NEWLINE + NEWLINE); - } - - /** - * Creates a list of dependencies on project resources. - * - * @param buffer - */ - private void addSources(StringBuffer buffer, IManagedBuildInfo info) throws CoreException { - // Add the list of project files to be built - buffer.append("OBJS = \\" + NEWLINE); - - //Get a list of files from the project - IResource[] members = getProject().members(); - for (int i = 0; i < members.length; i++) { - IResource resource = members[i]; - IPath sourcePath = resource.getProjectRelativePath().removeFileExtension(); - String srcExt = resource.getFileExtension(); - String outExt = info.getOutputExtension(srcExt); - if (outExt != null) { - // Add the extension back to path - IPath outputPath = sourcePath.addFileExtension(outExt); - // Add the file to the list of dependencies for the base target - buffer.append(outputPath.toString() + " \\" + NEWLINE); - } - } - buffer.append(NEWLINE); - - // Add a rule for building each resource to the makefile - for (int j = 0; j < members.length; j++) { - IResource resource = members[j]; - IPath sourcePath = resource.getProjectRelativePath().removeFileExtension(); - String srcExt = resource.getFileExtension(); - String outExt = info.getOutputExtension(srcExt); - if (outExt != null) { - // Add the extension back to path - IPath outputPath = sourcePath.addFileExtension(outExt); - addRule(buffer, resource.getProjectRelativePath(), outputPath.toString(), info); - } - } - } - - /** - * @param buffer - */ - private void addTargets(StringBuffer buffer, IManagedBuildInfo info) { - // Generate a rule per source - - // This is the top build rule - String flags = info.getFlagsForTarget("exe") + " "; - String cmd = info.getToolForTarget("exe") + " "; - buffer.append(info.getBuildArtifactName() + COLON + " ${OBJS}" + NEWLINE); - buffer.append(TAB + cmd + flags + "$@ ${OBJS}" + NEWLINE); - buffer.append(NEWLINE); - - // TODO Generate 'all' for now but determine the real rules from UI - buffer.append("all: " + info.getBuildArtifactName() + NEWLINE); - buffer.append(NEWLINE); - - // Always add a clean target - buffer.append("clean:" + NEWLINE); - buffer.append(TAB + "$(RM) *.o " + info.getBuildArtifactName() + NEWLINE); - buffer.append(NEWLINE); - } - /* (non-Javadoc) * @see org.eclipse.core.internal.events.InternalBuilder#build(int, java.util.Map, org.eclipse.core.runtime.IProgressMonitor) */ protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { + String statusMsg = CCorePlugin.getFormattedString(START, getProject().getName()); + if (statusMsg != null) { + monitor.subTask(statusMsg); + } + if (kind == IncrementalProjectBuilder.FULL_BUILD) { fullBuild(monitor); } else { // Create a delta visitor to make sure we should be rebuilding - MyResourceDeltaVisitor visitor = new MyResourceDeltaVisitor(); + ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(); IResourceDelta delta = getDelta(getProject()); if (delta == null) { fullBuild(monitor); @@ -176,52 +119,107 @@ public class GeneratedMakefileBuilder extends ACBuilder { } // Checking to see if the user cancelled the build checkCancel(monitor); - // Build referenced projects + + // Ask build mechanism to compute deltas for project dependencies next time return getProject().getReferencedProjects(); } /** - * Check whether the build has been canceled. + * Check whether the build has been canceled. Cancellation requests + * propagated to the caller by throwing OperationCanceledException. + * + * @see org.eclipse.core.runtime.OperationCanceledException#OperationCanceledException() */ public void checkCancel(IProgressMonitor monitor) { - if (monitor != null && monitor.isCanceled()) + if (monitor != null && monitor.isCanceled()) { throw new OperationCanceledException(); + } } /** * @param monitor */ - private void fullBuild(IProgressMonitor monitor) throws CoreException { - // Rebuild the entire project - IProject currentProject = getProject(); - String statusMsg = null; - - // Need to report status to the user + protected void fullBuild(IProgressMonitor monitor) throws CoreException { + // Always need one of these bad boys if (monitor == null) { monitor = new NullProgressMonitor(); } - statusMsg = CCorePlugin.getFormattedString(REBUILD, currentProject.getName()); + + // We also need one of these ... + IProject currentProject = getProject(); + if (currentProject == null) { + // Flag some sort of error and bail + return; + } + + // Regenerate the makefiles for any managed projects this project depends on + IProject[] deps = currentProject.getReferencedProjects(); + for (int i = 0; i < deps.length; i++) { + IProject depProject = deps[i]; + if (ManagedBuildManager.manages(depProject)) { + IManagedBuildInfo depInfo = ManagedBuildManager.getBuildInfo(depProject); + MakefileGenerator generator = new MakefileGenerator(depProject, depInfo, monitor); + try { + generator.regenerateMakefiles(); + } catch (CoreException e) { + // This may be an empty project exception + if (e.getStatus().getCode() == GeneratedMakefileBuilder.EMPTY_PROJECT_BUILD_ERROR) { + // Just keep looking for other projects + continue; + } + } + } + } + + // Need to report status to the user + String statusMsg = CCorePlugin.getFormattedString(REBUILD, currentProject.getName()); monitor.subTask(statusMsg); - // Get a filehandle for the makefile - IPath filePath = getWorkingDirectory().append(IPath.SEPARATOR + FILENAME); - String temp = filePath.toString(); - IFile fileHandle = getMakefile(filePath, monitor); - - // Add the items to the makefile - populateMakefile(fileHandle, monitor); + // Regenerate the makefiles for this project + IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(getProject()); + MakefileGenerator generator = new MakefileGenerator(currentProject, info, monitor); + try { + generator.regenerateMakefiles(); + } catch (CoreException e) { + // See if this is an empty project + if (e.getStatus().getCode() == GeneratedMakefileBuilder.EMPTY_PROJECT_BUILD_ERROR) { + monitor.worked(1); + return; + } + } + IPath topBuildDir = generator.getTopBuildDir(); + + // Now call make + invokeMake(true, topBuildDir.removeFirstSegments(1), info, monitor); monitor.worked(1); } + protected IPath getBuildDirectory(String dirName) throws CoreException { + // Create or get the handle for the build directory + IFolder folder = getProject().getFolder(dirName); + if (!folder.exists()) { + try { + folder.create(false, true, null); + } + catch (CoreException e) { + if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) + folder.refreshLocal(IResource.DEPTH_ZERO, null); + else + throw e; + } + } + return folder.getFullPath(); + } + /** * Gets the makefile for the project. It may be empty. * * @return The IFile to generate the makefile into. */ - public IFile getMakefile(IPath filePath, IProgressMonitor monitor) throws CoreException { + protected IFile getMakefile(IPath filePath, IProgressMonitor monitor) throws CoreException { // Create or get the handle for the makefile - IWorkspaceRoot root= CCorePlugin.getWorkspace().getRoot(); + IWorkspaceRoot root = CCorePlugin.getWorkspace().getRoot(); IFile newFile = root.getFileForLocation(filePath); if (newFile == null) { newFile = root.getFile(filePath); @@ -242,6 +240,45 @@ public class GeneratedMakefileBuilder extends ACBuilder { return newFile; } + /** + * @param makefilePath + * @param info + * @return + */ + protected String[] getMakeTargets() { + List args = new ArrayList(); + // Add each target + String sessionTarget = MakeUtil.getSessionTarget(getProject()); + StringTokenizer tokens = new StringTokenizer(sessionTarget); + while (tokens.hasMoreTokens()) { + args.add(tokens.nextToken().trim()); + } + if (args.isEmpty()) { + args.add("all"); + } + return (String[])args.toArray(new String[args.size()]); + } + + /** + * @return + */ + protected List getResourcesToBuild() { + if (resourcesToBuild == null) { + resourcesToBuild = new ArrayList(); + } + return resourcesToBuild; + } + + /** + * @return + */ + protected List getRuleList() { + if (ruleList == null) { + ruleList = new ArrayList(); + } + return ruleList; + } + /* (non-Javadoc) * @see org.eclipse.cdt.core.resources.ACBuilder#getWorkingDirectory() */ @@ -257,7 +294,7 @@ public class GeneratedMakefileBuilder extends ACBuilder { * @param delta * @param monitor */ - private void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException { + protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException { // Rebuild the resource tree in the delta IProject currentProject = getProject(); String statusMsg = null; @@ -269,38 +306,140 @@ public class GeneratedMakefileBuilder extends ACBuilder { statusMsg = CCorePlugin.getFormattedString(INCREMENTAL, currentProject.getName()); monitor.subTask(statusMsg); - // Get a filehandle for the makefile - IPath filePath = getWorkingDirectory().append(IPath.SEPARATOR + FILENAME); - IFile fileHandle = getMakefile(filePath, monitor); - - // Now populate it - populateMakefile(fileHandle, monitor); + IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(getProject()); + IPath buildDir = new Path(info.getConfigurationName()); + invokeMake(false, buildDir, info, monitor); monitor.worked(1); } - /** - * Recreate the entire contents of the makefile. - * - * @param fileHandle The file to place the contents in. - */ - private void populateMakefile(IFile fileHandle, IProgressMonitor monitor) throws CoreException { - // Write out the contents of the build model - StringBuffer buffer = new StringBuffer(); - IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(getProject()); - - // Add the macro definitions - addMacros(buffer, info); + protected void invokeMake(boolean fullBuild, IPath buildDir, IManagedBuildInfo info, IProgressMonitor monitor) { + boolean isCanceled = false; + IProject currentProject = getProject(); + SubProgressMonitor subMonitor = null; + if (monitor == null) { + monitor = new NullProgressMonitor(); + } - // Add a list of source files - addSources(buffer, info); - - // Add targets - addTargets(buffer, info); + // Flag to the user that make is about to be called + IPath makeCommand = new Path(info.getMakeCommand()); + String[] msgs = new String[2]; + msgs[0] = info.getMakeCommand(); + msgs[1] = currentProject.getName(); + String statusMsg = CCorePlugin.getFormattedString(MAKE, msgs); + if (statusMsg != null) { + monitor.subTask(statusMsg); + } - // Save the file - Util.save(buffer, fileHandle); + // Get a build console for the project + IConsole console = null; + ConsoleOutputStream consoleOutStream = null; + IWorkspace workspace = null; + IMarker[] markers = null; + try { + console = CCorePlugin.getDefault().getConsole(); + console.start(currentProject); + consoleOutStream = console.getOutputStream(); + + // Remove all markers for this project + workspace = currentProject.getWorkspace(); + markers = currentProject.findMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); + if (markers != null) { + workspace.deleteMarkers(markers); + } + } catch (CoreException e) { + } + + IPath workingDirectory = getWorkingDirectory().append(buildDir); + + // Get the arguments to be passed to make from build model + String[] makeTargets = getMakeTargets(); + + // Get a launcher for the make command + String errMsg = null; + CommandLauncher launcher = new CommandLauncher(); + launcher.showCommand(true); + + // Set the environmennt, some scripts may need the CWD var to be set. + Properties props = launcher.getEnvironment(); + props.put("CWD", workingDirectory.toOSString()); + props.put("PWD", workingDirectory.toOSString()); + String[] env = null; + ArrayList envList = new ArrayList(); + Enumeration names = props.propertyNames(); + if (names != null) { + while (names.hasMoreElements()) { + String key = (String) names.nextElement(); + envList.add(key + "=" + props.getProperty(key)); + } + env = (String[]) envList.toArray(new String[envList.size()]); + } + + // Hook up an error parser + ErrorParserManager epm = new ErrorParserManager(this); + epm.setOutputStream(consoleOutStream); + OutputStream stdout = epm.getOutputStream(); + OutputStream stderr = epm.getOutputStream(); + + // Launch make + Process proc = launcher.execute(makeCommand, makeTargets, env, workingDirectory); + if (proc != null) { + try { + // Close the input of the Process explicitely. + // We will never write to it. + proc.getOutputStream().close(); + } catch (IOException e) { + } + subMonitor = new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN); + if (launcher.waitAndRead(stdout, stderr, subMonitor) != CommandLauncher.OK) { + errMsg = launcher.getErrorMessage(); + + isCanceled = monitor.isCanceled(); + monitor.setCanceled(false); + subMonitor = new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN); + subMonitor.subTask("Refresh From Local"); + + try { + currentProject.refreshLocal(IResource.DEPTH_INFINITE, subMonitor); + } catch (CoreException e) { + } + + subMonitor = new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN); + subMonitor.subTask("Parsing"); + } else { + errMsg = launcher.getErrorMessage(); + } + + + // Report either the success or failure of our mission + StringBuffer buf = new StringBuffer(); + if (errMsg != null && errMsg.length() > 0) { + String errorDesc = CCorePlugin.getResourceString(BUILD_ERROR); + buf.append(errorDesc); + buf.append(System.getProperty("line.separator", "\n")); + buf.append("(").append(errMsg).append(")"); + } + else { + // Report a successful build + String successMsg = CCorePlugin.getFormattedString(BUILD_FINISHED, currentProject.getName()); + buf.append(successMsg); + buf.append(System.getProperty("line.separator", "\n")); + } + // Write your message on the pavement + try { + consoleOutStream.write(buf.toString().getBytes()); + consoleOutStream.flush(); + stdout.close(); + stderr.close(); + } catch (IOException e) { + } + + epm.reportProblems(); + + subMonitor.done(); + monitor.setCanceled(isCanceled); + } + monitor.done(); } - } diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/MakefileGenerator.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/MakefileGenerator.java new file mode 100644 index 00000000000..c27bb1d03a6 --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/MakefileGenerator.java @@ -0,0 +1,574 @@ +package org.eclipse.cdt.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.build.managed.IManagedBuildInfo; +import org.eclipse.cdt.core.search.ICSearchConstants; +import org.eclipse.cdt.internal.core.model.Util; +import org.eclipse.cdt.internal.core.sourcedependency.DependencyManager; +import org.eclipse.cdt.internal.core.sourcedependency.DependencyQueryJob; +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.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.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; + +public class MakefileGenerator { + // String constants for messages + private static final String MESSAGE = "MakeBuilder.message"; //$NON-NLS-1$ + private static final String BUILD_ERROR = MESSAGE + ".error"; //$NON-NLS-1$ + private static final String COMMENT = "MakeBuilder.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 = "module.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 = "module.mk"; //$NON-NLS-1$ + protected static final String LINEBREAK = "\\"; + protected static final String NEWLINE = System.getProperty("line.separator"); + protected static final String SEMI_COLON = ";"; + 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 moduleList; + protected IProgressMonitor monitor; + protected IProject project; + protected List ruleList; + protected IPath topBuildDir; + + /** + * 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)) { + generator.appendModule(resource); + } + return false; + } + + // Recurse into subdirectories + return true; + } + + } + + 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; + } + + /** + * @param module + * @return + */ + protected StringBuffer addDeps(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(CCorePlugin.getResourceString(AUTO_DEP) + NEWLINE); + DependencyManager dependencyManager = CCorePlugin.getDefault().getCoreModel().getDependencyManager(); + + /* + * Visit each resource in the folder that we have a rule to build. + * The dependency output for each resource will be in the format + * /. : ... + * 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 { + dependencyManager.performConcurrentJob(new DependencyQueryJob(project, (IFile)resource, dependencyManager, dependencies), ICSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, 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; + } + + /** + * @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); + + // Add the macro for the output flag + buffer.append("OUTPUT_FLAG := "); + buffer.append(info.getOutputFlag() + NEWLINE); + + buffer.append(CCorePlugin.getResourceString(SRC_LISTS) + NEWLINE); + buffer.append("C_SRCS := " + NEWLINE); + buffer.append("CC_SRCS := " + NEWLINE); + + buffer.append(NEWLINE + NEWLINE); + return buffer; + } + + /** + * @return + */ + protected StringBuffer addModules() { + StringBuffer buffer = new StringBuffer(); + // Add the comment + buffer.append(CCorePlugin.getResourceString(MOD_LIST) + NEWLINE); + buffer.append("MODULES := " + LINEBREAK + NEWLINE); + buffer.append("." + LINEBREAK + NEWLINE); + + // Get all the module names + ListIterator iter = getModuleList().listIterator(); + while (iter.hasNext()) { + IContainer container = (IContainer) iter.next(); + IPath path = container.getProjectRelativePath(); + buffer.append(path.toString() + WHITESPACE + LINEBREAK + NEWLINE); + } + + buffer.append(NEWLINE); + buffer.append(CCorePlugin.getResourceString(MOD_INCL) + NEWLINE); + buffer.append("include ${patsubst %, %/module.mk, $(MODULES)}" + NEWLINE); + + buffer.append(NEWLINE + NEWLINE); + return buffer; + } + + + /** + * Answers a StringBuffer containing all of the sources contributed by + * a container to the build. + * @param module + * @return + */ + 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 ruleBuffer = new StringBuffer(CCorePlugin.getResourceString(MOD_RULES) + NEWLINE); + + // Put the comment in + buffer.append(CCorePlugin.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)) { + // TODO use build model to determine what list the file goes in + ccBuffer.append(resource.getName() + WHITESPACE + LINEBREAK + NEWLINE); + // Try to add the rule for the file + addRule(relativePath, ruleBuffer, resource); + } + } + } + + // Finish the commands in the buffers + cBuffer.append("}" + NEWLINE + NEWLINE); + ccBuffer.append("}" + NEWLINE + NEWLINE); + + // Append them all together + buffer.append(cBuffer).append(ccBuffer).append(ruleBuffer); + return buffer; + } + + /** + * Answers a StrinBuffer containing all of the required targets to + * properly build the project. + */ + protected StringBuffer addTargets() { + StringBuffer buffer = new StringBuffer(); + + // Get the target and it's extension + String target = info.getBuildArtifactName(); + IPath temp = new Path(target); + String extension = temp.getFileExtension(); + + /* + * Write out the taqrget rule as: + * .: $(CC_SRCS:$(ROOT)/%.cpp=%.o) $(C_SRCS:$(ROOT)/%.c=%.o) + * $(BUILD_TOOL) $(FLAGS) $(OUTPUT_FLAG) $@ $^ $(LIB_DEPS) + */ + String cmd = info.getToolForTarget(extension); + String flags = info.getFlagsForTarget(extension); + buffer.append(target + COLON + WHITESPACE + "$(CC_SRCS:$(ROOT)/%.cpp=%.o) $(C_SRCS:$(ROOT)/%.c=%.o)" + NEWLINE); + buffer.append(TAB + cmd + WHITESPACE + flags + WHITESPACE + "$(OUTPUT_FLAG) $@" + WHITESPACE + "$^" + WHITESPACE + NEWLINE); + buffer.append(NEWLINE); + + // TODO Generate 'all' for now but determine the real rules from UI + buffer.append("all: " + target + NEWLINE); + buffer.append(NEWLINE); + + // Always add a clean target + buffer.append(".PHONY: clean" + NEWLINE); + buffer.append("clean:" + NEWLINE); + buffer.append(TAB + "$(RM)" + WHITESPACE + "${addprefix ., $(CC_SRCS:$(ROOT)%.cpp=%.o)} ${addprefix ., $(C_SRCS:$(ROOT)%.c=%.o)}" + WHITESPACE + target + NEWLINE); + buffer.append(NEWLINE); + + buffer.append(NEWLINE + CCorePlugin.getResourceString(DEP_INCL) + NEWLINE); + buffer.append("include ${patsubst %, %/module.dep, $(MODULES)}" + NEWLINE); + + buffer.append(NEWLINE); + return buffer; + } + + protected void addRule(String relativePath, StringBuffer buffer, IResource resource) { + String rule = null; + String cmd = null; + String buildFlags = null; + String inputExtension = null; + String outputExtension = 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 + * /%.o: $(ROOT)//%.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); + + buffer.append(TAB + cmd + WHITESPACE + buildFlags + WHITESPACE + "$(OUTPUT_FLAG) $@" + WHITESPACE + "$<" + NEWLINE + NEWLINE); + } + } + + /** + * @param resource + */ + public void appendModule(IResource resource) { + // The build model knows how to build this file + IContainer container = resource.getParent(); + if (!getModuleList().contains(container)) { + getModuleList().add(container); + } + } + + /** + * Check whether the build has been canceled. Cancellation requests + * propagated to the caller by throwing OperationCanceledException. + * + * @see org.eclipse.core.runtime.OperationCanceledException#OperationCanceledException() + */ + public void checkCancel() { + if (monitor != null && monitor.isCanceled()) { + throw new OperationCanceledException(); + } + } + + /** + * @return + */ + private List getModuleList() { + if (moduleList == null) { + moduleList = new ArrayList(); + } + return moduleList; + } + + /** + * + */ + private List getRuleList() { + if (ruleList == null) { + ruleList = new ArrayList(); + } + return ruleList; + } + + /** + * @param string + * @return + */ + 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(); + } + + /** + * @param makefilePath + * @param monitor + * @return + */ + 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; + } + // TODO handle long running file operation + return newFile; + } + + /** + * Answers the IPath of the top directory generated for the build + * output, or null if none has been generated. + * + * @return + */ + public IPath getTopBuildDir() { + return topBuildDir; + } + + /** + * Create the entire contents of the makefile. + * + * @param fileHandle The file to place the contents in. + * @param info + * @param monitor + */ + protected void populateMakefile(IFile fileHandle) { + StringBuffer buffer = new StringBuffer(); + + // Add the macro definitions + buffer.append(addMacros()); + + // Append the module list + buffer.append(addModules()); + + // Add targets + buffer.append(addTargets()); + + // Save the file + try { + Util.save(buffer, fileHandle); + } catch (CoreException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + + /** + * @param module + */ + protected void populateModMakefile(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(addDeps(module)); + + // Save the files + Util.save(makeBuf, modMakefile); + Util.save(depBuf, modDepfile); + } + + + public void regenerateMakefiles() throws CoreException { + // Visit the resources in the project + ResourceProxyVisitor visitor = new ResourceProxyVisitor(this, info); + project.accept(visitor, IResource.NONE); + if (getModuleList().isEmpty()) { + // There is nothing to build + IStatus status = new Status(IStatus.INFO, CCorePlugin.PLUGIN_ID, GeneratedMakefileBuilder.EMPTY_PROJECT_BUILD_ERROR, "", null); + throw new CoreException(status); + } + + // See if the user has cancelled the build + checkCancel(); + + // 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); + + // Populate the makefile + populateMakefile(makefileHandle); + checkCancel(); + + // Now populate the module makefiles + ListIterator iter = getModuleList().listIterator(); + while (iter.hasNext()) { + populateModMakefile((IContainer)iter.next()); + checkCancel(); + } + } + +}